From 8690f7c6cb6dbbf5e878471cf5b20f3a74af5f68 Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Fri, 5 Jun 2026 18:59:40 +0200
Subject: [PATCH] groupware: port tests to Stalwart 0.16.7
---
go.mod | 4 +-
go.sum | 4 -
pkg/jmap/export_integration_test.go | 1135 +++++++++++++----
pkg/jmap/http.go | 1 +
pkg/jmap/integration_addressbook_test.go | 16 +-
pkg/jmap/integration_calendar_test.go | 12 +-
pkg/jmap/integration_email_test.go | 30 +-
pkg/jmap/integration_ws_test.go | 4 +-
pkg/jmap/model_examples.go | 26 +-
pkg/jmap/tools.go | 10 +-
vendor/github.com/go-crypt/crypt/LICENSE | 21 -
.../go-crypt/crypt/algorithm/const.go | 26 -
.../go-crypt/crypt/algorithm/doc.go | 3 -
.../go-crypt/crypt/algorithm/errors.go | 66 -
.../crypt/algorithm/shacrypt/const.go | 46 -
.../crypt/algorithm/shacrypt/decoder.go | 136 --
.../crypt/algorithm/shacrypt/digest.go | 83 --
.../go-crypt/crypt/algorithm/shacrypt/doc.go | 7 -
.../crypt/algorithm/shacrypt/hasher.go | 156 ---
.../go-crypt/crypt/algorithm/shacrypt/opts.go | 98 --
.../crypt/algorithm/shacrypt/variant.go | 92 --
.../go-crypt/crypt/algorithm/types.go | 62 -
.../crypt/internal/encoding/base64adapted.go | 14 -
.../go-crypt/crypt/internal/encoding/const.go | 9 -
.../crypt/internal/encoding/digest.go | 10 -
.../go-crypt/crypt/internal/encoding/doc.go | 2 -
.../crypt/internal/encoding/parameters.go | 54 -
.../go-crypt/crypt/internal/random/bytes.go | 32 -
.../go-crypt/crypt/internal/random/doc.go | 2 -
vendor/github.com/go-crypt/x/LICENSE | 27 -
vendor/github.com/go-crypt/x/base64/base64.go | 51 -
vendor/github.com/go-crypt/x/base64/const.go | 7 -
vendor/github.com/go-crypt/x/crypt/const.go | 223 ----
vendor/github.com/go-crypt/x/crypt/crypt.go | 300 -----
vendor/github.com/go-crypt/x/crypt/util.go | 54 -
.../testcontainers-go/network/network.go | 196 +++
vendor/modules.txt | 11 +-
37 files changed, 1108 insertions(+), 1922 deletions(-)
delete mode 100644 vendor/github.com/go-crypt/crypt/LICENSE
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/const.go
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/doc.go
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/errors.go
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/shacrypt/const.go
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/shacrypt/decoder.go
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/shacrypt/digest.go
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/shacrypt/doc.go
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/shacrypt/hasher.go
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/shacrypt/opts.go
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/shacrypt/variant.go
delete mode 100644 vendor/github.com/go-crypt/crypt/algorithm/types.go
delete mode 100644 vendor/github.com/go-crypt/crypt/internal/encoding/base64adapted.go
delete mode 100644 vendor/github.com/go-crypt/crypt/internal/encoding/const.go
delete mode 100644 vendor/github.com/go-crypt/crypt/internal/encoding/digest.go
delete mode 100644 vendor/github.com/go-crypt/crypt/internal/encoding/doc.go
delete mode 100644 vendor/github.com/go-crypt/crypt/internal/encoding/parameters.go
delete mode 100644 vendor/github.com/go-crypt/crypt/internal/random/bytes.go
delete mode 100644 vendor/github.com/go-crypt/crypt/internal/random/doc.go
delete mode 100644 vendor/github.com/go-crypt/x/LICENSE
delete mode 100644 vendor/github.com/go-crypt/x/base64/base64.go
delete mode 100644 vendor/github.com/go-crypt/x/base64/const.go
delete mode 100644 vendor/github.com/go-crypt/x/crypt/const.go
delete mode 100644 vendor/github.com/go-crypt/x/crypt/crypt.go
delete mode 100644 vendor/github.com/go-crypt/x/crypt/util.go
create mode 100644 vendor/github.com/testcontainers/testcontainers-go/network/network.go
diff --git a/go.mod b/go.mod
index 0e4717e8b5..a6ac5213bf 100644
--- a/go.mod
+++ b/go.mod
@@ -25,7 +25,6 @@ require (
github.com/ggwhite/go-masker v1.1.0
github.com/go-chi/chi/v5 v5.3.0
github.com/go-chi/render v1.0.3
- github.com/go-crypt/crypt v0.4.5
github.com/go-jose/go-jose/v3 v3.0.5
github.com/go-ldap/ldap/v3 v3.4.13
github.com/go-ldap/ldif v0.0.0-20200320164324-fd88d9b715b3
@@ -62,6 +61,7 @@ require (
github.com/microcosm-cc/bluemonday v1.0.27
github.com/miekg/dns v1.1.68
github.com/mna/pigeon v1.3.0
+ github.com/moby/moby/api v1.54.1
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/nats-io/nats-server/v2 v2.14.2
github.com/nats-io/nats.go v1.51.0
@@ -220,7 +220,6 @@ require (
github.com/gdexlab/go-render v1.0.1 // indirect
github.com/go-acme/lego/v4 v4.4.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
- github.com/go-crypt/x v0.4.7 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-git/go-git/v5 v5.19.1 // indirect
@@ -310,7 +309,6 @@ require (
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
- github.com/moby/moby/api v1.54.1 // indirect
github.com/moby/moby/client v0.4.0 // indirect
github.com/moby/patternmatcher v0.6.1 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
diff --git a/go.sum b/go.sum
index 92276a95d6..2101d70e21 100644
--- a/go.sum
+++ b/go.sum
@@ -392,10 +392,6 @@ github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutV
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
-github.com/go-crypt/crypt v0.4.5 h1:cCR5vVejGk1kurwoGfkLxGORY+Pc9GiE7xKCpyHZ3n4=
-github.com/go-crypt/crypt v0.4.5/go.mod h1:cQijpCkqavdF52J1bE0PObWwqKKjQCHASHQ2dtLzOJs=
-github.com/go-crypt/x v0.4.7 h1:hObjW67nhq/GI1jaD7XCv5RoiVKzF46XIbULgzH71oU=
-github.com/go-crypt/x v0.4.7/go.mod h1:K3q7VmLC0U1QFAPn0SQvXjkAtu6FJuH0rN9LNqobX6k=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
diff --git a/pkg/jmap/export_integration_test.go b/pkg/jmap/export_integration_test.go
index ae596bbfd1..b1b72ef3b3 100644
--- a/pkg/jmap/export_integration_test.go
+++ b/pkg/jmap/export_integration_test.go
@@ -1,9 +1,11 @@
package jmap
import (
+ "archive/tar"
"bytes"
"context"
"crypto/tls"
+ "encoding/base64"
"encoding/json"
"fmt"
"io"
@@ -16,6 +18,7 @@ import (
"os"
"reflect"
"regexp"
+ "runtime"
"slices"
"strconv"
"strings"
@@ -24,22 +27,22 @@ import (
"time"
"github.com/google/go-cmp/cmp"
+ dockernetwork "github.com/moby/moby/api/types/network"
"github.com/stretchr/testify/require"
"github.com/gorilla/websocket"
"github.com/tidwall/pretty"
"github.com/testcontainers/testcontainers-go"
+ "github.com/testcontainers/testcontainers-go/network"
"github.com/testcontainers/testcontainers-go/wait"
"github.com/brianvoe/gofakeit/v7"
pw "github.com/sethvargo/go-password/password"
"github.com/opencloud-eu/opencloud/pkg/jscontact"
- clog "github.com/opencloud-eu/opencloud/pkg/log"
+ oclog "github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/structs"
-
- "github.com/go-crypt/crypt/algorithm/shacrypt"
)
const (
@@ -52,87 +55,228 @@ const (
type User struct {
name string
description string
- email string
+ alias string
password string
+ email string
}
func userpassword() string {
- password, err := pw.Generate(4+rand.Intn(28), 2, 0, false, true)
+ password, err := pw.Generate(10+rand.Intn(28), 2, 0, false, true)
if err != nil {
panic(err)
}
return password
}
+func mkuser(name string, description string, alias string) User {
+ parts := strings.Split(alias, "@")
+ return User{name: name, description: description, alias: alias, email: name + "@" + parts[1], password: userpassword()}
+}
+
var (
- domains = [...]string{"earth.gov", "mars.mil", "opa.org"}
- users = [...]User{
- {"cdrummer", "Camina Drummer", "camina.drummer@opa.org", userpassword()},
- {"aburton", "Amos Burton", "amos.burton@earth.gov", userpassword()},
- {"jholden", "James Holden", "james.holden@earth.gov", userpassword()},
- {"adawes", "Anderson Dawes", "anderson.dawes@opa.org", userpassword()},
- {"nnagata", "Naomi Nagata", "naomi.nagata@opa.org", userpassword()},
- {"kashford", "Klaes Ashford", "klaes.ashford@opa.org", userpassword()},
- {"fjohnson", "Fred Johnson", "fred.johnson@opa.org", userpassword()},
- {"cavasarala", "Chrisjen Avasarala}", "chrissy@earth.gov", userpassword()},
- {"bdraper", "Roberta Draper", "bobby@mars.mil", userpassword()},
+ users = [...]User{
+ mkuser("cdrummer", "Camina Drummer", "camina.drummer@opa.org"),
+ mkuser("aburton", "Amos Burton", "amos.burton@earth.gov"),
+ mkuser("jholden", "James Holden", "james.holden@earth.gov"),
+ mkuser("adawes", "Anderson Dawes", "anderson.dawes@opa.org"),
+ mkuser("nnagata", "Naomi Nagata", "naomi.nagata@opa.org"),
+ mkuser("kashford", "Klaes Ashford", "klaes.ashford@opa.org"),
+ mkuser("fjohnson", "Fred Johnson", "fred.johnson@opa.org"),
+ mkuser("cavasarala", "Chrisjen Avasarala}", "chrissy@earth.gov"),
+ mkuser("bdraper", "Roberta Draper", "bobby@mars.mil"),
}
)
const (
- stalwartImage = "ghcr.io/stalwartlabs/stalwart:v0.15.5-alpine"
- httpPort = "8080"
- imapsPort = "993"
- configTemplate = `
-authentication.fallback-admin.secret = "secret"
-authentication.fallback-admin.user = "mailadmin"
-authentication.master.secret = "{{.masterpassword}}"
-authentication.master.user = "{{.masterusername}}"
-directory.test.bind.auth.method = "default"
-directory.test.cache.size = 1048576
-directory.test.cache.ttl.negative = "10m"
-directory.test.cache.ttl.positive = "1h"
-directory.test.store = "rocksdb"
-directory.test.type = "internal"
-metrics.prometheus.enable = false
-server.listener.http.bind = "[::]:{{.httpPort}}"
-server.listener.http.protocol = "http"
-server.listener.imaptls.bind = "[::]:{{.imapsPort}}"
-server.listener.imaptls.protocol = "imap"
-server.listener.imaptls.tls.implicit = true
-server.hostname = "{{.hostname}}"
-server.max-connections = 8192
-server.socket.backlog = 1024
-server.socket.nodelay = true
-server.socket.reuse-addr = true
-server.socket.reuse-port = true
-storage.blob = "rocksdb"
-storage.data = "rocksdb"
-storage.directory = "test"
-storage.fts = "rocksdb"
-storage.lookup = "rocksdb"
-store.rocksdb.compression = "lz4"
-store.rocksdb.path = "/opt/stalwart/data"
-store.rocksdb.type = "rocksdb"
-tracer.log.ansi = false
-tracer.log.buffered = false
-tracer.log.enable = true
-tracer.log.level = "trace"
-tracer.log.lossy = false
-tracer.log.multiline = false
-tracer.log.type = "stdout"
-sharing.allow-directory-query = {{.dirquery}}
-auth.dkim.sign = false
-auth.dkim.verify = "disable"
-auth.spf.verify.ehlo = "disable"
-auth.spf.verify.mail-from = "disable"
-auth.arc.verify = "disable"
-auth.arc.seal = false
-auth.dmarc.verify = "disable"
-auth.iprev.verify = "disable"
+ stalwartImage = "ghcr.io/stalwartlabs/stalwart:v0.16.7-alpine"
+ httpPort = "8080"
+ imapsPort = "993"
+ jsonConfigTemplate = `{"@type":"RocksDb","path":"/var/lib/stalwart/","blobSize":16834,"bufferSize":134217728,"poolWorkers":null}`
+ cliDockerfile = `
+FROM alpine:latest
+ARG VERSION="1.0.8"
+ARG ARCH="x86_64"
+RUN apk add --no-cache curl tar xz && curl --proto '=https' --tlsv1.2 -LsSf https://github.com/stalwartlabs/cli/releases/download/v${VERSION}/stalwart-cli-${ARCH}-unknown-linux-musl.tar.xz | tar xJf - --strip-components=1 -C /usr/local/bin/ && rm /usr/local/bin/*.md
+CMD ["/usr/local/bin/stalwart-cli"]
`
)
+var (
+ dumpTemplate = []string{
+ `{"@type":"destroy","object":"Tracer"}`,
+ `{"@type":"destroy","object":"NetworkListener"}`,
+ `{"@type":"destroy","object":"DnsServer"}`,
+ `{"@type":"destroy","object":"Certificate"}`,
+ `{"@type":"destroy","object":"Tenant"}`,
+ `{"@type":"destroy","object":"Role"}`,
+ `{"@type":"destroy","object":"AcmeProvider"}`,
+ `{"@type":"destroy","object":"Directory"}`,
+ `{"@type":"destroy","object":"Domain"}`,
+ `{"@type":"destroy","object":"Account"}`,
+ `{"@type":"destroy","object":"DkimSignature"}`,
+ `{"@type":"create","object":"Tracer","value":{"tracer-iugg9zgwaaaa":{"@type":"Stdout","eventsPolicy":"exclude","enable":true,"multiline":false,"level":"trace","lossy":false,"buffered":true,"ansi":false,"events":{"store.data-write":true,"eval.result":true,"store.cache-hit":true,"store.cache-miss":true,"store.cache-stale":true,"store.cache-update":true,"store.data-iterate":true,"spam.rules-updated":true,"resource.download-external":true,"task-manager.task-acquired":true,"task-manager.scheduler-started":true,"task-manager.manager-started":true}}}}`,
+ `{"@type":"create","object":"NetworkListener","value":{"networklistener-iughfwjyahqb":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"http","bind":{"[::]:8080":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":false,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"http"},"networklistener-iughfwjyahab":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"https","bind":{"[::]:443":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":true,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"http"},"networklistener-iughfwjyagqb":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"sieve","bind":{"[::]:4190":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":false,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"manageSieve"},"networklistener-iughfwjyagab":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"pop3s","bind":{"[::]:995":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":true,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"pop3"},"networklistener-iughfwjyafqb":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"imaps","bind":{"[::]:993":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":true,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"imap"},"networklistener-iughfwjwafab":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"submissions","bind":{"[::]:465":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":true,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"smtp"},"networklistener-iughfwjwaeqb":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"smtp","bind":{"[::]:25":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":false,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"smtp"}}}`,
+ `{"@type":"create","object":"Role","value":{"role-e":{"disabledPermissions":{},"memberTenantId":null,"enabledPermissions":{"authenticate":true,"authenticateWithAlias":true,"interactAi":true,"impersonate":true,"unlimitedRequests":true,"unlimitedUploads":true,"fetchAnyBlob":true,"oAuthClientRegistration":true,"oAuthClientOverride":true,"liveTracing":true,"liveMetrics":true,"liveDeliveryTest":true,"sysAccountGet":true,"sysAccountCreate":true,"sysAccountUpdate":true,"sysAccountDestroy":true,"sysAccountQuery":true,"sysAccountPasswordGet":true,"sysAccountPasswordUpdate":true,"sysAccountSettingsGet":true,"sysAccountSettingsUpdate":true,"sysAcmeProviderGet":true,"sysAcmeProviderCreate":true,"sysAcmeProviderUpdate":true,"sysAcmeProviderDestroy":true,"sysAcmeProviderQuery":true,"actionReloadSettings":true,"actionReloadTlsCertificates":true,"actionReloadLookupStores":true,"actionReloadBlockedIps":true,"actionUpdateApps":true,"actionTroubleshootDmarc":true,"actionClassifySpam":true,"actionInvalidateCaches":true,"actionInvalidateNegativeCaches":true,"actionPauseMtaQueue":true,"actionResumeMtaQueue":true,"sysActionGet":true,"sysActionCreate":true,"sysActionUpdate":true,"sysActionDestroy":true,"sysActionQuery":true,"sysAddressBookGet":true,"sysAddressBookUpdate":true,"sysAiModelGet":true,"sysAiModelCreate":true,"sysAiModelUpdate":true,"sysAiModelDestroy":true,"sysAiModelQuery":true,"sysAlertGet":true,"sysAlertCreate":true,"sysAlertUpdate":true,"sysAlertDestroy":true,"sysAlertQuery":true,"sysAllowedIpGet":true,"sysAllowedIpCreate":true,"sysAllowedIpUpdate":true,"sysAllowedIpDestroy":true,"sysAllowedIpQuery":true,"sysApiKeyGet":true,"sysApiKeyCreate":true,"sysApiKeyUpdate":true,"sysApiKeyDestroy":true,"sysApiKeyQuery":true,"sysAppPasswordGet":true,"sysAppPasswordCreate":true,"sysAppPasswordUpdate":true,"sysAppPasswordDestroy":true,"sysAppPasswordQuery":true,"sysApplicationGet":true,"sysApplicationCreate":true,"sysApplicationUpdate":true,"sysApplicationDestroy":true,"sysApplicationQuery":true,"sysArchivedItemGet":true,"sysArchivedItemCreate":true,"sysArchivedItemUpdate":true,"sysArchivedItemDestroy":true,"sysArchivedItemQuery":true,"sysArfExternalReportGet":true,"sysArfExternalReportCreate":true,"sysArfExternalReportUpdate":true,"sysArfExternalReportDestroy":true,"sysArfExternalReportQuery":true,"sysAsnGet":true,"sysAsnUpdate":true,"sysAuthenticationGet":true,"sysAuthenticationUpdate":true,"sysBlobStoreGet":true,"sysBlobStoreUpdate":true,"sysBlockedIpGet":true,"sysBlockedIpCreate":true,"sysBlockedIpUpdate":true,"sysBlockedIpDestroy":true,"sysBlockedIpQuery":true,"sysBootstrapGet":true,"sysBootstrapUpdate":true,"sysCacheGet":true,"sysCacheUpdate":true,"sysCalendarGet":true,"sysCalendarUpdate":true,"sysCalendarAlarmGet":true,"sysCalendarAlarmUpdate":true,"sysCalendarSchedulingGet":true,"sysCalendarSchedulingUpdate":true,"sysCertificateGet":true,"sysCertificateCreate":true,"sysCertificateUpdate":true,"sysCertificateDestroy":true,"sysCertificateQuery":true,"sysClusterNodeGet":true,"sysClusterNodeCreate":true,"sysClusterNodeUpdate":true,"sysClusterNodeDestroy":true,"sysClusterNodeQuery":true,"sysClusterRoleGet":true,"sysClusterRoleCreate":true,"sysClusterRoleUpdate":true,"sysClusterRoleDestroy":true,"sysClusterRoleQuery":true,"sysCoordinatorGet":true,"sysCoordinatorUpdate":true,"sysDataRetentionGet":true,"sysDataRetentionUpdate":true,"sysDataStoreGet":true,"sysDataStoreUpdate":true,"sysDirectoryGet":true,"sysDirectoryCreate":true,"sysDirectoryUpdate":true,"sysDirectoryDestroy":true,"sysDirectoryQuery":true,"sysDkimReportSettingsGet":true,"sysDkimReportSettingsUpdate":true,"sysDkimSignatureGet":true,"sysDkimSignatureCreate":true,"sysDkimSignatureUpdate":true,"sysDkimSignatureDestroy":true,"sysDkimSignatureQuery":true,"sysDmarcExternalReportGet":true,"sysDmarcExternalReportCreate":true,"sysDmarcExternalReportUpdate":true,"sysDmarcExternalReportDestroy":true,"sysDmarcExternalReportQuery":true,"sysDmarcInternalReportGet":true,"sysDmarcInternalReportCreate":true,"sysDmarcInternalReportUpdate":true,"sysDmarcInternalReportDestroy":true,"sysDmarcInternalReportQuery":true,"sysDmarcReportSettingsGet":true,"sysDmarcReportSettingsUpdate":true,"sysDnsResolverGet":true,"sysDnsResolverUpdate":true,"sysDnsServerGet":true,"sysDnsServerCreate":true,"sysDnsServerUpdate":true,"sysDnsServerDestroy":true,"sysDnsServerQuery":true,"sysDomainGet":true,"sysDomainCreate":true,"sysDomainUpdate":true,"sysDomainDestroy":true,"sysDomainQuery":true,"sysDsnReportSettingsGet":true,"sysDsnReportSettingsUpdate":true,"sysEmailGet":true,"sysEmailUpdate":true,"sysEnterpriseGet":true,"sysEnterpriseUpdate":true,"sysEventTracingLevelGet":true,"sysEventTracingLevelCreate":true,"sysEventTracingLevelUpdate":true,"sysEventTracingLevelDestroy":true,"sysEventTracingLevelQuery":true,"sysFileStorageGet":true,"sysFileStorageUpdate":true,"sysHttpGet":true,"sysHttpUpdate":true,"sysHttpFormGet":true,"sysHttpFormUpdate":true,"sysHttpLookupGet":true,"sysHttpLookupCreate":true,"sysHttpLookupUpdate":true,"sysHttpLookupDestroy":true,"sysHttpLookupQuery":true,"sysImapGet":true,"sysImapUpdate":true,"sysInMemoryStoreGet":true,"sysInMemoryStoreUpdate":true,"sysJmapGet":true,"sysJmapUpdate":true,"sysLogGet":true,"sysLogCreate":true,"sysLogUpdate":true,"sysLogDestroy":true,"sysLogQuery":true,"sysMailingListGet":true,"sysMailingListCreate":true,"sysMailingListUpdate":true,"sysMailingListDestroy":true,"sysMailingListQuery":true,"sysMaskedEmailGet":true,"sysMaskedEmailCreate":true,"sysMaskedEmailUpdate":true,"sysMaskedEmailDestroy":true,"sysMaskedEmailQuery":true,"sysMemoryLookupKeyGet":true,"sysMemoryLookupKeyCreate":true,"sysMemoryLookupKeyUpdate":true,"sysMemoryLookupKeyDestroy":true,"sysMemoryLookupKeyQuery":true,"sysMemoryLookupKeyValueGet":true,"sysMemoryLookupKeyValueCreate":true,"sysMemoryLookupKeyValueUpdate":true,"sysMemoryLookupKeyValueDestroy":true,"sysMemoryLookupKeyValueQuery":true,"sysMetricGet":true,"sysMetricCreate":true,"sysMetricUpdate":true,"sysMetricDestroy":true,"sysMetricQuery":true,"sysMetricsGet":true,"sysMetricsUpdate":true,"sysMetricsStoreGet":true,"sysMetricsStoreUpdate":true,"sysMtaConnectionStrategyGet":true,"sysMtaConnectionStrategyCreate":true,"sysMtaConnectionStrategyUpdate":true,"sysMtaConnectionStrategyDestroy":true,"sysMtaConnectionStrategyQuery":true,"sysMtaDeliveryScheduleGet":true,"sysMtaDeliveryScheduleCreate":true,"sysMtaDeliveryScheduleUpdate":true,"sysMtaDeliveryScheduleDestroy":true,"sysMtaDeliveryScheduleQuery":true,"sysMtaExtensionsGet":true,"sysMtaExtensionsUpdate":true,"sysMtaHookGet":true,"sysMtaHookCreate":true,"sysMtaHookUpdate":true,"sysMtaHookDestroy":true,"sysMtaHookQuery":true,"sysMtaInboundSessionGet":true,"sysMtaInboundSessionUpdate":true,"sysMtaInboundThrottleGet":true,"sysMtaInboundThrottleCreate":true,"sysMtaInboundThrottleUpdate":true,"sysMtaInboundThrottleDestroy":true,"sysMtaInboundThrottleQuery":true,"sysMtaMilterGet":true,"sysMtaMilterCreate":true,"sysMtaMilterUpdate":true,"sysMtaMilterDestroy":true,"sysMtaMilterQuery":true,"sysMtaOutboundStrategyGet":true,"sysMtaOutboundStrategyUpdate":true,"sysMtaOutboundThrottleGet":true,"sysMtaOutboundThrottleCreate":true,"sysMtaOutboundThrottleUpdate":true,"sysMtaOutboundThrottleDestroy":true,"sysMtaOutboundThrottleQuery":true,"sysMtaQueueQuotaGet":true,"sysMtaQueueQuotaCreate":true,"sysMtaQueueQuotaUpdate":true,"sysMtaQueueQuotaDestroy":true,"sysMtaQueueQuotaQuery":true,"sysMtaRouteGet":true,"sysMtaRouteCreate":true,"sysMtaRouteUpdate":true,"sysMtaRouteDestroy":true,"sysMtaRouteQuery":true,"sysMtaStageAuthGet":true,"sysMtaStageAuthUpdate":true,"sysMtaStageConnectGet":true,"sysMtaStageConnectUpdate":true,"sysMtaStageDataGet":true,"sysMtaStageDataUpdate":true,"sysMtaStageEhloGet":true,"sysMtaStageEhloUpdate":true,"sysMtaStageMailGet":true,"sysMtaStageMailUpdate":true,"sysMtaStageRcptGet":true,"sysMtaStageRcptUpdate":true,"sysMtaStsGet":true,"sysMtaStsUpdate":true,"sysMtaTlsStrategyGet":true,"sysMtaTlsStrategyCreate":true,"sysMtaTlsStrategyUpdate":true,"sysMtaTlsStrategyDestroy":true,"sysMtaTlsStrategyQuery":true,"sysMtaVirtualQueueGet":true,"sysMtaVirtualQueueCreate":true,"sysMtaVirtualQueueUpdate":true,"sysMtaVirtualQueueDestroy":true,"sysMtaVirtualQueueQuery":true,"sysNetworkListenerGet":true,"sysNetworkListenerCreate":true,"sysNetworkListenerUpdate":true,"sysNetworkListenerDestroy":true,"sysNetworkListenerQuery":true,"sysOAuthClientGet":true,"sysOAuthClientCreate":true,"sysOAuthClientUpdate":true,"sysOAuthClientDestroy":true,"sysOAuthClientQuery":true,"sysOidcProviderGet":true,"sysOidcProviderUpdate":true,"sysPublicKeyGet":true,"sysPublicKeyCreate":true,"sysPublicKeyUpdate":true,"sysPublicKeyDestroy":true,"sysPublicKeyQuery":true,"sysQueuedMessageGet":true,"sysQueuedMessageCreate":true,"sysQueuedMessageUpdate":true,"sysQueuedMessageDestroy":true,"sysQueuedMessageQuery":true,"sysReportSettingsGet":true,"sysReportSettingsUpdate":true,"sysRoleGet":true,"sysRoleCreate":true,"sysRoleUpdate":true,"sysRoleDestroy":true,"sysRoleQuery":true,"sysSearchGet":true,"sysSearchUpdate":true,"sysSearchStoreGet":true,"sysSearchStoreUpdate":true,"sysSecurityGet":true,"sysSecurityUpdate":true,"sysSenderAuthGet":true,"sysSenderAuthUpdate":true,"sysSharingGet":true,"sysSharingUpdate":true,"sysSieveSystemInterpreterGet":true,"sysSieveSystemInterpreterUpdate":true,"sysSieveSystemScriptGet":true,"sysSieveSystemScriptCreate":true,"sysSieveSystemScriptUpdate":true,"sysSieveSystemScriptDestroy":true,"sysSieveSystemScriptQuery":true,"sysSieveUserInterpreterGet":true,"sysSieveUserInterpreterUpdate":true,"sysSieveUserScriptGet":true,"sysSieveUserScriptCreate":true,"sysSieveUserScriptUpdate":true,"sysSieveUserScriptDestroy":true,"sysSieveUserScriptQuery":true,"sysSpamClassifierGet":true,"sysSpamClassifierUpdate":true,"sysSpamDnsblServerGet":true,"sysSpamDnsblServerCreate":true,"sysSpamDnsblServerUpdate":true,"sysSpamDnsblServerDestroy":true,"sysSpamDnsblServerQuery":true,"sysSpamDnsblSettingsGet":true,"sysSpamDnsblSettingsUpdate":true,"sysSpamFileExtensionGet":true,"sysSpamFileExtensionCreate":true,"sysSpamFileExtensionUpdate":true,"sysSpamFileExtensionDestroy":true,"sysSpamFileExtensionQuery":true,"sysSpamLlmGet":true,"sysSpamLlmUpdate":true,"sysSpamPyzorGet":true,"sysSpamPyzorUpdate":true,"sysSpamRuleGet":true,"sysSpamRuleCreate":true,"sysSpamRuleUpdate":true,"sysSpamRuleDestroy":true,"sysSpamRuleQuery":true,"sysSpamSettingsGet":true,"sysSpamSettingsUpdate":true,"sysSpamTagGet":true,"sysSpamTagCreate":true,"sysSpamTagUpdate":true,"sysSpamTagDestroy":true,"sysSpamTagQuery":true,"sysSpamTrainingSampleGet":true,"sysSpamTrainingSampleCreate":true,"sysSpamTrainingSampleUpdate":true,"sysSpamTrainingSampleDestroy":true,"sysSpamTrainingSampleQuery":true,"sysSpfReportSettingsGet":true,"sysSpfReportSettingsUpdate":true,"sysStoreLookupGet":true,"sysStoreLookupCreate":true,"sysStoreLookupUpdate":true,"sysStoreLookupDestroy":true,"sysStoreLookupQuery":true,"sysSystemSettingsGet":true,"sysSystemSettingsUpdate":true,"taskIndexDocument":true,"taskUnindexDocument":true,"taskIndexTrace":true,"taskCalendarAlarmEmail":true,"taskCalendarAlarmNotification":true,"taskCalendarItipMessage":true,"taskMergeThreads":true,"taskDmarcReport":true,"taskTlsReport":true,"taskRestoreArchivedItem":true,"taskDestroyAccount":true,"taskAccountMaintenance":true,"taskTenantMaintenance":true,"taskStoreMaintenance":true,"taskSpamFilterMaintenance":true,"taskAcmeRenewal":true,"taskDkimManagement":true,"taskDnsManagement":true,"sysTaskGet":true,"sysTaskCreate":true,"sysTaskUpdate":true,"sysTaskDestroy":true,"sysTaskQuery":true,"sysTaskManagerGet":true,"sysTaskManagerUpdate":true,"sysTenantGet":true,"sysTenantCreate":true,"sysTenantUpdate":true,"sysTenantDestroy":true,"sysTenantQuery":true,"sysTlsExternalReportGet":true,"sysTlsExternalReportCreate":true,"sysTlsExternalReportUpdate":true,"sysTlsExternalReportDestroy":true,"sysTlsExternalReportQuery":true,"sysTlsInternalReportGet":true,"sysTlsInternalReportCreate":true,"sysTlsInternalReportUpdate":true,"sysTlsInternalReportDestroy":true,"sysTlsInternalReportQuery":true,"sysTlsReportSettingsGet":true,"sysTlsReportSettingsUpdate":true,"sysTraceGet":true,"sysTraceCreate":true,"sysTraceUpdate":true,"sysTraceDestroy":true,"sysTraceQuery":true,"sysTracerGet":true,"sysTracerCreate":true,"sysTracerUpdate":true,"sysTracerDestroy":true,"sysTracerQuery":true,"sysTracingStoreGet":true,"sysTracingStoreUpdate":true,"sysWebDavGet":true,"sysWebDavUpdate":true,"sysWebHookGet":true,"sysWebHookCreate":true,"sysWebHookUpdate":true,"sysWebHookDestroy":true,"sysWebHookQuery":true},"description":"System Administrator","roleIds":{}},"role-d":{"disabledPermissions":{},"memberTenantId":null,"enabledPermissions":{"authenticate":true,"authenticateWithAlias":true,"interactAi":true,"fetchAnyBlob":true,"liveDeliveryTest":true,"sysAccountGet":true,"sysAccountCreate":true,"sysAccountUpdate":true,"sysAccountDestroy":true,"sysAccountQuery":true,"sysAcmeProviderGet":true,"sysAcmeProviderCreate":true,"sysAcmeProviderUpdate":true,"sysAcmeProviderDestroy":true,"sysAcmeProviderQuery":true,"sysDkimSignatureGet":true,"sysDkimSignatureCreate":true,"sysDkimSignatureUpdate":true,"sysDkimSignatureDestroy":true,"sysDkimSignatureQuery":true,"sysDnsServerGet":true,"sysDnsServerCreate":true,"sysDnsServerUpdate":true,"sysDnsServerDestroy":true,"sysDnsServerQuery":true,"sysDomainGet":true,"sysDomainCreate":true,"sysDomainUpdate":true,"sysDomainDestroy":true,"sysDomainQuery":true,"sysMailingListGet":true,"sysMailingListCreate":true,"sysMailingListUpdate":true,"sysMailingListDestroy":true,"sysMailingListQuery":true,"sysOAuthClientGet":true,"sysOAuthClientCreate":true,"sysOAuthClientUpdate":true,"sysOAuthClientDestroy":true,"sysOAuthClientQuery":true,"sysQueuedMessageGet":true,"sysQueuedMessageCreate":true,"sysQueuedMessageUpdate":true,"sysQueuedMessageDestroy":true,"sysQueuedMessageQuery":true,"sysRoleGet":true,"sysRoleCreate":true,"sysRoleUpdate":true,"sysRoleDestroy":true,"sysRoleQuery":true},"description":"Tenant Administrator","roleIds":{}},"role-c":{"disabledPermissions":{},"memberTenantId":null,"enabledPermissions":{"emailSend":true,"emailReceive":true,"calendarAlarmsSend":true,"calendarSchedulingSend":true,"calendarSchedulingReceive":true,"jmapPushSubscriptionGet":true,"jmapPushSubscriptionCreate":true,"jmapPushSubscriptionUpdate":true,"jmapPushSubscriptionDestroy":true,"jmapMailboxGet":true,"jmapMailboxChanges":true,"jmapMailboxQuery":true,"jmapMailboxQueryChanges":true,"jmapMailboxCreate":true,"jmapMailboxUpdate":true,"jmapMailboxDestroy":true,"jmapThreadGet":true,"jmapThreadChanges":true,"jmapEmailGet":true,"jmapEmailChanges":true,"jmapEmailQuery":true,"jmapEmailQueryChanges":true,"jmapEmailCreate":true,"jmapEmailUpdate":true,"jmapEmailDestroy":true,"jmapEmailCopy":true,"jmapEmailImport":true,"jmapEmailParse":true,"jmapSearchSnippetGet":true,"jmapIdentityGet":true,"jmapIdentityChanges":true,"jmapIdentityCreate":true,"jmapIdentityUpdate":true,"jmapIdentityDestroy":true,"jmapEmailSubmissionGet":true,"jmapEmailSubmissionChanges":true,"jmapEmailSubmissionQuery":true,"jmapEmailSubmissionQueryChanges":true,"jmapEmailSubmissionCreate":true,"jmapEmailSubmissionUpdate":true,"jmapEmailSubmissionDestroy":true,"jmapVacationResponseGet":true,"jmapVacationResponseCreate":true,"jmapVacationResponseUpdate":true,"jmapVacationResponseDestroy":true,"jmapSieveScriptGet":true,"jmapSieveScriptQuery":true,"jmapSieveScriptValidate":true,"jmapSieveScriptCreate":true,"jmapSieveScriptUpdate":true,"jmapSieveScriptDestroy":true,"jmapPrincipalGet":true,"jmapPrincipalQuery":true,"jmapPrincipalChanges":true,"jmapPrincipalQueryChanges":true,"jmapPrincipalGetAvailability":true,"jmapPrincipalCreate":true,"jmapPrincipalUpdate":true,"jmapPrincipalDestroy":true,"jmapQuotaGet":true,"jmapQuotaChanges":true,"jmapQuotaQuery":true,"jmapQuotaQueryChanges":true,"jmapBlobGet":true,"jmapBlobCopy":true,"jmapBlobLookup":true,"jmapBlobUpload":true,"jmapAddressBookGet":true,"jmapAddressBookChanges":true,"jmapAddressBookCreate":true,"jmapAddressBookUpdate":true,"jmapAddressBookDestroy":true,"jmapContactCardGet":true,"jmapContactCardChanges":true,"jmapContactCardQuery":true,"jmapContactCardQueryChanges":true,"jmapContactCardCreate":true,"jmapContactCardUpdate":true,"jmapContactCardDestroy":true,"jmapContactCardCopy":true,"jmapContactCardParse":true,"jmapFileNodeGet":true,"jmapFileNodeChanges":true,"jmapFileNodeQuery":true,"jmapFileNodeQueryChanges":true,"jmapFileNodeCreate":true,"jmapFileNodeUpdate":true,"jmapFileNodeDestroy":true,"jmapShareNotificationGet":true,"jmapShareNotificationChanges":true,"jmapShareNotificationQuery":true,"jmapShareNotificationQueryChanges":true,"jmapShareNotificationCreate":true,"jmapShareNotificationUpdate":true,"jmapShareNotificationDestroy":true,"jmapCalendarGet":true,"jmapCalendarChanges":true,"jmapCalendarCreate":true,"jmapCalendarUpdate":true,"jmapCalendarDestroy":true,"jmapCalendarEventGet":true,"jmapCalendarEventChanges":true,"jmapCalendarEventQuery":true,"jmapCalendarEventQueryChanges":true,"jmapCalendarEventCreate":true,"jmapCalendarEventUpdate":true,"jmapCalendarEventDestroy":true,"jmapCalendarEventCopy":true,"jmapCalendarEventParse":true,"jmapCalendarEventNotificationGet":true,"jmapCalendarEventNotificationChanges":true,"jmapCalendarEventNotificationQuery":true,"jmapCalendarEventNotificationQueryChanges":true,"jmapCalendarEventNotificationCreate":true,"jmapCalendarEventNotificationUpdate":true,"jmapCalendarEventNotificationDestroy":true,"jmapParticipantIdentityGet":true,"jmapParticipantIdentityChanges":true,"jmapParticipantIdentityCreate":true,"jmapParticipantIdentityUpdate":true,"jmapParticipantIdentityDestroy":true,"jmapCoreEcho":true,"imapAuthenticate":true,"imapAclGet":true,"imapAclSet":true,"imapMyRights":true,"imapListRights":true,"imapAppend":true,"imapCapability":true,"imapId":true,"imapCopy":true,"imapMove":true,"imapCreate":true,"imapDelete":true,"imapEnable":true,"imapExpunge":true,"imapFetch":true,"imapIdle":true,"imapList":true,"imapLsub":true,"imapNamespace":true,"imapRename":true,"imapSearch":true,"imapSort":true,"imapSelect":true,"imapExamine":true,"imapStatus":true,"imapStore":true,"imapSubscribe":true,"imapThread":true,"pop3Authenticate":true,"pop3List":true,"pop3Uidl":true,"pop3Stat":true,"pop3Retr":true,"pop3Dele":true,"sieveAuthenticate":true,"sieveListScripts":true,"sieveSetActive":true,"sieveGetScript":true,"sievePutScript":true,"sieveDeleteScript":true,"sieveRenameScript":true,"sieveCheckScript":true,"sieveHaveSpace":true,"davSyncCollection":true,"davExpandProperty":true,"davPrincipalAcl":true,"davPrincipalList":true,"davPrincipalMatch":true,"davPrincipalSearch":true,"davPrincipalSearchPropSet":true,"davFilePropFind":true,"davFilePropPatch":true,"davFileGet":true,"davFileMkCol":true,"davFileDelete":true,"davFilePut":true,"davFileCopy":true,"davFileMove":true,"davFileLock":true,"davFileAcl":true,"davCardPropFind":true,"davCardPropPatch":true,"davCardGet":true,"davCardMkCol":true,"davCardDelete":true,"davCardPut":true,"davCardCopy":true,"davCardMove":true,"davCardLock":true,"davCardAcl":true,"davCardQuery":true,"davCardMultiGet":true,"davCalPropFind":true,"davCalPropPatch":true,"davCalGet":true,"davCalMkCol":true,"davCalDelete":true,"davCalPut":true,"davCalCopy":true,"davCalMove":true,"davCalLock":true,"davCalAcl":true,"davCalQuery":true,"davCalMultiGet":true,"davCalFreeBusyQuery":true,"sysAccountSettingsGet":true,"sysAccountSettingsUpdate":true,"sysArchivedItemGet":true,"sysArchivedItemCreate":true,"sysArchivedItemUpdate":true,"sysArchivedItemDestroy":true,"sysArchivedItemQuery":true,"sysMaskedEmailGet":true,"sysMaskedEmailCreate":true,"sysMaskedEmailUpdate":true,"sysMaskedEmailDestroy":true,"sysMaskedEmailQuery":true,"sysPublicKeyGet":true,"sysPublicKeyCreate":true,"sysPublicKeyUpdate":true,"sysPublicKeyDestroy":true,"sysPublicKeyQuery":true,"sysSpamTrainingSampleGet":true,"sysSpamTrainingSampleUpdate":true,"sysSpamTrainingSampleDestroy":true,"sysSpamTrainingSampleQuery":true,"jmapFileNodeCopy":true},"description":"Group","roleIds":{}},"role-b":{"disabledPermissions":{},"memberTenantId":null,"enabledPermissions":{"authenticate":true,"authenticateWithAlias":true,"interactAi":true,"emailSend":true,"emailReceive":true,"calendarAlarmsSend":true,"calendarSchedulingSend":true,"calendarSchedulingReceive":true,"jmapPushSubscriptionGet":true,"jmapPushSubscriptionCreate":true,"jmapPushSubscriptionUpdate":true,"jmapPushSubscriptionDestroy":true,"jmapMailboxGet":true,"jmapMailboxChanges":true,"jmapMailboxQuery":true,"jmapMailboxQueryChanges":true,"jmapMailboxCreate":true,"jmapMailboxUpdate":true,"jmapMailboxDestroy":true,"jmapThreadGet":true,"jmapThreadChanges":true,"jmapEmailGet":true,"jmapEmailChanges":true,"jmapEmailQuery":true,"jmapEmailQueryChanges":true,"jmapEmailCreate":true,"jmapEmailUpdate":true,"jmapEmailDestroy":true,"jmapEmailCopy":true,"jmapEmailImport":true,"jmapEmailParse":true,"jmapSearchSnippetGet":true,"jmapIdentityGet":true,"jmapIdentityChanges":true,"jmapIdentityCreate":true,"jmapIdentityUpdate":true,"jmapIdentityDestroy":true,"jmapEmailSubmissionGet":true,"jmapEmailSubmissionChanges":true,"jmapEmailSubmissionQuery":true,"jmapEmailSubmissionQueryChanges":true,"jmapEmailSubmissionCreate":true,"jmapEmailSubmissionUpdate":true,"jmapEmailSubmissionDestroy":true,"jmapVacationResponseGet":true,"jmapVacationResponseCreate":true,"jmapVacationResponseUpdate":true,"jmapVacationResponseDestroy":true,"jmapSieveScriptGet":true,"jmapSieveScriptQuery":true,"jmapSieveScriptValidate":true,"jmapSieveScriptCreate":true,"jmapSieveScriptUpdate":true,"jmapSieveScriptDestroy":true,"jmapPrincipalGet":true,"jmapPrincipalQuery":true,"jmapPrincipalChanges":true,"jmapPrincipalQueryChanges":true,"jmapPrincipalGetAvailability":true,"jmapPrincipalCreate":true,"jmapPrincipalUpdate":true,"jmapPrincipalDestroy":true,"jmapQuotaGet":true,"jmapQuotaChanges":true,"jmapQuotaQuery":true,"jmapQuotaQueryChanges":true,"jmapBlobGet":true,"jmapBlobCopy":true,"jmapBlobLookup":true,"jmapBlobUpload":true,"jmapAddressBookGet":true,"jmapAddressBookChanges":true,"jmapAddressBookCreate":true,"jmapAddressBookUpdate":true,"jmapAddressBookDestroy":true,"jmapContactCardGet":true,"jmapContactCardChanges":true,"jmapContactCardQuery":true,"jmapContactCardQueryChanges":true,"jmapContactCardCreate":true,"jmapContactCardUpdate":true,"jmapContactCardDestroy":true,"jmapContactCardCopy":true,"jmapContactCardParse":true,"jmapFileNodeGet":true,"jmapFileNodeChanges":true,"jmapFileNodeQuery":true,"jmapFileNodeQueryChanges":true,"jmapFileNodeCreate":true,"jmapFileNodeUpdate":true,"jmapFileNodeDestroy":true,"jmapShareNotificationGet":true,"jmapShareNotificationChanges":true,"jmapShareNotificationQuery":true,"jmapShareNotificationQueryChanges":true,"jmapShareNotificationCreate":true,"jmapShareNotificationUpdate":true,"jmapShareNotificationDestroy":true,"jmapCalendarGet":true,"jmapCalendarChanges":true,"jmapCalendarCreate":true,"jmapCalendarUpdate":true,"jmapCalendarDestroy":true,"jmapCalendarEventGet":true,"jmapCalendarEventChanges":true,"jmapCalendarEventQuery":true,"jmapCalendarEventQueryChanges":true,"jmapCalendarEventCreate":true,"jmapCalendarEventUpdate":true,"jmapCalendarEventDestroy":true,"jmapCalendarEventCopy":true,"jmapCalendarEventParse":true,"jmapCalendarEventNotificationGet":true,"jmapCalendarEventNotificationChanges":true,"jmapCalendarEventNotificationQuery":true,"jmapCalendarEventNotificationQueryChanges":true,"jmapCalendarEventNotificationCreate":true,"jmapCalendarEventNotificationUpdate":true,"jmapCalendarEventNotificationDestroy":true,"jmapParticipantIdentityGet":true,"jmapParticipantIdentityChanges":true,"jmapParticipantIdentityCreate":true,"jmapParticipantIdentityUpdate":true,"jmapParticipantIdentityDestroy":true,"jmapCoreEcho":true,"imapAuthenticate":true,"imapAclGet":true,"imapAclSet":true,"imapMyRights":true,"imapListRights":true,"imapAppend":true,"imapCapability":true,"imapId":true,"imapCopy":true,"imapMove":true,"imapCreate":true,"imapDelete":true,"imapEnable":true,"imapExpunge":true,"imapFetch":true,"imapIdle":true,"imapList":true,"imapLsub":true,"imapNamespace":true,"imapRename":true,"imapSearch":true,"imapSort":true,"imapSelect":true,"imapExamine":true,"imapStatus":true,"imapStore":true,"imapSubscribe":true,"imapThread":true,"pop3Authenticate":true,"pop3List":true,"pop3Uidl":true,"pop3Stat":true,"pop3Retr":true,"pop3Dele":true,"sieveAuthenticate":true,"sieveListScripts":true,"sieveSetActive":true,"sieveGetScript":true,"sievePutScript":true,"sieveDeleteScript":true,"sieveRenameScript":true,"sieveCheckScript":true,"sieveHaveSpace":true,"davSyncCollection":true,"davExpandProperty":true,"davPrincipalAcl":true,"davPrincipalList":true,"davPrincipalMatch":true,"davPrincipalSearch":true,"davPrincipalSearchPropSet":true,"davFilePropFind":true,"davFilePropPatch":true,"davFileGet":true,"davFileMkCol":true,"davFileDelete":true,"davFilePut":true,"davFileCopy":true,"davFileMove":true,"davFileLock":true,"davFileAcl":true,"davCardPropFind":true,"davCardPropPatch":true,"davCardGet":true,"davCardMkCol":true,"davCardDelete":true,"davCardPut":true,"davCardCopy":true,"davCardMove":true,"davCardLock":true,"davCardAcl":true,"davCardQuery":true,"davCardMultiGet":true,"davCalPropFind":true,"davCalPropPatch":true,"davCalGet":true,"davCalMkCol":true,"davCalDelete":true,"davCalPut":true,"davCalCopy":true,"davCalMove":true,"davCalLock":true,"davCalAcl":true,"davCalQuery":true,"davCalMultiGet":true,"davCalFreeBusyQuery":true,"sysAccountPasswordGet":true,"sysAccountPasswordUpdate":true,"sysAccountSettingsGet":true,"sysAccountSettingsUpdate":true,"sysApiKeyGet":true,"sysApiKeyCreate":true,"sysApiKeyUpdate":true,"sysApiKeyDestroy":true,"sysApiKeyQuery":true,"sysAppPasswordGet":true,"sysAppPasswordCreate":true,"sysAppPasswordUpdate":true,"sysAppPasswordDestroy":true,"sysAppPasswordQuery":true,"sysArchivedItemGet":true,"sysArchivedItemCreate":true,"sysArchivedItemUpdate":true,"sysArchivedItemDestroy":true,"sysArchivedItemQuery":true,"sysMaskedEmailGet":true,"sysMaskedEmailCreate":true,"sysMaskedEmailUpdate":true,"sysMaskedEmailDestroy":true,"sysMaskedEmailQuery":true,"sysPublicKeyGet":true,"sysPublicKeyCreate":true,"sysPublicKeyUpdate":true,"sysPublicKeyDestroy":true,"sysPublicKeyQuery":true,"sysSpamTrainingSampleGet":true,"sysSpamTrainingSampleUpdate":true,"sysSpamTrainingSampleDestroy":true,"sysSpamTrainingSampleQuery":true,"jmapFileNodeCopy":true},"description":"User","roleIds":{}}}}`,
+ `{
+ "@type": "create",
+ "object": "Domain",
+ "value": {
+ "domain-b": {
+ "aliases": {},
+ "catchAllAddress": null,
+ "certificateManagement": {
+ "@type": "Manual"
+ },
+ "dkimManagement": {
+ "@type": "Manual"
+ },
+ "subAddressing": {
+ "@type": "Enabled"
+ },
+ "logo": null,
+ "isEnabled": true,
+ "memberTenantId": null,
+ "description": null,
+ "name": "<<.domain>>",
+ "directoryId": null,
+ "allowRelaying": false,
+ "dnsManagement": {
+ "@type": "Manual"
+ },
+ "reportAddressUri": "mailto:postmaster"
+ }
+ }
+ }`,
+ `{
+ "@type": "create",
+ "object": "Account",
+ "value": {
+ "account-d": {
+ "@type": "User",
+ "name": "<<.masterusername>>",
+ "memberGroupIds": {},
+ "locale": "en_US",
+ "quotas": {},
+ "aliases": {},
+ "domainId": "#domain-b",
+ "memberTenantId": null,
+ "permissions": {
+ "@type": "Merge",
+ "enabledPermissions": {
+ "impersonate": true
+ },
+ "disabledPermissions": {}
+ },
+ "encryptionAtRest": {
+ "@type": "Disabled"
+ },
+ "credentials": {
+ "0": {
+ "@type": "Password",
+ "expiresAt": null,
+ "secret": "<<.masterpassword>>",
+ "allowedIps": {}
+ }
+ },
+ "description": "Master",
+ "timeZone": null,
+ "roles": {
+ "@type": "User"
+ }
+ },
+ "account-b": {
+ "@type": "User",
+ "name": "<<.adminusername>>",
+ "memberGroupIds": {},
+ "locale": "en_US",
+ "quotas": {},
+ "aliases": {},
+ "domainId": "#domain-b",
+ "memberTenantId": null,
+ "permissions": {
+ "@type": "Inherit"
+ },
+ "encryptionAtRest": {
+ "@type": "Disabled"
+ },
+ "credentials": {
+ "0": {
+ "@type": "Password",
+ "expiresAt": null,
+ "secret": "<<.adminpassword>>",
+ "allowedIps": {}
+ }
+ },
+ "description": "System administrator",
+ "timeZone": null,
+ "roles": {
+ "@type": "Admin"
+ }
+ }
+ }
+ }`,
+ `{"@type":"update","object":"Authentication","value":{"defaultGroupRoleIds":{"#role-c":true},"defaultAdminRoleIds":{"#role-e":true,"#role-b":true},"defaultUserRoleIds":{"#role-b":true},"maxAppPasswords":5,"defaultTenantRoleIds":{"#role-d":true,"#role-b":true},"directoryId":null,"passwordMinLength":1,"passwordDefaultExpiry":null,"passwordHashAlgorithm":"argon2id","passwordMinStrength":"zero","passwordMaxLength":128,"maxApiKeys":5}}`,
+ `{"@type":"update","object":"Sharing","value":{"allowDirectoryQueries":<<.dirquery>>,"maxShares":10}}`,
+ `{
+ "@type": "update",
+ "object": "SystemSettings",
+ "value": {
+ "defaultHostname": "<<.hostname>>",
+ "threadPoolSize": null,
+ "defaultCertificateId": null,
+ "proxyTrustedNetworks": {},
+ "defaultDomainId": "#domain-b",
+ "services": {
+ "caldav": {
+ "hostname": null,
+ "cleartext": false
+ },
+ "carddav": {
+ "hostname": null,
+ "cleartext": false
+ },
+ "imap": {
+ "hostname": null,
+ "cleartext": false
+ },
+ "jmap": {
+ "hostname": null,
+ "cleartext": false
+ },
+ "managesieve": {
+ "hostname": null,
+ "cleartext": false
+ },
+ "pop3": {
+ "hostname": null,
+ "cleartext": false
+ },
+ "smtp": {
+ "hostname": null,
+ "cleartext": false
+ },
+ "webdav": {
+ "hostname": null,
+ "cleartext": false
+ }
+ },
+ "mailExchangers": {
+ "0": {
+ "priority": 10,
+ "hostname": null
+ }
+ },
+ "maxConnections": 8192,
+ "providerInfo": {}
+ }
+ }`,
+ `{"@type":"update","object":"DataRetention","value":{"archiveDeletedAccountsFor":null,"blobCleanupSchedule":{"@type":"Daily","hour":4,"minute":0},"expungeSchedule":{"@type":"Daily","hour":0,"minute":0},"dataCleanupSchedule":{"@type":"Daily","hour":2,"minute":0},"expungeTrashAfter":2592000000,"holdMtaReportsFor":2592000000,"metricsCollectionInterval":{"@type":"Hourly","minute":0},"holdMetricsFor":7776000000,"holdTracesFor":2592000000,"expungeShareNotifyAfter":2592000000,"expungeSchedulingInboxAfter":2592000000,"maxChangesHistory":10000,"expungeSubmissionsAfter":259200000,"archiveDeletedItemsFor":null}}`,
+ `{"@type":"update","object":"BlobStore","value":{"@type":"Default"}}`,
+ `{"@type":"update","object":"InMemoryStore","value":{"@type":"Default"}}`,
+ `{"@type":"update","object":"SearchStore","value":{"@type":"Default"}}`,
+ }
+)
+
func skip(t *testing.T) bool {
if os.Getenv("CI") == "woodpecker" {
t.Skip("Skipping tests because CI==woodpecker")
@@ -157,7 +301,7 @@ type StalwartTest struct {
ctx context.Context
cancelCtx context.CancelFunc
client *Client
- logger *clog.Logger
+ logger *oclog.Logger
jmapBaseUrl *url.URL
sessionUrl *url.URL
@@ -196,24 +340,58 @@ func (s *StalwartTest) Session(username string) *Session {
// "localhost" as we defined the hostname in the Stalwart configuration,
// and we also need to overwrite the port number as its not mapped
session.JmapUrl.Host = s.jmapBaseUrl.Host
+ session.JmapUrl.Scheme = "http" // replace https with http
session.WebsocketUrl.Host = s.jmapBaseUrl.Host
- var err error
- session.ApiUrl, err = replaceHost(session.ApiUrl, s.jmapBaseUrl.Host)
- require.NoError(s.t, err)
- session.DownloadUrl, err = replaceHost(session.DownloadUrl, s.jmapBaseUrl.Host)
- require.NoError(s.t, err)
- session.UploadUrl, err = replaceHost(session.UploadUrl, s.jmapBaseUrl.Host)
- require.NoError(s.t, err)
- session.EventSourceUrl, err = replaceHost(session.EventSourceUrl, s.jmapBaseUrl.Host)
- require.NoError(s.t, err)
+ session.WebsocketUrl.Scheme = "ws" // replace wss with ws
+
+ if v, err := replaceHostProto(session.ApiUrl, "http", s.jmapBaseUrl.Host); err != nil {
+ require.NoError(s.t, err)
+ } else {
+ session.ApiUrl = v
+ }
+ if v, err := replaceHostProto(session.DownloadUrl, "http", s.jmapBaseUrl.Host); err != nil {
+ require.NoError(s.t, err)
+ } else {
+ session.DownloadUrl = v
+ }
+ if v, err := replaceHostProto(session.UploadUrl, "http", s.jmapBaseUrl.Host); err != nil {
+ require.NoError(s.t, err)
+ } else {
+ session.UploadUrl = v
+ }
+ if v, err := replaceHostProto(session.EventSourceUrl, "http", s.jmapBaseUrl.Host); err != nil {
+ require.NoError(s.t, err)
+ } else {
+ session.EventSourceUrl = v
+ }
return &session
}
-type stalwartTestLogConsumer struct{}
+type cliLogConsumer struct {
+ lines *[]string
+}
-func (lc *stalwartTestLogConsumer) Accept(l testcontainers.Log) {
- fmt.Print("STALWART: " + string(l.Content))
+func (c *cliLogConsumer) Accept(l testcontainers.Log) {
+ s := string(l.Content)
+ log.Printf("CLI: %s", s)
+ *c.lines = append(*c.lines, s)
+}
+
+type printingLogConsumer struct {
+ prefix string
+}
+
+// 2026/06/05 16:18:36 STALWART: 2026-06-05T14:18:36Z INFO Shutting down Stalwart Server (server.shutdown) causedBy = "SIGTERM"
+var printingLogConsumerRegex = regexp.MustCompile(`^(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ)\s+(\S+)\s+(.+)$`)
+
+func (c *printingLogConsumer) Accept(l testcontainers.Log) {
+ str := string(l.Content)
+ if m := printingLogConsumerRegex.FindAllStringSubmatch(str, 3); m != nil {
+ log.Printf("\x1b[32;1m%s\x1b[0m \x1b[34m%s\x1b[0m \x1b[31;1m%s\x1b[0m %s", c.prefix, m[0][1], m[0][2], m[0][3])
+ } else {
+ log.Printf("%s: %s", c.prefix, str)
+ }
}
func withDirectoryQueries(allowDirectoryQueries bool) func(map[string]any) {
@@ -222,153 +400,494 @@ func withDirectoryQueries(allowDirectoryQueries bool) func(map[string]any) {
}
}
+func applySnapshot(ctx context.Context, net *testcontainers.DockerNetwork, uri, user, password string, content *strings.Reader) ([]string, error) {
+ var buf bytes.Buffer
+ tw := tar.NewWriter(&buf)
+ hdr := &tar.Header{
+ Name: "Dockerfile",
+ Mode: 0600,
+ Size: int64(len(cliDockerfile)),
+ }
+ if err := tw.WriteHeader(hdr); err != nil {
+ return nil, err
+ }
+ if _, err := tw.Write([]byte(cliDockerfile)); err != nil {
+ return nil, err
+ }
+ if err := tw.Close(); err != nil {
+ return nil, err
+ }
+
+ output := []string{}
+
+ cliArch := "x86_64"
+ switch runtime.GOARCH {
+ case "amd64":
+ cliArch = "x86_64"
+ case "arm64", "arm64be":
+ cliArch = "aarch64"
+ case "arm", "armbe":
+ cliArch = "armv7"
+ default:
+ return nil, fmt.Errorf("unsupported architecture: '%s'", runtime.GOARCH)
+ }
+
+ opts := []testcontainers.ContainerCustomizer{
+ testcontainers.WithDockerfile(testcontainers.FromDockerfile{
+ ContextArchive: bytes.NewReader(buf.Bytes()),
+ KeepImage: true, // speeds up subsequent test runs by using the Docker cache
+ BuildArgs: map[string]*string{
+ "ARCH": &cliArch,
+ },
+ }),
+ testcontainers.WithCmdArgs("/usr/local/bin/stalwart-cli", "apply", "--json", "--no-color", "--file", "/snapshot.json"),
+ testcontainers.WithEnv(map[string]string{
+ "STALWART_URL": uri,
+ "STALWART_USER": user,
+ "STALWART_PASSWORD": password,
+ }),
+ testcontainers.WithFiles(testcontainers.ContainerFile{
+ Reader: content,
+ ContainerFilePath: "/snapshot.json",
+ FileMode: 0o700,
+ }),
+ testcontainers.WithLogConsumers(&cliLogConsumer{lines: &output}),
+ testcontainers.WithWaitStrategy(wait.ForExit()),
+ }
+
+ if net != nil {
+ opts = append(opts, network.WithNetwork([]string{"cli"}, net))
+ }
+
+ container, err := testcontainers.Run(ctx, "", opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ rc := 0
+ s, stateErr := container.State(ctx)
+ if stateErr == nil {
+ rc = s.ExitCode
+ }
+
+ if err := container.Terminate(ctx); err != nil {
+ return nil, err
+ }
+
+ if stateErr != nil {
+ return nil, stateErr
+ }
+ if rc != 0 {
+ return nil, fmt.Errorf("cli returned exit code %d", rc)
+ }
+ return output, nil
+}
+
+func postJmap(ctx context.Context, h http.Client, url string, username string, password string, body map[string]any) (map[string]any, error) {
+ bb, err := json.Marshal(body)
+ if err != nil {
+ return nil, err
+ }
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(bb))
+ if err != nil {
+ return nil, err
+ }
+ req.SetBasicAuth(username, password)
+ resp, err := h.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.Status != "200 OK" {
+ return nil, fmt.Errorf("failed POST onto the JMAP API endpoint at %s: %s", url, resp.Status)
+ }
+
+ result := map[string]any{}
+ err = json.NewDecoder(resp.Body).Decode(&result)
+ if err != nil {
+ return nil, err
+ }
+
+ return result, nil
+}
+
+func createManagementObject(ctx context.Context, h http.Client, url string, adminUsername string, adminPassword string, objectType string, obj map[string]any) (string, error) {
+ result, err := postJmap(ctx, h, url, adminUsername, adminPassword, map[string]any{
+ "using": []string{"urn:ietf:params:jmap:core", "urn:stalwart:jmap"},
+ "methodCalls": []any{
+ []any{
+ fmt.Sprintf("x:%s/set", objectType),
+ map[string]any{
+ "create": map[string]any{
+ "n": obj,
+ },
+ },
+ "c",
+ },
+ },
+ })
+ if err != nil {
+ return "", err
+ }
+
+ text := ""
+ {
+ if b, err := json.MarshalIndent(result, "", " "); err == nil {
+ text = string(b)
+ }
+ }
+
+ if methodResponses, ok := result["methodResponses"]; !ok {
+ return "", fmt.Errorf("response has no methodResponses: %s", text)
+ } else {
+ methodResponses := methodResponses.([]any)
+ if len(methodResponses) != 1 {
+ return "", fmt.Errorf("response has a methodResponses that doesn't have a single item: %s", text)
+
+ }
+ firstMethodResponse := methodResponses[0].([]any)
+ if len(firstMethodResponse) != 3 {
+ return "", fmt.Errorf("response has a methodResponses with one item that is a JMAP response that does not have 3 elements: %s", text)
+ }
+ payload := firstMethodResponse[1]
+ if payload == nil {
+ return "", fmt.Errorf("response has a first methodResponse without any payload: %s", text)
+ }
+ if created, ok := (payload.(map[string]any))["created"]; !ok {
+ return "", fmt.Errorf("response has a first methodResponse with a payload that has no 'created': %s", text)
+ } else {
+ created := created.(map[string]any)
+ if specifically, ok := created["n"]; !ok {
+ return "", fmt.Errorf("response has a first methodResponse with a payload that has no 'n': %s", text)
+ } else {
+ specifically := specifically.(map[string]any)
+ if id, ok := specifically["id"]; !ok {
+ return "", fmt.Errorf("response has a first methodResponse with a payload with a created with an 'n' that has no 'id': %s", text)
+ } else {
+ return id.(string), nil
+ }
+ }
+ }
+ }
+}
+
+func createJmapClient(container *testcontainers.DockerContainer, ctx context.Context, auth HttpJmapClientAuthenticator, logger *oclog.Logger) (Client, *url.URL, *url.URL, error) {
+ ip, err := container.Host(ctx)
+ if err != nil {
+ return Client{}, nil, nil, err
+ }
+
+ tlsConfig := &tls.Config{InsecureSkipVerify: true}
+
+ tr := http.DefaultTransport.(*http.Transport).Clone()
+ tr.ResponseHeaderTimeout = time.Duration(30 * time.Second)
+ tr.TLSClientConfig = tlsConfig
+ jh := *http.DefaultClient
+ jh.Transport = tr
+
+ wsd := &websocket.Dialer{
+ TLSClientConfig: tlsConfig,
+ HandshakeTimeout: time.Duration(10) * time.Second,
+ }
+
+ jmapPort, err := container.MappedPort(ctx, httpPort)
+ if err != nil {
+ return Client{}, nil, nil, err
+ }
+ jmapBaseUrl := &url.URL{
+ Scheme: "http",
+ Host: ip + ":" + jmapPort.Port(),
+ Path: "/",
+ }
+ sessionUrl := jmapBaseUrl.JoinPath(".well-known", "jmap")
+
+ if Wireshark != "" {
+ fmt.Printf("\x1b[45;37;1m Starting Wireshark on port %v \x1b[0m\n", jmapPort)
+ attr := os.ProcAttr{
+ Dir: ".",
+ Env: os.Environ(),
+ Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
+ }
+ cmd := []string{Wireshark, "-pkSl", "-i", "lo", "-f", fmt.Sprintf("port %d", jmapPort.Num()), "-Y", "http||websocket"}
+ process, err := os.StartProcess(Wireshark, cmd, &attr)
+ if err != nil {
+ return Client{}, nil, nil, err
+ }
+ err = process.Release()
+ if err != nil {
+ return Client{}, nil, nil, err
+ }
+
+ time.Sleep(10 * time.Second)
+ }
+
+ eventListener := nullHttpJmapApiClientEventListener{}
+
+ api := NewHttpJmapClient(&jh, auth, eventListener)
+
+ wscf, err := NewHttpWsClientFactory(wsd, auth, logger, eventListener)
+ if err != nil {
+ return Client{}, nil, nil, err
+ }
+
+ return NewClient(api, api, api, wscf), jmapBaseUrl, sessionUrl, nil
+}
+
+type ContextPasswordAuthHttpJmapClientAuthenticator struct {
+ key string
+}
+
+var _ HttpJmapClientAuthenticator = &ContextPasswordAuthHttpJmapClientAuthenticator{}
+
+func (h *ContextPasswordAuthHttpJmapClientAuthenticator) Authenticate(ctx context.Context, username string, _ *oclog.Logger, req *http.Request) Error {
+ password := ctx.Value(h.key).(string)
+ req.SetBasicAuth(username, password)
+ return nil
+}
+
+func (h *ContextPasswordAuthHttpJmapClientAuthenticator) AuthenticateWS(ctx context.Context, username string, _ *oclog.Logger, headers http.Header) Error {
+ password := ctx.Value(h.key).(string)
+ headers.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(username+":"+password)))
+ return nil
+}
+
+const useNetwork = false
+
func newStalwartTest(t *testing.T, options ...func(map[string]any)) (*StalwartTest, error) { //NOSONAR
- ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
+ //ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
+ ctx := t.Context()
+ cancel := func() {}
var _ context.CancelFunc = cancel // ignore context leak warning: it is passed in the struct and called in Close()
// A master user name different from "master" does not seem to work as of the current Stalwart version
//masterUsernameSuffix, err := pw.Generate(4+rand.Intn(28), 2, 0, false, true)
//require.NoError(err)
masterUsername := "master" //"master_" + masterUsernameSuffix
-
- masterPassword, err := pw.Generate(4+rand.Intn(28), 2, 0, false, true)
+ masterPassword, err := pw.Generate(10+rand.Intn(28), 2, 0, false, true)
if err != nil {
return nil, err
}
- masterPasswordHash := ""
- {
- hasher, err := shacrypt.New(shacrypt.WithSHA512(), shacrypt.WithIterations(shacrypt.IterationsDefaultOmitted))
- if err != nil {
- return nil, err
- }
- digest, err := hasher.Hash(masterPassword)
- if err != nil {
- return nil, err
- }
- masterPasswordHash = digest.Encode()
+ adminUsername := "admin"
+ adminPassword, err := pw.Generate(10+rand.Intn(28), 2, 0, false, true)
+ if err != nil {
+ return nil, err
}
- hostname := "localhost"
+ recoveryAlias := "recovery"
+ containerAlias := "stalwart"
+ volumeName := "stalwart-test-data-volume"
+ {
+ str, err := pw.Generate(32, 12, 0, true, true)
+ if err != nil {
+ return nil, err
+ }
+ volumeName += "-" + str
+ }
+ hostname := "127.0.0.1"
settings := map[string]any{
- "hostname": hostname,
- "masterusername": masterUsername,
- "masterpassword": masterPasswordHash,
- "httpPort": httpPort,
- "imapsPort": imapsPort,
- "dirquery": "false",
+ "hostname": hostname,
+ "masterusername": masterUsername,
+ "masterpassword": masterPassword, //Hash,
+ "adminusername": adminUsername,
+ "adminpassword": adminPassword,
+ "httpPort": httpPort,
+ "httpsPort": 10443,
+ "popsPort": 10995,
+ "imapsPort": imapsPort,
+ "submissionsPort": 10465,
+ "smtpPort": 10025,
+ "dirquery": "false",
+ "domain": "example.org",
}
for _, option := range options {
option(settings)
}
- configBuf := bytes.NewBufferString("")
- template.Must(template.New("").Parse(configTemplate)).Execute(configBuf, settings)
- config := configBuf.String()
- configReader := strings.NewReader(config)
-
- container, err := testcontainers.Run(
- ctx,
- stalwartImage,
- testcontainers.WithLogConsumers(&stalwartTestLogConsumer{}),
- testcontainers.WithExposedPorts(httpPort+"/tcp", imapsPort+"/tcp"),
- testcontainers.WithFiles(testcontainers.ContainerFile{
- Reader: configReader,
- ContainerFilePath: "/opt/stalwart/etc/config.toml",
- FileMode: 0o700,
- }),
- testcontainers.WithWaitStrategyAndDeadline(
- 30*time.Second,
- wait.ForLog(`Network listener started (network.listen-start) listenerId = "imaptls"`),
- wait.ForLog(`Network listener started (network.listen-start) listenerId = "http"`),
- ),
- )
-
- success := false
- defer func() {
- if !success {
- testcontainers.CleanupContainer(t, container)
+ var net *testcontainers.DockerNetwork
+ {
+ if useNetwork {
+ if n, err := network.New(ctx); err != nil {
+ return nil, err
+ } else {
+ testcontainers.CleanupNetwork(t, n)
+ net = n
+ }
+ } else {
+ net = nil
}
- }()
+ }
+
+ httpWait := wait.ForHTTP("/.well-known/jmap")
+ httpWait.Port = dockernetwork.MustParsePort(httpPort)
+
+ configBuf := bytes.NewBufferString("")
+ template.Must(template.New("config").Delims("<<", ">>").Parse(jsonConfigTemplate)).Execute(configBuf, settings)
+ config := configBuf.String()
+ {
+ var recovery *testcontainers.DockerContainer
+ {
+ opts := []testcontainers.ContainerCustomizer{
+ testcontainers.WithLogConsumers(&printingLogConsumer{prefix: "RECOVERY"}),
+ testcontainers.WithExposedPorts(httpPort + "/tcp"),
+ testcontainers.WithEnv(map[string]string{
+ "STALWART_RECOVERY_ADMIN": strings.Join([]string{adminUsername, adminPassword}, ":"),
+ "STALWART_RECOVERY_MODE": "1",
+ }),
+ testcontainers.WithFiles(testcontainers.ContainerFile{
+ Reader: strings.NewReader(config),
+ ContainerFilePath: "/etc/stalwart/config.json",
+ FileMode: 0o666,
+ }),
+ testcontainers.WithWaitStrategyAndDeadline(
+ 30*time.Second,
+ wait.ForMappedPort(httpPort),
+ httpWait,
+ ),
+ testcontainers.WithName(recoveryAlias),
+ testcontainers.WithMounts(
+ testcontainers.ContainerMount{
+ Source: testcontainers.GenericVolumeMountSource{
+ Name: volumeName,
+ },
+ Target: "/var/lib/stalwart",
+ ReadOnly: false,
+ },
+ ),
+ }
+ if net != nil {
+ opts = append(opts, network.WithNetwork([]string{recoveryAlias}, net))
+ }
+
+ if c, err := testcontainers.Run(ctx, stalwartImage, opts...); err != nil {
+ return nil, err
+ } else {
+ testcontainers.CleanupContainer(t, c)
+ recovery = c
+ }
+ }
+
+ // import config using the cli
+ {
+ uri := ""
+ if net != nil {
+ uri = (&url.URL{Scheme: "http", Host: recoveryAlias + ":" + httpPort, Path: "/"}).String()
+ } else {
+ if ir, err := recovery.Inspect(ctx); err != nil {
+ return nil, err
+ } else {
+ id := ir.Config.Hostname
+ for _, network := range ir.NetworkSettings.Networks {
+ id = network.IPAddress.String()
+ }
+ uri = (&url.URL{Scheme: "http", Host: id + ":" + httpPort, Path: "/"}).String()
+ }
+ }
+
+ snapshot := []string{}
+ {
+ t := template.New("snapshot").Delims("<<", ">>")
+ for _, line := range dumpTemplate {
+ buf := bytes.NewBufferString("")
+ p, err := t.Parse(line)
+ if err != nil {
+ return nil, err
+ }
+ err = p.Execute(buf, settings)
+ if err != nil {
+ return nil, err
+ }
+
+ var m map[string]any
+ err = json.Unmarshal(buf.Bytes(), &m)
+ if err != nil {
+ return nil, err
+ }
+ b, err := json.Marshal(m)
+ if err != nil {
+ return nil, err
+ }
+ snapshot = append(snapshot, strings.TrimSpace(string(b)))
+ }
+ }
+ text := strings.Join(snapshot, "\n")
+
+ t.Logf("Snapshot:\n%s", text)
+ output, err := applySnapshot(ctx, net, uri, adminUsername, adminPassword, strings.NewReader(text))
+ if err != nil {
+ return nil, err
+ }
+ t.Logf("Output of applying configuration:\n%s", strings.Join(output, ""))
+ }
+
+ if err := recovery.Terminate(ctx, testcontainers.RemoveVolumes()); err != nil {
+ return nil, err
+ }
+ }
+
+ var container *testcontainers.DockerContainer
+ {
+ opts := []testcontainers.ContainerCustomizer{
+ testcontainers.WithLogConsumers(&printingLogConsumer{prefix: "STALWART"}),
+ testcontainers.WithExposedPorts(httpPort+"/tcp", imapsPort+"/tcp"),
+ testcontainers.WithFiles(testcontainers.ContainerFile{
+ Reader: strings.NewReader(config),
+ ContainerFilePath: "/etc/stalwart/config.json",
+ FileMode: 0o666,
+ }),
+ testcontainers.WithWaitStrategyAndDeadline(
+ 30*time.Second,
+ wait.ForMappedPort(httpPort),
+ httpWait,
+ ),
+ testcontainers.WithName(containerAlias),
+ testcontainers.WithMounts(
+ testcontainers.ContainerMount{
+ Source: testcontainers.GenericVolumeMountSource{
+ Name: volumeName,
+ },
+ Target: "/var/lib/stalwart",
+ ReadOnly: false,
+ },
+ ),
+ }
+
+ if net != nil {
+ opts = append(opts, network.WithNetwork([]string{containerAlias}, net))
+ }
+
+ if c, err := testcontainers.Run(ctx, stalwartImage, opts...); err != nil {
+ return nil, err
+ } else {
+ testcontainers.CleanupContainer(t, c, testcontainers.RemoveVolumes(volumeName))
+ container = c
+ }
+ }
ip, err := container.Host(ctx)
if err != nil {
return nil, err
}
- imapPort, err := container.MappedPort(ctx, "993")
+ imapPort, err := container.MappedPort(ctx, imapsPort)
if err != nil {
return nil, err
}
- tlsConfig := &tls.Config{InsecureSkipVerify: true}
+ domains := structs.Uniq(structs.Map(users[:], func(u User) string {
+ parts := strings.Split(u.email, "@")
+ return parts[1]
+ }))
+ slices.Sort(domains)
- loggerImpl := clog.NewLogger(clog.Level("trace"))
- logger := &loggerImpl
- var j Client
- var jmapBaseUrl *url.URL
- var sessionUrl *url.URL
- {
- tr := http.DefaultTransport.(*http.Transport).Clone()
- tr.ResponseHeaderTimeout = time.Duration(30 * time.Second)
- tr.TLSClientConfig = tlsConfig
- jh := *http.DefaultClient
- jh.Transport = tr
-
- wsd := &websocket.Dialer{
- TLSClientConfig: tlsConfig,
- HandshakeTimeout: time.Duration(10) * time.Second,
- }
-
- jmapPort, err := container.MappedPort(ctx, httpPort)
- if err != nil {
- return nil, err
- }
- jmapBaseUrl = &url.URL{
- Scheme: "http",
- Host: ip + ":" + jmapPort.Port(),
- Path: "/",
- }
- sessionUrl = jmapBaseUrl.JoinPath(".well-known", "jmap")
-
- if Wireshark != "" {
- fmt.Printf("\x1b[45;37;1m Starting Wireshark on port %v \x1b[0m\n", jmapPort)
- attr := os.ProcAttr{
- Dir: ".",
- Env: os.Environ(),
- Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
- }
- cmd := []string{Wireshark, "-pkSl", "-i", "lo", "-f", fmt.Sprintf("port %d", jmapPort.Num()), "-Y", "http||websocket"}
- process, err := os.StartProcess(Wireshark, cmd, &attr)
- require.NoError(t, err)
- err = process.Release()
- require.NoError(t, err)
-
- time.Sleep(10 * time.Second)
- }
-
- eventListener := nullHttpJmapApiClientEventListener{}
-
- auth := NewMasterAuthHttpJmapClientAuthenticator(masterUsername, masterPassword)
-
- api := NewHttpJmapClient(&jh, auth, eventListener)
-
- wscf, err := NewHttpWsClientFactory(wsd, auth, logger, eventListener)
- if err != nil {
- return nil, err
- }
-
- j = NewClient(api, api, api, wscf)
- }
-
- // provision some things using Stalwart's Management API
+ // provision some things using Stalwart's JMAP based Management API
{
var h http.Client
{
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.ResponseHeaderTimeout = time.Duration(30 * time.Second)
- tr.TLSClientConfig = tlsConfig
+ tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
h = *http.DefaultClient
h.Transport = tr
}
@@ -376,112 +895,178 @@ func newStalwartTest(t *testing.T, options ...func(map[string]any)) (*StalwartTe
apiPort, err := container.MappedPort(ctx, httpPort)
require.NoError(t, err)
- url := fmt.Sprintf("http://%s:%d/api/principal", ip, apiPort.Num())
+ apiPath := ""
+ {
+ // fetch JMAP session first
+ session := map[string]any{}
+ {
+ url := fmt.Sprintf("http://%s:%d/.well-known/jmap", ip, apiPort.Num())
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ require.NoError(t, err)
+ req.SetBasicAuth(adminUsername, adminPassword)
+ resp, err := h.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+ require.Equal(t, "200 OK", resp.Status)
+ err = json.NewDecoder(resp.Body).Decode(&session)
+ require.NoError(t, err)
+ }
+ if u, ok := session["apiUrl"]; ok {
+ p, err := url.Parse(u.(string))
+ require.NoError(t, err)
+ apiPath = strings.TrimLeft(p.Path, "/")
+ } else {
+ require.FailNow(t, "failed to find apiUrl in JMAP Session")
+ }
+ }
+ url := fmt.Sprintf("http://%s:%d/%s", ip, apiPort.Num(), apiPath)
+ domainIds := map[string]string{}
for _, domain := range domains {
fmt.Printf("Creating domain '%v'\n", domain)
- bb, err := json.Marshal(map[string]any{
- "type": "domain",
- "name": domain,
- "description": domain,
+ id, err := createManagementObject(ctx, h, url, adminUsername, adminPassword, "Domain", map[string]any{
+ "name": domain,
+ "aliases": map[string]any{},
+ "certificateManagement": map[string]any{"@type": "Manual"},
+ "dkimManagement": map[string]any{"@type": "Manual"},
+ "dnsManagement": map[string]any{"@type": "Manual"},
+ "subAddressing": map[string]any{"@type": "Enabled"},
})
require.NoError(t, err)
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(bb))
- require.NoError(t, err)
- req.SetBasicAuth("mailadmin", "secret")
- resp, err := h.Do(req)
- require.NoError(t, err)
- require.Equal(t, "200 OK", resp.Status) //NOSONAR
+ domainIds[domain] = id
}
for _, user := range users {
fmt.Printf("Creating individual '%v'\n", user.name)
- bb, err := json.Marshal(map[string]any{
- "type": "individual",
- "name": user.name,
- "description": user.description,
- "emails": user.email,
- "roles": []string{"user"},
- "secrets": user.password,
- "quota": 20000000000,
- })
- require.NoError(t, err)
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(bb))
- require.NoError(t, err)
- req.SetBasicAuth("mailadmin", "secret")
- resp, err := h.Do(req)
- require.NoError(t, err)
- require.Equal(t, "200 OK", resp.Status)
- // fetch the user once with the superadmin credentials to "activate" it,
- // it is unclear why that is needed, but without that, we get errors back
- // that we are not allowed to access that resource
+ localPart := ""
+ domain := ""
+ domainId := ""
{
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- require.NoError(t, err)
- req.SetBasicAuth("mailadmin", "secret")
- resp, err := h.Do(req)
- require.NoError(t, err)
- require.Equal(t, "200 OK", resp.Status)
- }
- }
-
- {
- require.NoError(t, err)
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
- require.NoError(t, err)
- req.SetBasicAuth("mailadmin", "secret")
- resp, err := h.Do(req)
- require.NoError(t, err)
- require.Equal(t, "200 OK", resp.Status)
- var list struct {
- Data struct {
- Total int `json:"total"`
- Items []struct {
- Type string `json:"type"`
- Id int `json:"id"`
- Name string `json:"name"`
- Emails []string `json:"emails"`
- Roles []string `json:"roles"`
- } `json:"items"`
- } `json:"data"`
- }
- bb, err := io.ReadAll(resp.Body)
- require.NoError(t, err)
- defer resp.Body.Close()
- err = json.Unmarshal(bb, &list)
- require.NoError(t, err)
- individuals := []struct {
- Id int
- Name string
- Emails []string
- Roles []string
- }{}
- for _, p := range list.Data.Items {
- if p.Type == "individual" {
- individuals = append(individuals, struct {
- Id int
- Name string
- Emails []string
- Roles []string
- }{p.Id, p.Name, p.Emails, p.Roles})
+ parts := strings.Split(user.alias, "@")
+ localPart = parts[0]
+ domain = parts[1]
+ if v, ok := domainIds[domain]; ok {
+ domainId = v
+ } else {
+ require.FailNow(t, "failed to find id for domain '%s' in user email address '%s'", domain, user.email)
}
}
- require.Equal(t, len(users), len(individuals))
+ _, err := createManagementObject(ctx, h, url, adminUsername, adminPassword, "Account", map[string]any{
+ "@type": "User",
+ "name": user.name,
+ "description": user.description,
+ "aliases": map[string]any{
+ "0": map[string]any{
+ "enabled": true,
+ "name": localPart,
+ "domainId": domainId,
+ },
+ },
+ "credentials": map[string]any{
+ "0": map[string]any{
+ "@type": "Password",
+ "secret": user.password,
+ "allowedIps": map[string]any{},
+ "expiresAt": nil,
+ },
+ },
+ "domainId": domainId,
+ "encryptionAtRest": map[string]any{"@type": "Disabled"},
+ "memberGroupIds": map[string]any{},
+ "permissions": map[string]any{"@type": "Inherit"},
+ "quotas": map[string]any{
+ "maxEmails": 200000,
+ "maxDiskQuota": 20000000000,
+ },
+ "roles": map[string]any{"@type": "User"},
+ })
+ require.NoError(t, err)
+ }
+
+ // fetch all users and double-check whether they are all in there, but also use this
+ // to detect which primary email address has been assigned to the user by Stalwart,
+ // and then store it into the user object
+ {
+ result, err := postJmap(ctx, h, url, adminUsername, adminPassword, map[string]any{
+ "using": []string{"urn:ietf:params:jmap:core", "urn:stalwart:jmap"},
+ "methodCalls": []any{
+ []any{
+ "x:Account/get",
+ map[string]any{},
+ "0",
+ },
+ },
+ })
+ require.NoError(t, err)
+
+ usersByName := structs.Index(users[:], func(u User) string { return u.name })
+ {
+ mr := result["methodResponses"].([]any)
+ f := mr[0].([]any)
+ p := f[1].(map[string]any)
+ l := p["list"].([]any)
+ for _, u := range l {
+ u := u.(map[string]any)
+ name := u["name"].(string)
+ email := u["emailAddress"].(string)
+ if match, ok := usersByName[name]; ok {
+ require.Equal(t, match.email, email)
+ aliases := u["aliases"].(map[string]any)
+ require.Len(t, aliases, 1)
+ for _, alias := range aliases {
+ parts := strings.Split(match.alias, "@")
+ alias := alias.(map[string]any)
+ require.True(t, alias["enabled"].(bool))
+ require.Equal(t, parts[0], alias["name"].(string))
+ }
+ delete(usersByName, name)
+ }
+ }
+ }
+ require.Empty(t, usersByName)
+ text := ""
+ {
+ if b, err := json.MarshalIndent(result, "", " "); err == nil {
+ text = string(b)
+ }
+ }
+ t.Logf("Accounts:\n%s", text)
}
{
+
+ loggerImpl := oclog.NewLogger(oclog.Level("trace"))
+ logger := &loggerImpl
+
+ auth := &ContextPasswordAuthHttpJmapClientAuthenticator{key: "password"}
+
+ j, _, sessionUrl, err := createJmapClient(container, ctx, auth, logger)
+ if err != nil {
+ return nil, err
+ }
+
// check whether we can fetch a session for the provisioned users
for _, user := range users {
- session, err := j.FetchSession(ctx, sessionUrl, user.name, logger)
- require.NoError(t, err, "failed to retrieve JMAP session for newly created principal '%s'", user.name)
- require.Equal(t, user.name, session.Username)
+ ctx := context.WithValue(ctx, "password", user.password)
+ session, err := j.FetchSession(ctx, sessionUrl, user.email, oclog.From(logger.With().Str("username", user.email).Str("password", user.password)))
+ require.NoError(t, err, "failed to retrieve JMAP session for newly created principal '%s'", user.email)
+ require.Equal(t, user.email, session.Username)
}
}
}
- success = true
+ loggerImpl := oclog.NewLogger(oclog.Level("trace"))
+ logger := &loggerImpl
+
+ auth := NewMasterAuthHttpJmapClientAuthenticator(masterUsername, masterPassword)
+
+ j, jmapBaseUrl, sessionUrl, err := createJmapClient(container, ctx, auth, logger)
+ if err != nil {
+ return nil, err
+ }
+
return &StalwartTest{
t: t,
ip: ip,
@@ -498,9 +1083,9 @@ func newStalwartTest(t *testing.T, options ...func(map[string]any)) (*StalwartTe
var urlHostRegex = regexp.MustCompile(`^(https?://)(.+?)/(.+)$`)
-func replaceHost(u string, host string) (string, error) {
+func replaceHostProto(u string, proto string, host string) (string, error) {
if m := urlHostRegex.FindAllStringSubmatch(u, -1); m != nil {
- return fmt.Sprintf("%s%s/%s", m[0][1], host, m[0][3]), nil
+ return fmt.Sprintf("%s://%s/%s", proto, host, m[0][3]), nil
} else {
return "", fmt.Errorf("'%v' does not match '%v'", u, urlHostRegex)
}
@@ -1171,7 +1756,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
defer s.Close()
user := pickUser()
- session := s.Session(user.name)
+ session := s.Session(user.email)
ctx := s.Context(session)
accountId := acc(session)
diff --git a/pkg/jmap/http.go b/pkg/jmap/http.go
index ff739c13b2..c9fcef0dcb 100644
--- a/pkg/jmap/http.go
+++ b/pkg/jmap/http.go
@@ -532,6 +532,7 @@ func (w *HttpWsClientFactory) connect(ctx context.Context, sessionProvider func(
h := http.Header{}
w.auth(ctx, username, logger, h)
+ w.logger.Trace().Str("username", log.SafeString(username)).Str("url", log.SafeString(u.String())).Msgf("connecting")
c, res, err := w.dialer.DialContext(ctx, u.String(), h)
if err != nil {
return nil, "", endpoint, jmapError(err, JmapErrorFailedToEstablishWssConnection)
diff --git a/pkg/jmap/integration_addressbook_test.go b/pkg/jmap/integration_addressbook_test.go
index 97428cc78e..87d6f8c9eb 100644
--- a/pkg/jmap/integration_addressbook_test.go
+++ b/pkg/jmap/integration_addressbook_test.go
@@ -59,8 +59,8 @@ func TestAddressBooks(t *testing.T) {
},
func(orig AddressBook) AddressBookChange {
return AddressBookChange{
- Description: ptr(orig.Description + " (changed)"),
- IsSubscribed: ptr(!orig.IsSubscribed),
+ Description: new(orig.Description + " (changed)"),
+ IsSubscribed: new(!orig.IsSubscribed),
}
},
func(t *testing.T, orig AddressBook, _ AddressBookChange, changed AddressBook) {
@@ -85,7 +85,7 @@ func TestContacts(t *testing.T) {
defer s.Close()
user := pickUser()
- session := s.Session(user.name)
+ session := s.Session(user.email)
ctx := s.Context(session)
accountId, addressbookId, expectedContactCardsById, boxes, err := s.fillContacts(t, count, session, ctx, user)
@@ -213,8 +213,8 @@ func TestContacts(t *testing.T) {
now := time.Now().Truncate(time.Duration(1) * time.Second).UTC()
for _, event := range expectedContactCardsById {
change := ContactCardChange{
- Language: ptr("xyz"),
- Updated: ptr(now),
+ Language: new("xyz"),
+ Updated: new(now),
}
result, err := s.client.UpdateContactCard(accountId, event.Id, change, ctx)
require.NoError(err)
@@ -359,7 +359,7 @@ func (s *StalwartTest) fillContacts( //NOSONAR
user User,
) (AccountId, string, map[string]ContactCard, ContactsBoxes, error) {
require := require.New(t)
- c, err := NewTestJmapClient(session, user.name, user.password, true, true)
+ c, err := NewTestJmapClient(session, user.email, user.password, true, true)
require.NoError(err)
defer c.Close()
@@ -401,11 +401,11 @@ func (s *StalwartTest) fillContacts( //NOSONAR
card := ContactCardChange{
Type: jscontact.ContactCardType,
- Version: ptr(jscontact.JSContactVersion_1_0),
+ Version: new(jscontact.JSContactVersion_1_0),
AddressBookIds: toBoolPtrMap([]string{addressbookId}),
ProdId: &productName,
Language: &language,
- Kind: ptr(jscontact.ContactCardKindIndividual),
+ Kind: new(jscontact.ContactCardKindIndividual),
Name: &nameObj,
}
diff --git a/pkg/jmap/integration_calendar_test.go b/pkg/jmap/integration_calendar_test.go
index 53aaf24976..442165ba47 100644
--- a/pkg/jmap/integration_calendar_test.go
+++ b/pkg/jmap/integration_calendar_test.go
@@ -49,8 +49,8 @@ func TestCalendars(t *testing.T) { //NOSONAR
},
func(orig Calendar) CalendarChange {
return CalendarChange{
- Description: ptr(orig.Description + " (changed)"),
- IsSubscribed: ptr(!orig.IsSubscribed),
+ Description: new(orig.Description + " (changed)"),
+ IsSubscribed: new(!orig.IsSubscribed),
}
},
func(t *testing.T, orig Calendar, _ CalendarChange, changed Calendar) {
@@ -75,7 +75,7 @@ func TestEvents(t *testing.T) {
defer s.Close()
user := pickUser()
- session := s.Session(user.name)
+ session := s.Session(user.email)
ctx := s.Context(session)
accountId, calendarId, expectedEventsById, boxes, err := s.fillEvents(t, count, ctx, user)
@@ -178,7 +178,7 @@ func TestEvents(t *testing.T) {
for _, event := range expectedEventsById {
change := CalendarEventChange{
EventChange: jscalendar.EventChange{
- Status: ptr(jscalendar.StatusCancelled),
+ Status: new(jscalendar.StatusCancelled),
ObjectChange: jscalendar.ObjectChange{
Sequence: uintPtr(99),
ShowWithoutTime: truep,
@@ -382,7 +382,7 @@ func (s *StalwartTest) fillEvents( //NOSONAR
user User,
) (AccountId, string, map[string]CalendarEvent, EventsBoxes, error) {
require := require.New(t)
- c, err := NewTestJmapClient(ctx.Session, user.name, user.password, true, true)
+ c, err := NewTestJmapClient(ctx.Session, user.email, user.password, true, true)
require.NoError(err)
defer c.Close()
@@ -467,7 +467,7 @@ func (s *StalwartTest) fillEvents( //NOSONAR
EventChange: jscalendar.EventChange{
Type: jscalendar.EventType,
Start: jscalendar.LocalDateTime(start),
- Duration: ptr(jscalendar.Duration(duration)),
+ Duration: new(jscalendar.Duration(duration)),
Status: &status,
ObjectChange: jscalendar.ObjectChange{
CommonObjectChange: jscalendar.CommonObjectChange{
diff --git a/pkg/jmap/integration_email_test.go b/pkg/jmap/integration_email_test.go
index d7b1e1389f..2c70eb85f4 100644
--- a/pkg/jmap/integration_email_test.go
+++ b/pkg/jmap/integration_email_test.go
@@ -39,7 +39,7 @@ func TestEmails(t *testing.T) {
defer s.Close()
user := pickUser()
- session := s.Session(user.name)
+ session := s.Session(user.email)
ctx := s.Context(session)
accountId := session.PrimaryAccounts.Mail
@@ -59,9 +59,11 @@ func TestEmails(t *testing.T) {
result, err := s.client.GetIdentities(accountId, []string{}, ctx)
require.NoError(err)
require.Equal(session.State, result.GetSessionState())
- require.Len(result.Payload.List, 1)
- require.Equal(user.email, result.Payload.List[0].Email)
- require.Equal(user.description, result.Payload.List[0].Name)
+ require.Len(result.Payload.List, 2)
+ emailMatches := structs.Filter(result.Payload.List, func(i Identity) bool { return i.Email == user.email })
+ require.Len(emailMatches, 1)
+ aliasMatches := structs.Filter(result.Payload.List, func(i Identity) bool { return i.Email == user.alias })
+ require.Len(aliasMatches, 1)
}
{
@@ -122,7 +124,7 @@ func TestSendingEmails(t *testing.T) {
defer s.Close()
from := pickUser()
- session := s.Session(from.name)
+ session := s.Session(from.email)
ctx := s.Context(session)
accountId := session.PrimaryAccounts.Mail
@@ -131,7 +133,7 @@ func TestSendingEmails(t *testing.T) {
others := structs.Filter(users[:], func(u User) bool { return u.name != from.name })
to = others[rand.Intn(len(others))]
}
- toSession := s.Session(to.name)
+ toSession := s.Session(to.email)
toAccountId := toSession.PrimaryAccounts.Mail
var cc User
@@ -139,7 +141,7 @@ func TestSendingEmails(t *testing.T) {
others := structs.Filter(users[:], func(u User) bool { return u.name != from.name && u.name != to.name })
cc = others[rand.Intn(len(others))]
}
- ccSession := s.Session(cc.name)
+ ccSession := s.Session(cc.email)
ccAccountId := ccSession.PrimaryAccounts.Mail
var mailboxPerRole map[string]Mailbox
@@ -190,8 +192,10 @@ func TestSendingEmails(t *testing.T) {
{
result, err := s.client.GetIdentities(accountId, []string{}, ctx)
require.NoError(err)
- require.NotEmpty(result.Payload.List)
- identity = result.Payload.List[0]
+ require.Len(result.Payload.List, 2)
+ matchesAlias := structs.Filter(result.Payload.List, func(i Identity) bool { return i.Email == from.alias })
+ require.Len(matchesAlias, 1)
+ identity = matchesAlias[0]
}
create := EmailChange{
@@ -270,7 +274,7 @@ func TestSendingEmails(t *testing.T) {
require.Equal(sub.UndoStatus, UndoStatusPending) // this *might* be fragile: if the server is fast enough, would we get "final" here?
require.Empty(sub.DsnBlobIds)
require.Empty(sub.MdnBlobIds)
- require.Equal(from.email, sub.Envelope.MailFrom.Email)
+ require.Equal(from.alias, sub.Envelope.MailFrom.Email)
require.Nil(sub.Envelope.MailFrom.Parameters)
require.Len(sub.Envelope.RcptTo, 2)
require.Contains(sub.Envelope.RcptTo, Address{Email: to.email})
@@ -325,7 +329,7 @@ func TestSendingEmails(t *testing.T) {
require.Equal(1, mailbox.TotalEmails)
}
}
- require.NotEmpty(inboxId, "failed to find the Mailbox with the 'inbox' role for %v", r.user.name)
+ require.NotEmpty(inboxId, "failed to find the Mailbox with the 'inbox' role for %v", r.user.email)
}
result, err := s.client.QueryEmails([]AccountId{r.accountId}, EmailFilterCondition{InMailbox: inboxId}, 0, 0, true, 0, rctx)
@@ -538,7 +542,7 @@ func (s *StalwartTest) fillEmailsWithImap(folder string, count int, empty bool,
}
}(c)
- if err = c.Login(user.name, user.password).Wait(); err != nil {
+ if err = c.Login(user.email, user.password).Wait(); err != nil {
return nil, 0, err
}
@@ -586,7 +590,7 @@ func (s *StalwartTest) fillEmailsWithImap(folder string, count int, empty bool,
domain := addressParts[0][2]
toName := displayName
- toAddress := fmt.Sprintf("%s@%s", user.name, domain)
+ toAddress := fmt.Sprintf("%s@%s", user.email, domain)
ccName1 := "Team Lead"
ccAddress1 := fmt.Sprintf("lead@%s", domain)
ccName2 := "Coworker"
diff --git a/pkg/jmap/integration_ws_test.go b/pkg/jmap/integration_ws_test.go
index 8bcd0b35e3..6a5dd46854 100644
--- a/pkg/jmap/integration_ws_test.go
+++ b/pkg/jmap/integration_ws_test.go
@@ -68,7 +68,7 @@ func TestWs(t *testing.T) {
defer s.Close()
user := pickUser()
- session := s.Session(user.name)
+ session := s.Session(user.email)
ctx := s.Context(session)
mailAccountId := session.PrimaryAccounts.Mail
@@ -77,7 +77,7 @@ func TestWs(t *testing.T) {
_, inboxFolder = s.findInbox(t, mailAccountId, ctx)
}
- l := &testWsPushListener{t: t, username: user.name, logger: s.logger, mailAccountId: mailAccountId}
+ l := &testWsPushListener{t: t, username: user.email, logger: s.logger, mailAccountId: mailAccountId}
s.client.AddWsPushListener(l)
require.Equal(uint32(0), l.calls.Load())
diff --git a/pkg/jmap/model_examples.go b/pkg/jmap/model_examples.go
index 2585ca15bc..43a42c296b 100644
--- a/pkg/jmap/model_examples.go
+++ b/pkg/jmap/model_examples.go
@@ -575,7 +575,7 @@ func (e Exemplar) MailboxInbox() (Mailbox, string, string) {
Id: e.MailboxInboxId,
Name: "Inbox",
Role: JmapMailboxRoleInbox,
- SortOrder: ptr(0),
+ SortOrder: new(0),
TotalEmails: 1291,
UnreadEmails: 82,
TotalThreads: 891,
@@ -600,7 +600,7 @@ func (e Exemplar) MailboxInboxProjects() (Mailbox, string, string) {
Id: e.MailboxProjectId,
ParentId: e.MailboxInboxId,
Name: "Projects",
- SortOrder: ptr(0),
+ SortOrder: new(0),
TotalEmails: 112,
UnreadEmails: 3,
TotalThreads: 85,
@@ -625,7 +625,7 @@ func (e Exemplar) MailboxDrafts() (Mailbox, string, string) {
Id: e.MailboxDraftsId,
Name: "Drafts",
Role: JmapMailboxRoleDrafts,
- SortOrder: ptr(0),
+ SortOrder: new(0),
TotalEmails: 12,
UnreadEmails: 1,
TotalThreads: 12,
@@ -650,7 +650,7 @@ func (e Exemplar) MailboxSent() (Mailbox, string, string) {
Id: e.MailboxSentId,
Name: "Sent Items",
Role: JmapMailboxRoleSent,
- SortOrder: ptr(0),
+ SortOrder: new(0),
TotalEmails: 1621,
UnreadEmails: 0,
TotalThreads: 1621,
@@ -675,7 +675,7 @@ func (e Exemplar) MailboxJunk() (Mailbox, string, string) {
Id: e.MailboxJunkId,
Name: "Junk Mail",
Role: JmapMailboxRoleJunk,
- SortOrder: ptr(0),
+ SortOrder: new(0),
TotalEmails: 251,
UnreadEmails: 0,
TotalThreads: 251,
@@ -700,7 +700,7 @@ func (e Exemplar) MailboxDeleted() (Mailbox, string, string) {
Id: e.MailboxDeletedId,
Name: "Deleted Items",
Role: JmapMailboxRoleTrash,
- SortOrder: ptr(0),
+ SortOrder: new(0),
TotalEmails: 99,
UnreadEmails: 0,
TotalThreads: 91,
@@ -918,7 +918,7 @@ func (e Exemplar) AddressBook() AddressBook {
func (e Exemplar) AddressBookChange() AddressBookChange {
return AddressBookChange{
- Description: ptr("A different name"),
+ Description: new("A different name"),
}
}
@@ -2325,9 +2325,9 @@ func (e Exemplar) CalendarEventSearchResults() CalendarEventSearchResults {
return CalendarEventSearchResults{
Results: []CalendarEvent{ev},
CanCalculateChanges: true,
- Position: ptr(uint(3)),
- Limit: ptr(uint(10)),
- Total: ptr(uint(4)),
+ Position: new(uint(3)),
+ Limit: new(uint(10)),
+ Total: new(uint(4)),
}
}
@@ -2337,8 +2337,8 @@ func (e Exemplar) ContactCardSearchResults() ContactCardSearchResults {
return ContactCardSearchResults{
Results: []ContactCard{c1, c2},
CanCalculateChanges: true,
- Position: ptr(uint(3)),
- Limit: ptr(uint(10)),
- Total: ptr(uint(4)),
+ Position: new(uint(3)),
+ Limit: new(uint(10)),
+ Total: new(uint(4)),
}
}
diff --git a/pkg/jmap/tools.go b/pkg/jmap/tools.go
index b993ee2a9d..f9f58b6b78 100644
--- a/pkg/jmap/tools.go
+++ b/pkg/jmap/tools.go
@@ -394,14 +394,10 @@ func mapPairs[K comparable, L, R any](left map[K]L, right map[K]R) map[K]pair[L,
}
var (
- truep = ptr(true)
- falsep = ptr(false)
+ truep = new(true)
+ falsep = new(false)
)
-func ptr[T any | string | int | uint | bool](t T) *T {
- return &t
-}
-
func identity1[T any](t T) T {
return t
}
@@ -410,7 +406,7 @@ func list[T Foo, GETRESP GetResponse[T]](r GETRESP) []T { return r.GetList() }
func getid[T Idable](r T) string { return r.GetId() }
func uintPtr[T int | uint](i T) *uint {
- return ptr(uint(i))
+ return new(uint(i))
}
func valueIf[T any | uint | int | bool](value *T, condition bool) *T {
diff --git a/vendor/github.com/go-crypt/crypt/LICENSE b/vendor/github.com/go-crypt/crypt/LICENSE
deleted file mode 100644
index a149832c0d..0000000000
--- a/vendor/github.com/go-crypt/crypt/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2022 github.com/go-crypt/crypt
-
-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.
\ No newline at end of file
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/const.go b/vendor/github.com/go-crypt/crypt/algorithm/const.go
deleted file mode 100644
index 08c585b4db..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/const.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package algorithm
-
-const (
- // DigestSHA1 is te name for SHA1 digests.
- DigestSHA1 = "sha1"
-
- // DigestSHA224 is te name for SHA224 digests.
- DigestSHA224 = "sha224"
-
- // DigestSHA256 is te name for SHA256 digests.
- DigestSHA256 = "sha256"
-
- // DigestSHA384 is te name for SHA384 digests.
- DigestSHA384 = "sha384"
-
- // DigestSHA512 is te name for SHA512 digests.
- DigestSHA512 = "sha512"
-)
-
-const (
- // SaltLengthDefault is the default salt size for most implementations.
- SaltLengthDefault = 16
-
- // KeyLengthDefault is the default key size for most implementations.
- KeyLengthDefault = 32
-)
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/doc.go b/vendor/github.com/go-crypt/crypt/algorithm/doc.go
deleted file mode 100644
index 35a89bc551..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/doc.go
+++ /dev/null
@@ -1,3 +0,0 @@
-// Package algorithm is a package which contains the individual algorithms and interfaces related to their
-// implementation.
-package algorithm
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/errors.go b/vendor/github.com/go-crypt/crypt/algorithm/errors.go
deleted file mode 100644
index 2cec6fc8ae..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/errors.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package algorithm
-
-import (
- "errors"
-)
-
-var (
- // ErrEncodedHashInvalidFormat is an error returned when an encoded hash has an invalid format.
- ErrEncodedHashInvalidFormat = errors.New("provided encoded hash has an invalid format")
-
- // ErrEncodedHashInvalidIdentifier is an error returned when an encoded hash has an invalid identifier for the
- // given digest.
- ErrEncodedHashInvalidIdentifier = errors.New("provided encoded hash has an invalid identifier")
-
- // ErrEncodedHashInvalidVersion is an error returned when an encoded hash has an unsupported or otherwise invalid
- // version.
- ErrEncodedHashInvalidVersion = errors.New("provided encoded hash has an invalid version")
-
- // ErrEncodedHashInvalidOption is an error returned when an encoded hash has an unsupported or otherwise invalid
- // option in the option field.
- ErrEncodedHashInvalidOption = errors.New("provided encoded hash has an invalid option")
-
- // ErrEncodedHashInvalidOptionKey is an error returned when an encoded hash has an unknown or otherwise invalid
- // option key in the option field.
- ErrEncodedHashInvalidOptionKey = errors.New("provided encoded hash has an invalid option key")
-
- // ErrEncodedHashInvalidOptionValue is an error returned when an encoded hash has an unknown or otherwise invalid
- // option value in the option field.
- ErrEncodedHashInvalidOptionValue = errors.New("provided encoded hash has an invalid option value")
-
- // ErrEncodedHashKeyEncoding is an error returned when an encoded hash has a salt with an invalid or unsupported
- // encoding.
- ErrEncodedHashKeyEncoding = errors.New("provided encoded hash has a key value that can't be decoded")
-
- // ErrEncodedHashSaltEncoding is an error returned when an encoded hash has a salt with an invalid or unsupported
- // encoding.
- ErrEncodedHashSaltEncoding = errors.New("provided encoded hash has a salt value that can't be decoded")
-
- // ErrKeyDerivation is returned when a Key function returns an error.
- ErrKeyDerivation = errors.New("failed to derive the key with the provided parameters")
-
- // ErrSaltEncoding is an error returned when a salt has an invalid or unsupported encoding.
- ErrSaltEncoding = errors.New("provided salt has a value that can't be decoded")
-
- // ErrPasswordInvalid is an error returned when a password has an invalid or unsupported properties. It is NOT
- // returned on password mismatches.
- ErrPasswordInvalid = errors.New("password is invalid")
-
- // ErrSaltInvalid is an error returned when a salt has an invalid or unsupported properties.
- ErrSaltInvalid = errors.New("salt is invalid")
-
- // ErrSaltReadRandomBytes is an error returned when generating the random bytes for salt resulted in an error.
- ErrSaltReadRandomBytes = errors.New("could not read random bytes for salt")
-
- // ErrParameterInvalid is an error returned when a parameter has an invalid value.
- ErrParameterInvalid = errors.New("parameter is invalid")
-)
-
-// Error format strings.
-const (
- ErrFmtInvalidIntParameter = "%w: parameter '%s' must be between %d%s and %d but is set to '%d'"
- ErrFmtDigestDecode = "%s decode error: %w"
- ErrFmtDigestMatch = "%s match error: %w"
- ErrFmtHasherHash = "%s hashing error: %w"
- ErrFmtHasherValidation = "%s validation error: %w"
-)
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/const.go b/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/const.go
deleted file mode 100644
index b7906462b8..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/const.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package shacrypt
-
-const (
- // EncodingFmt is the encoding format for this algorithm.
- EncodingFmt = "$%s$rounds=%d$%s$%s"
-
- // EncodingFmtRoundsOmitted is the encoding format for this algorithm when the rounds can be omitted.
- EncodingFmtRoundsOmitted = "$%s$%s$%s"
-
- // AlgName is the name for this algorithm.
- AlgName = "shacrypt"
-
- // AlgIdentifierSHA256 is the identifier used in encoded SHA256 variants of this algorithm.
- AlgIdentifierSHA256 = "5"
-
- // AlgIdentifierSHA512 is the identifier used in encoded SHA512 variants of this algorithm.
- AlgIdentifierSHA512 = "6"
-
- // IterationsMin is the minimum number of iterations accepted.
- IterationsMin = 1000
-
- // IterationsMax is the maximum number of iterations accepted.
- IterationsMax = 999999999
-
- // IterationsDefaultSHA256 is the default number of iterations for SHA256.
- IterationsDefaultSHA256 = 1000000
-
- // IterationsDefaultSHA512 is the default number of iterations for SHA512.
- IterationsDefaultSHA512 = 500000
-
- // IterationsDefaultOmitted is the default number of iterations when the rounds are omitted.
- IterationsDefaultOmitted = 5000
-
- // SaltLengthMin is the minimum salt length.
- SaltLengthMin = 1
-
- // SaltLengthMax is the maximum salt length.
- SaltLengthMax = 16
-
- // SaltCharSet are the valid characters for the salt.
- SaltCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./"
-)
-
-const (
- variantDefault = VariantSHA512
-)
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/decoder.go b/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/decoder.go
deleted file mode 100644
index a0fb81a839..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/decoder.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package shacrypt
-
-import (
- "fmt"
- "strconv"
-
- "github.com/go-crypt/crypt/algorithm"
- "github.com/go-crypt/crypt/internal/encoding"
-)
-
-// RegisterDecoder the decoder with the algorithm.DecoderRegister.
-func RegisterDecoder(r algorithm.DecoderRegister) (err error) {
- if err = RegisterDecoderSHA256(r); err != nil {
- return err
- }
-
- if err = RegisterDecoderSHA512(r); err != nil {
- return err
- }
-
- return nil
-}
-
-// RegisterDecoderSHA256 registers specifically the sha256 decoder variant with the algorithm.DecoderRegister.
-func RegisterDecoderSHA256(r algorithm.DecoderRegister) (err error) {
- if err = r.RegisterDecodeFunc(VariantSHA256.Prefix(), DecodeVariant(VariantSHA256)); err != nil {
- return err
- }
-
- return nil
-}
-
-// RegisterDecoderSHA512 registers specifically the sha512 decoder variant with the algorithm.DecoderRegister.
-func RegisterDecoderSHA512(r algorithm.DecoderRegister) (err error) {
- if err = r.RegisterDecodeFunc(VariantSHA512.Prefix(), DecodeVariant(VariantSHA512)); err != nil {
- return err
- }
-
- return nil
-}
-
-// Decode the encoded digest into a algorithm.Digest.
-func Decode(encodedDigest string) (digest algorithm.Digest, err error) {
- return DecodeVariant(VariantNone)(encodedDigest)
-}
-
-// DecodeVariant the encoded digest into a algorithm.Digest provided it matches the provided Variant. If VariantNone is
-// used all variants can be decoded.
-func DecodeVariant(v Variant) func(encodedDigest string) (digest algorithm.Digest, err error) {
- return func(encodedDigest string) (digest algorithm.Digest, err error) {
- var (
- parts []string
- variant Variant
- )
-
- if variant, parts, err = decoderParts(encodedDigest); err != nil {
- return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err)
- }
-
- if v != VariantNone && v != variant {
- return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, fmt.Errorf("the '%s' variant cannot be decoded only the '%s' variant can be", variant.String(), v.String()))
- }
-
- if digest, err = decode(variant, parts); err != nil {
- return nil, fmt.Errorf(algorithm.ErrFmtDigestDecode, AlgName, err)
- }
-
- return digest, nil
- }
-}
-
-func decoderParts(encodedDigest string) (variant Variant, parts []string, err error) {
- parts = encoding.Split(encodedDigest, -1)
-
- if n := len(parts); n != 4 && n != 5 {
- return VariantNone, nil, algorithm.ErrEncodedHashInvalidFormat
- }
-
- variant = NewVariant(parts[1])
-
- if variant == VariantNone {
- return variant, nil, fmt.Errorf("%w: identifier '%s' is not an encoded %s digest", algorithm.ErrEncodedHashInvalidIdentifier, parts[1], AlgName)
- }
-
- return variant, parts[2:], nil
-}
-
-func decode(variant Variant, parts []string) (digest algorithm.Digest, err error) {
- decoded := &Digest{
- variant: variant,
- }
-
- var (
- ip, is, ik int
- )
-
- switch len(parts) {
- case 2:
- ip, is, ik = -1, 0, 1
- case 3:
- ip, is, ik = 0, 1, 2
- }
-
- if len(parts[ik]) == 0 {
- return nil, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrEncodedHashKeyEncoding)
- }
-
- decoded.iterations = IterationsDefaultOmitted
-
- var params []encoding.Parameter
-
- if ip >= 0 {
- if params, err = encoding.DecodeParameterStr(parts[ip]); err != nil {
- return nil, err
- }
- }
-
- for _, param := range params {
- switch param.Key {
- case "rounds":
- var rounds uint64
-
- if rounds, err = strconv.ParseUint(param.Value, 10, 32); err != nil {
- return nil, fmt.Errorf("%w: option '%s' has invalid value '%s': %v", algorithm.ErrEncodedHashInvalidOptionValue, param.Key, param.Value, err)
- }
-
- decoded.iterations = int(rounds)
- default:
- return nil, fmt.Errorf("%w: option '%s' with value '%s' is unknown", algorithm.ErrEncodedHashInvalidOptionKey, param.Key, param.Value)
- }
- }
-
- decoded.salt, decoded.key = []byte(parts[is]), []byte(parts[ik])
-
- return decoded, nil
-}
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/digest.go b/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/digest.go
deleted file mode 100644
index 46a760220b..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/digest.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package shacrypt
-
-import (
- "crypto/subtle"
- "fmt"
- "strings"
-
- xcrypt "github.com/go-crypt/x/crypt"
-
- "github.com/go-crypt/crypt/algorithm"
-)
-
-// Digest is a digest which handles SHA-crypt hashes like SHA256 or SHA512.
-type Digest struct {
- variant Variant
-
- iterations int
- salt, key []byte
-}
-
-// Match returns true if the string password matches the current shacrypt.Digest.
-func (d *Digest) Match(password string) (match bool) {
- return d.MatchBytes([]byte(password))
-}
-
-// MatchBytes returns true if the []byte passwordBytes matches the current shacrypt.Digest.
-func (d *Digest) MatchBytes(passwordBytes []byte) (match bool) {
- match, _ = d.MatchBytesAdvanced(passwordBytes)
-
- return match
-}
-
-// MatchAdvanced is the same as Match except if there is an error it returns that as well.
-func (d *Digest) MatchAdvanced(password string) (match bool, err error) {
- if match, err = d.MatchBytesAdvanced([]byte(password)); err != nil {
- return match, fmt.Errorf(algorithm.ErrFmtDigestMatch, AlgName, err)
- }
-
- return match, nil
-}
-
-// MatchBytesAdvanced is the same as MatchBytes except if there is an error it returns that as well.
-func (d *Digest) MatchBytesAdvanced(passwordBytes []byte) (match bool, err error) {
- if len(d.key) == 0 {
- return false, fmt.Errorf("%w: key has 0 bytes", algorithm.ErrPasswordInvalid)
- }
-
- return subtle.ConstantTimeCompare(d.key, xcrypt.KeySHACrypt(d.variant.HashFunc(), passwordBytes, d.salt, d.iterations)) == 1, nil
-}
-
-// Encode this Digest as a string for storage.
-func (d *Digest) Encode() (hash string) {
- switch d.iterations {
- case IterationsDefaultOmitted:
- return strings.ReplaceAll(fmt.Sprintf(EncodingFmtRoundsOmitted,
- d.variant.Prefix(),
- d.salt, d.key,
- ), "\n", "")
- default:
- return strings.ReplaceAll(fmt.Sprintf(EncodingFmt,
- d.variant.Prefix(), d.iterations,
- d.salt, d.key,
- ), "\n", "")
- }
-}
-
-// String returns the storable format of the shacrypt.Digest hash utilizing fmt.Sprintf and shacrypt.EncodingFmt.
-func (d *Digest) String() string {
- return d.Encode()
-}
-
-func (d *Digest) defaults() {
- switch d.variant {
- case VariantSHA256, VariantSHA512:
- break
- default:
- d.variant = variantDefault
- }
-
- if d.iterations == 0 {
- d.iterations = d.variant.DefaultIterations()
- }
-}
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/doc.go b/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/doc.go
deleted file mode 100644
index 4ac7c8e691..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/doc.go
+++ /dev/null
@@ -1,7 +0,0 @@
-// Package shacrypt provides helpful abstractions for an implementation of SHA-crypt and implements
-// github.com/go-crypt/crypt interfaces.
-//
-// See https://www.akkadia.org/drepper/SHA-crypt.html for specification details.
-//
-// This implementation is loaded by crypt.NewDefaultDecoder and crypt.NewDecoderAll.
-package shacrypt
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/hasher.go b/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/hasher.go
deleted file mode 100644
index 6b8f9d11de..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/hasher.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package shacrypt
-
-import (
- "fmt"
-
- xcrypt "github.com/go-crypt/x/crypt"
-
- "github.com/go-crypt/crypt/algorithm"
- "github.com/go-crypt/crypt/internal/random"
-)
-
-// New returns a *Hasher without any settings configured. This d to a SHA512 hash.Hash
-// with 1000000 iterations. These settings can be overridden with the methods with the With prefix.
-func New(opts ...Opt) (hasher *Hasher, err error) {
- hasher = &Hasher{}
-
- if err = hasher.WithOptions(opts...); err != nil {
- return nil, err
- }
-
- if err = hasher.Validate(); err != nil {
- return nil, err
- }
-
- return hasher, nil
-}
-
-// Hasher is a algorithm.Hash for SHA-crypt which can be initialized via shacrypt.New using a functional options pattern.
-type Hasher struct {
- variant Variant
-
- iterations, bytesSalt int
-
- d bool
-}
-
-// NewSHA256 returns a *Hasher with the SHA256 hash.Hash which d to 1000000 iterations. These
-// settings can be overridden with the methods with the With prefix.
-func NewSHA256() (hasher *Hasher, err error) {
- return New(
- WithVariant(VariantSHA256),
- WithIterations(VariantSHA256.DefaultIterations()),
- )
-}
-
-// NewSHA512 returns a *Hasher with the SHA512 hash.Hash which d to 1000000 iterations. These
-// settings can be overridden with the methods with the With prefix.
-func NewSHA512() (hasher *Hasher, err error) {
- return New(
- WithVariant(VariantSHA512),
- WithIterations(VariantSHA512.DefaultIterations()),
- )
-}
-
-// WithOptions defines the options for this scrypt.Hasher.
-func (h *Hasher) WithOptions(opts ...Opt) (err error) {
- for _, opt := range opts {
- if err = opt(h); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// Hash performs the hashing operation and returns either a shacrypt.Digest as a algorithm.Digest or an error.
-func (h *Hasher) Hash(password string) (digest algorithm.Digest, err error) {
- h.defaults()
-
- if digest, err = h.hash(password); err != nil {
- return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err)
- }
-
- return digest, nil
-}
-
-func (h *Hasher) hash(password string) (digest algorithm.Digest, err error) {
- var salt []byte
-
- if salt, err = random.CharSetBytes(h.bytesSalt, SaltCharSet); err != nil {
- return nil, fmt.Errorf("%w: %v", algorithm.ErrSaltReadRandomBytes, err)
- }
-
- return h.hashWithSalt(password, salt)
-}
-
-// HashWithSalt overloads the Hash method allowing the user to provide a salt. It's recommended instead to configure the
-// salt size and let this be a random value generated using crypto/rand.
-func (h *Hasher) HashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) {
- h.defaults()
-
- if digest, err = h.hashWithSalt(password, salt); err != nil {
- return nil, fmt.Errorf(algorithm.ErrFmtHasherHash, AlgName, err)
- }
-
- return digest, nil
-}
-
-func (h *Hasher) hashWithSalt(password string, salt []byte) (digest algorithm.Digest, err error) {
- if s := len(salt); s > SaltLengthMax || s < SaltLengthMin {
- return nil, fmt.Errorf("%w: salt bytes must have a length of between %d and %d but has a length of %d", algorithm.ErrSaltInvalid, SaltLengthMin, SaltLengthMax, len(salt))
- }
-
- d := &Digest{
- variant: h.variant,
- iterations: h.iterations,
- salt: salt,
- }
-
- d.defaults()
-
- d.key = xcrypt.KeySHACrypt(d.variant.HashFunc(), []byte(password), d.salt, d.iterations)
-
- return d, nil
-}
-
-// MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this option to
-// utilize the Validate method first or handle the panic appropriately.
-func (h *Hasher) MustHash(password string) (digest algorithm.Digest) {
- var err error
-
- if digest, err = h.Hash(password); err != nil {
- panic(err)
- }
-
- return digest
-}
-
-// Validate checks the settings/parameters for this shacrypt.Hasher and returns an error.
-func (h *Hasher) Validate() (err error) {
- h.defaults()
-
- if err = h.validate(); err != nil {
- return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, err)
- }
-
- return nil
-}
-
-func (h *Hasher) validate() (err error) {
- h.defaults()
-
- return nil
-}
-
-func (h *Hasher) defaults() {
- if h.d {
- return
- }
-
- h.d = true
-
- if h.bytesSalt < SaltLengthMin {
- h.bytesSalt = algorithm.SaltLengthDefault
- }
-}
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/opts.go b/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/opts.go
deleted file mode 100644
index 3499882be5..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/opts.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package shacrypt
-
-import (
- "fmt"
-
- "github.com/go-crypt/crypt/algorithm"
-)
-
-// Opt describes the functional option pattern for the shacrypt.Hasher.
-type Opt func(h *Hasher) (err error)
-
-// WithVariant configures the shacrypt.Variant of the resulting shacrypt.Digest.
-// Default is shacrypt.VariantSHA512.
-func WithVariant(variant Variant) Opt {
- return func(h *Hasher) (err error) {
- switch variant {
- case VariantNone:
- return nil
- case VariantSHA256, VariantSHA512:
- h.variant = variant
-
- return nil
- default:
- return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant '%d' is invalid", algorithm.ErrParameterInvalid, variant))
- }
- }
-}
-
-// WithVariantName uses the variant name or identifier to configure the shacrypt.Variant of the resulting shacrypt.Digest.
-// Default is shacrypt.VariantSHA512.
-func WithVariantName(identifier string) Opt {
- return func(h *Hasher) (err error) {
- if identifier == "" {
- return nil
- }
-
- variant := NewVariant(identifier)
-
- if variant == VariantNone {
- return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf("%w: variant identifier '%s' is invalid", algorithm.ErrParameterInvalid, identifier))
- }
-
- h.variant = variant
-
- return nil
- }
-}
-
-// WithSHA256 adjusts this Hasher to utilize the SHA256 hash.Hash.
-func WithSHA256() Opt {
- return func(h *Hasher) (err error) {
- h.variant = VariantSHA256
-
- return nil
- }
-}
-
-// WithSHA512 adjusts this Hasher to utilize the SHA512 hash.Hash.
-func WithSHA512() Opt {
- return func(h *Hasher) (err error) {
- h.variant = VariantSHA512
-
- return nil
- }
-}
-
-// WithIterations sets the iterations parameter of the resulting shacrypt.Digest.
-// Minimum 1000, Maximum 999999999. Default is 1000000.
-func WithIterations(iterations int) Opt {
- return func(h *Hasher) (err error) {
- if iterations < IterationsMin || iterations > IterationsMax {
- return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "iterations", IterationsMin, "", IterationsMax, iterations))
- }
-
- h.iterations = iterations
-
- return nil
- }
-}
-
-// WithRounds is an alias for shacrypt.WithIterations.
-func WithRounds(rounds int) Opt {
- return WithIterations(rounds)
-}
-
-// WithSaltLength adjusts the salt size (in bytes) of the resulting shacrypt.Digest.
-// Minimum 1, Maximum 16. Default is 16.
-func WithSaltLength(bytes int) Opt {
- return func(h *Hasher) (err error) {
- if bytes < SaltLengthMin || bytes > SaltLengthMax {
- return fmt.Errorf(algorithm.ErrFmtHasherValidation, AlgName, fmt.Errorf(algorithm.ErrFmtInvalidIntParameter, algorithm.ErrParameterInvalid, "salt length", SaltLengthMin, "", SaltLengthMax, bytes))
- }
-
- h.bytesSalt = bytes
-
- return nil
- }
-}
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/variant.go b/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/variant.go
deleted file mode 100644
index 681ddc4ecd..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/shacrypt/variant.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package shacrypt
-
-import (
- "crypto/sha256"
- "crypto/sha512"
-
- "github.com/go-crypt/crypt/algorithm"
-)
-
-// NewVariant converts an identifier string to a shacrypt.Variant.
-func NewVariant(identifier string) Variant {
- switch identifier {
- case AlgIdentifierSHA256, algorithm.DigestSHA256:
- return VariantSHA256
- case AlgIdentifierSHA512, algorithm.DigestSHA512:
- return VariantSHA512
- default:
- return VariantSHA512
- }
-}
-
-// Variant is a variant of the shacrypt.Digest.
-type Variant int
-
-const (
- // VariantNone is a variant of the shacrypt.Digest which is unknown.
- VariantNone Variant = iota
-
- // VariantSHA256 is a variant of the shacrypt.Digest which uses SHA-256.
- VariantSHA256
-
- // VariantSHA512 is a variant of the shacrypt.Digest which uses SHA-512.
- VariantSHA512
-)
-
-// String implements the fmt.Stringer returning a string representation of the shacrypt.Variant.
-func (v Variant) String() (identifier string) {
- switch v {
- case VariantSHA256:
- return algorithm.DigestSHA256
- case VariantSHA512:
- return algorithm.DigestSHA512
- default:
- return
- }
-}
-
-// Prefix returns the shacrypt.Variant prefix identifier.
-func (v Variant) Prefix() (prefix string) {
- switch v {
- case VariantSHA256:
- return AlgIdentifierSHA256
- case VariantSHA512:
- return AlgIdentifierSHA512
- default:
- return AlgIdentifierSHA512
- }
-}
-
-// Name returns the Variant name.
-func (v Variant) Name() (s string) {
- switch v {
- case VariantSHA256:
- return algorithm.DigestSHA256
- case VariantSHA512:
- return algorithm.DigestSHA512
- default:
- return algorithm.DigestSHA512
- }
-}
-
-// HashFunc returns the internal HMAC HashFunc.
-func (v Variant) HashFunc() algorithm.HashFunc {
- switch v {
- case VariantSHA256:
- return sha256.New
- case VariantSHA512:
- return sha512.New
- default:
- return sha512.New
- }
-}
-
-// DefaultIterations returns the default iterations for the particular variant.
-func (v Variant) DefaultIterations() int {
- switch v {
- case VariantSHA512:
- return IterationsDefaultSHA512
- default:
- return IterationsDefaultSHA256
- }
-}
diff --git a/vendor/github.com/go-crypt/crypt/algorithm/types.go b/vendor/github.com/go-crypt/crypt/algorithm/types.go
deleted file mode 100644
index 99fcb583ab..0000000000
--- a/vendor/github.com/go-crypt/crypt/algorithm/types.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package algorithm
-
-import (
- "fmt"
- "hash"
-)
-
-// Hash is an interface which implements password hashing.
-type Hash interface {
- // Validate checks the hasher configuration to ensure it's valid. This should be used when the Hash is going to be
- // reused and you should use it in conjunction with MustHash.
- Validate() (err error)
-
- // Hash performs the hashing operation on a password and resets any relevant parameters such as a manually set salt.
- // It then returns a Digest and error.
- Hash(password string) (hashed Digest, err error)
-
- // HashWithSalt is an overload of Digest that also accepts a salt.
- HashWithSalt(password string, salt []byte) (hashed Digest, err error)
-
- // MustHash overloads the Hash method and panics if the error is not nil. It's recommended if you use this method to
- // utilize the Validate method first or handle the panic appropriately.
- MustHash(password string) (hashed Digest)
-}
-
-// Matcher is an interface used to match passwords.
-type Matcher interface {
- Match(password string) (match bool)
- MatchBytes(passwordBytes []byte) (match bool)
- MatchAdvanced(password string) (match bool, err error)
- MatchBytesAdvanced(passwordBytes []byte) (match bool, err error)
-}
-
-// Digest represents a hashed password. It's implemented by all hashed password results so that when we pass a
-// stored hash into its relevant type we can verify the password against the hash.
-type Digest interface {
- fmt.Stringer
-
- Matcher
-
- Encode() (hash string)
-}
-
-// DecodeFunc describes a function to decode an encoded digest into a algorithm.Digest.
-type DecodeFunc func(encodedDigest string) (digest Digest, err error)
-
-// DecoderRegister describes an implementation that allows registering DecodeFunc's.
-type DecoderRegister interface {
- RegisterDecodeFunc(prefix string, decoder DecodeFunc) (err error)
- RegisterDecodePrefix(prefix, identifier string) (err error)
-
- Decoder
-}
-
-// Decoder is a representation of a implementation that performs generic decoding. Currently this is just intended for
-// use by implementers.
-type Decoder interface {
- Decode(encodedDigest string) (digest Digest, err error)
-}
-
-// HashFunc is a function which returns a hash.Hash.
-type HashFunc func() hash.Hash
diff --git a/vendor/github.com/go-crypt/crypt/internal/encoding/base64adapted.go b/vendor/github.com/go-crypt/crypt/internal/encoding/base64adapted.go
deleted file mode 100644
index 55b84c1154..0000000000
--- a/vendor/github.com/go-crypt/crypt/internal/encoding/base64adapted.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package encoding
-
-import (
- "encoding/base64"
-)
-
-const (
- encodeBase64Adapted = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./"
-)
-
-var (
- // Base64RawAdaptedEncoding is the adapted encoding for crypt purposes without padding.
- Base64RawAdaptedEncoding = base64.NewEncoding(encodeBase64Adapted).WithPadding(base64.NoPadding)
-)
diff --git a/vendor/github.com/go-crypt/crypt/internal/encoding/const.go b/vendor/github.com/go-crypt/crypt/internal/encoding/const.go
deleted file mode 100644
index b0a3d56d3e..0000000000
--- a/vendor/github.com/go-crypt/crypt/internal/encoding/const.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package encoding
-
-const (
- // Delimiter rune for all encodings.
- Delimiter = rune('$')
-
- // DelimiterStr is the string variation of Delimiter.
- DelimiterStr = string(Delimiter)
-)
diff --git a/vendor/github.com/go-crypt/crypt/internal/encoding/digest.go b/vendor/github.com/go-crypt/crypt/internal/encoding/digest.go
deleted file mode 100644
index f854968abe..0000000000
--- a/vendor/github.com/go-crypt/crypt/internal/encoding/digest.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package encoding
-
-import (
- "strings"
-)
-
-// Split an encoded digest by the encoding.Delimiter.
-func Split(encodedDigest string, n int) (parts []string) {
- return strings.SplitN(encodedDigest, DelimiterStr, n)
-}
diff --git a/vendor/github.com/go-crypt/crypt/internal/encoding/doc.go b/vendor/github.com/go-crypt/crypt/internal/encoding/doc.go
deleted file mode 100644
index 30a4f0be5a..0000000000
--- a/vendor/github.com/go-crypt/crypt/internal/encoding/doc.go
+++ /dev/null
@@ -1,2 +0,0 @@
-// Package encoding is an internal encoding helper package.
-package encoding
diff --git a/vendor/github.com/go-crypt/crypt/internal/encoding/parameters.go b/vendor/github.com/go-crypt/crypt/internal/encoding/parameters.go
deleted file mode 100644
index b7935be9af..0000000000
--- a/vendor/github.com/go-crypt/crypt/internal/encoding/parameters.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package encoding
-
-import (
- "fmt"
- "strconv"
- "strings"
-)
-
-// Parameter is a key value pair.
-type Parameter struct {
- Key string
- Value string
-}
-
-// Int converts the Value to an int using strconv.Atoi.
-func (p Parameter) Int() (int, error) {
- return strconv.Atoi(p.Value)
-}
-
-const (
- // ParameterDefaultItemSeparator is the default item separator.
- ParameterDefaultItemSeparator = ","
-
- // ParameterDefaultKeyValueSeparator is the default key value separator.
- ParameterDefaultKeyValueSeparator = "="
-)
-
-// DecodeParameterStr is an alias for DecodeParameterStrAdvanced using item separator and key value separator
-// of ',' and '=' respectively.
-func DecodeParameterStr(input string) (opts []Parameter, err error) {
- return DecodeParameterStrAdvanced(input, ParameterDefaultItemSeparator, ParameterDefaultKeyValueSeparator)
-}
-
-// DecodeParameterStrAdvanced decodes parameter strings into a []Parameter where sepItem separates each parameter, and sepKV separates the key and value.
-func DecodeParameterStrAdvanced(input string, sepItem, sepKV string) (opts []Parameter, err error) {
- if input == "" {
- return nil, fmt.Errorf("empty strings can't be decoded to parameters")
- }
-
- o := strings.Split(input, sepItem)
-
- opts = make([]Parameter, len(o))
-
- for i, joined := range o {
- kv := strings.SplitN(joined, sepKV, 2)
- if len(kv) != 2 {
- return nil, fmt.Errorf("parameter pair '%s' is not properly encoded: does not contain kv separator '%s'", joined, sepKV)
- }
-
- opts[i] = Parameter{Key: kv[0], Value: kv[1]}
- }
-
- return opts, nil
-}
diff --git a/vendor/github.com/go-crypt/crypt/internal/random/bytes.go b/vendor/github.com/go-crypt/crypt/internal/random/bytes.go
deleted file mode 100644
index 20bc235354..0000000000
--- a/vendor/github.com/go-crypt/crypt/internal/random/bytes.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package random
-
-import (
- "crypto/rand"
- "io"
-)
-
-// Bytes returns random arbitrary bytes with a length of n.
-func Bytes(n int) (bytes []byte, err error) {
- bytes = make([]byte, n)
-
- if _, err = io.ReadFull(rand.Reader, bytes); err != nil {
- return nil, err
- }
-
- return bytes, nil
-}
-
-// CharSetBytes returns random bytes with a length of n from the characters in the charset.
-func CharSetBytes(n int, charset string) (bytes []byte, err error) {
- bytes = make([]byte, n)
-
- if _, err = rand.Read(bytes); err != nil {
- return nil, err
- }
-
- for i, b := range bytes {
- bytes[i] = charset[b%byte(len(charset))]
- }
-
- return bytes, nil
-}
diff --git a/vendor/github.com/go-crypt/crypt/internal/random/doc.go b/vendor/github.com/go-crypt/crypt/internal/random/doc.go
deleted file mode 100644
index 2bf9f24d31..0000000000
--- a/vendor/github.com/go-crypt/crypt/internal/random/doc.go
+++ /dev/null
@@ -1,2 +0,0 @@
-// Package random is an internal helper package.
-package random
diff --git a/vendor/github.com/go-crypt/x/LICENSE b/vendor/github.com/go-crypt/x/LICENSE
deleted file mode 100644
index 6a66aea5ea..0000000000
--- a/vendor/github.com/go-crypt/x/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright (c) 2009 The Go Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
- * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/go-crypt/x/base64/base64.go b/vendor/github.com/go-crypt/x/base64/base64.go
deleted file mode 100644
index 2cb382a793..0000000000
--- a/vendor/github.com/go-crypt/x/base64/base64.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package base64
-
-import (
- "encoding/base64"
-)
-
-var AdaptedEncoding = base64.NewEncoding(encodeAdapted)
-
-// BcryptEncoding is the Bcrypt Base64 Alternative encoding.
-var BcryptEncoding = base64.NewEncoding(bcryptB64Alphabet)
-
-// EncodeCrypt implements the linux crypt lib's B64 encoding.
-func EncodeCrypt(src []byte) (dst []byte) {
- if len(src) == 0 {
- return nil
- }
-
- dst = make([]byte, (len(src)*8+5)/6)
-
- idst, isrc := 0, 0
-
- for isrc < len(src)/3*3 {
- v := uint(src[isrc+2])<<16 | uint(src[isrc+1])<<8 | uint(src[isrc])
- dst[idst+0] = cryptB64Alphabet[v&0x3f]
- dst[idst+1] = cryptB64Alphabet[v>>6&0x3f]
- dst[idst+2] = cryptB64Alphabet[v>>12&0x3f]
- dst[idst+3] = cryptB64Alphabet[v>>18]
- idst += 4
- isrc += 3
- }
-
- remainder := len(src) - isrc
-
- if remainder == 0 {
- return dst
- }
-
- v := uint(src[isrc+0])
- if remainder == 2 {
- v |= uint(src[isrc+1]) << 8
- }
-
- dst[idst+0] = cryptB64Alphabet[v&0x3f]
- dst[idst+1] = cryptB64Alphabet[v>>6&0x3f]
-
- if remainder == 2 {
- dst[idst+2] = cryptB64Alphabet[v>>12]
- }
-
- return dst
-}
diff --git a/vendor/github.com/go-crypt/x/base64/const.go b/vendor/github.com/go-crypt/x/base64/const.go
deleted file mode 100644
index 2e99236872..0000000000
--- a/vendor/github.com/go-crypt/x/base64/const.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package base64
-
-const (
- cryptB64Alphabet = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
- bcryptB64Alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
- encodeAdapted = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./"
-)
diff --git a/vendor/github.com/go-crypt/x/crypt/const.go b/vendor/github.com/go-crypt/x/crypt/const.go
deleted file mode 100644
index 09153bfb7f..0000000000
--- a/vendor/github.com/go-crypt/x/crypt/const.go
+++ /dev/null
@@ -1,223 +0,0 @@
-package crypt
-
-var permuteTableMD5Crypt = [16]byte{
- 12, 6, 0,
- 13, 7, 1,
- 14, 8, 2,
- 15, 9, 3,
- 5, 10, 4,
- 11,
-}
-
-var permuteTableSHA1Crypt = [21]byte{
- 2, 1, 0,
- 5, 4, 3,
- 8, 7, 6,
- 11, 10, 9,
- 14, 13, 12,
- 17, 16, 15,
- 0, 19, 18,
-}
-
-var permuteTableSHACryptSHA256 = [32]byte{
- 20, 10, 0,
- 11, 1, 21,
- 2, 22, 12,
- 23, 13, 3,
- 14, 4, 24,
- 5, 25, 15,
- 26, 16, 6,
- 17, 7, 27,
- 8, 28, 18,
- 29, 19, 9,
- 30, 31,
-}
-
-var permuteTableSHACryptSHA512 = [64]byte{
- 42, 21, 0,
- 1, 43, 22,
- 23, 2, 44,
- 45, 24, 3,
- 4, 46, 25,
- 26, 5, 47,
- 48, 27, 6,
- 7, 49, 28,
- 29, 8, 50,
- 51, 30, 9,
- 10, 52, 31,
- 32, 11, 53,
- 54, 33, 12,
- 13, 55, 34,
- 35, 14, 56,
- 57, 36, 15,
- 16, 58, 37,
- 38, 17, 59,
- 60, 39, 18,
- 19, 61, 40,
- 41, 20, 62,
- 63,
-}
-
-// The following is the 1517 bytes of Hamlet III.ii which is public domain. This is used by Sun's MD5 Crypt function.
-var magicTableMD5CryptSunHamlet = [1517]byte{
- 84, 111, 32, 98, 101, 44, 32, 111, 114, 32, 110,
- 111, 116, 32, 116, 111, 32, 98, 101, 44, 45,
- 45, 116, 104, 97, 116, 32, 105, 115, 32, 116,
- 104, 101, 32, 113, 117, 101, 115, 116, 105, 111,
- 110, 58, 45, 45, 10, 87, 104, 101, 116, 104,
- 101, 114, 32, 39, 116, 105, 115, 32, 110, 111,
- 98, 108, 101, 114, 32, 105, 110, 32, 116, 104,
- 101, 32, 109, 105, 110, 100, 32, 116, 111, 32,
- 115, 117, 102, 102, 101, 114, 10, 84, 104, 101,
- 32, 115, 108, 105, 110, 103, 115, 32, 97, 110,
- 100, 32, 97, 114, 114, 111, 119, 115, 32, 111,
- 102, 32, 111, 117, 116, 114, 97, 103, 101, 111,
- 117, 115, 32, 102, 111, 114, 116, 117, 110, 101,
- 10, 79, 114, 32, 116, 111, 32, 116, 97, 107,
- 101, 32, 97, 114, 109, 115, 32, 97, 103, 97,
- 105, 110, 115, 116, 32, 97, 32, 115, 101, 97,
- 32, 111, 102, 32, 116, 114, 111, 117, 98, 108,
- 101, 115, 44, 10, 65, 110, 100, 32, 98, 121,
- 32, 111, 112, 112, 111, 115, 105, 110, 103, 32,
- 101, 110, 100, 32, 116, 104, 101, 109, 63, 45,
- 45, 84, 111, 32, 100, 105, 101, 44, 45, 45,
- 116, 111, 32, 115, 108, 101, 101, 112, 44, 45,
- 45, 10, 78, 111, 32, 109, 111, 114, 101, 59,
- 32, 97, 110, 100, 32, 98, 121, 32, 97, 32,
- 115, 108, 101, 101, 112, 32, 116, 111, 32, 115,
- 97, 121, 32, 119, 101, 32, 101, 110, 100, 10,
- 84, 104, 101, 32, 104, 101, 97, 114, 116, 97,
- 99, 104, 101, 44, 32, 97, 110, 100, 32, 116,
- 104, 101, 32, 116, 104, 111, 117, 115, 97, 110,
- 100, 32, 110, 97, 116, 117, 114, 97, 108, 32,
- 115, 104, 111, 99, 107, 115, 10, 84, 104, 97,
- 116, 32, 102, 108, 101, 115, 104, 32, 105, 115,
- 32, 104, 101, 105, 114, 32, 116, 111, 44, 45,
- 45, 39, 116, 105, 115, 32, 97, 32, 99, 111,
- 110, 115, 117, 109, 109, 97, 116, 105, 111, 110,
- 10, 68, 101, 118, 111, 117, 116, 108, 121, 32,
- 116, 111, 32, 98, 101, 32, 119, 105, 115, 104,
- 39, 100, 46, 32, 84, 111, 32, 100, 105, 101,
- 44, 45, 45, 116, 111, 32, 115, 108, 101, 101,
- 112, 59, 45, 45, 10, 84, 111, 32, 115, 108,
- 101, 101, 112, 33, 32, 112, 101, 114, 99, 104,
- 97, 110, 99, 101, 32, 116, 111, 32, 100, 114,
- 101, 97, 109, 58, 45, 45, 97, 121, 44, 32,
- 116, 104, 101, 114, 101, 39, 115, 32, 116, 104,
- 101, 32, 114, 117, 98, 59, 10, 70, 111, 114,
- 32, 105, 110, 32, 116, 104, 97, 116, 32, 115,
- 108, 101, 101, 112, 32, 111, 102, 32, 100, 101,
- 97, 116, 104, 32, 119, 104, 97, 116, 32, 100,
- 114, 101, 97, 109, 115, 32, 109, 97, 121, 32,
- 99, 111, 109, 101, 44, 10, 87, 104, 101, 110,
- 32, 119, 101, 32, 104, 97, 118, 101, 32, 115,
- 104, 117, 102, 102, 108, 101, 100, 32, 111, 102,
- 102, 32, 116, 104, 105, 115, 32, 109, 111, 114,
- 116, 97, 108, 32, 99, 111, 105, 108, 44, 10,
- 77, 117, 115, 116, 32, 103, 105, 118, 101, 32,
- 117, 115, 32, 112, 97, 117, 115, 101, 58, 32,
- 116, 104, 101, 114, 101, 39, 115, 32, 116, 104,
- 101, 32, 114, 101, 115, 112, 101, 99, 116, 10,
- 84, 104, 97, 116, 32, 109, 97, 107, 101, 115,
- 32, 99, 97, 108, 97, 109, 105, 116, 121, 32,
- 111, 102, 32, 115, 111, 32, 108, 111, 110, 103,
- 32, 108, 105, 102, 101, 59, 10, 70, 111, 114,
- 32, 119, 104, 111, 32, 119, 111, 117, 108, 100,
- 32, 98, 101, 97, 114, 32, 116, 104, 101, 32,
- 119, 104, 105, 112, 115, 32, 97, 110, 100, 32,
- 115, 99, 111, 114, 110, 115, 32, 111, 102, 32,
- 116, 105, 109, 101, 44, 10, 84, 104, 101, 32,
- 111, 112, 112, 114, 101, 115, 115, 111, 114, 39,
- 115, 32, 119, 114, 111, 110, 103, 44, 32, 116,
- 104, 101, 32, 112, 114, 111, 117, 100, 32, 109,
- 97, 110, 39, 115, 32, 99, 111, 110, 116, 117,
- 109, 101, 108, 121, 44, 10, 84, 104, 101, 32,
- 112, 97, 110, 103, 115, 32, 111, 102, 32, 100,
- 101, 115, 112, 105, 115, 39, 100, 32, 108, 111,
- 118, 101, 44, 32, 116, 104, 101, 32, 108, 97,
- 119, 39, 115, 32, 100, 101, 108, 97, 121, 44,
- 10, 84, 104, 101, 32, 105, 110, 115, 111, 108,
- 101, 110, 99, 101, 32, 111, 102, 32, 111, 102,
- 102, 105, 99, 101, 44, 32, 97, 110, 100, 32,
- 116, 104, 101, 32, 115, 112, 117, 114, 110, 115,
- 10, 84, 104, 97, 116, 32, 112, 97, 116, 105,
- 101, 110, 116, 32, 109, 101, 114, 105, 116, 32,
- 111, 102, 32, 116, 104, 101, 32, 117, 110, 119,
- 111, 114, 116, 104, 121, 32, 116, 97, 107, 101,
- 115, 44, 10, 87, 104, 101, 110, 32, 104, 101,
- 32, 104, 105, 109, 115, 101, 108, 102, 32, 109,
- 105, 103, 104, 116, 32, 104, 105, 115, 32, 113,
- 117, 105, 101, 116, 117, 115, 32, 109, 97, 107,
- 101, 10, 87, 105, 116, 104, 32, 97, 32, 98,
- 97, 114, 101, 32, 98, 111, 100, 107, 105, 110,
- 63, 32, 119, 104, 111, 32, 119, 111, 117, 108,
- 100, 32, 116, 104, 101, 115, 101, 32, 102, 97,
- 114, 100, 101, 108, 115, 32, 98, 101, 97, 114,
- 44, 10, 84, 111, 32, 103, 114, 117, 110, 116,
- 32, 97, 110, 100, 32, 115, 119, 101, 97, 116,
- 32, 117, 110, 100, 101, 114, 32, 97, 32, 119,
- 101, 97, 114, 121, 32, 108, 105, 102, 101, 44,
- 10, 66, 117, 116, 32, 116, 104, 97, 116, 32,
- 116, 104, 101, 32, 100, 114, 101, 97, 100, 32,
- 111, 102, 32, 115, 111, 109, 101, 116, 104, 105,
- 110, 103, 32, 97, 102, 116, 101, 114, 32, 100,
- 101, 97, 116, 104, 44, 45, 45, 10, 84, 104,
- 101, 32, 117, 110, 100, 105, 115, 99, 111, 118,
- 101, 114, 39, 100, 32, 99, 111, 117, 110, 116,
- 114, 121, 44, 32, 102, 114, 111, 109, 32, 119,
- 104, 111, 115, 101, 32, 98, 111, 117, 114, 110,
- 10, 78, 111, 32, 116, 114, 97, 118, 101, 108,
- 108, 101, 114, 32, 114, 101, 116, 117, 114, 110,
- 115, 44, 45, 45, 112, 117, 122, 122, 108, 101,
- 115, 32, 116, 104, 101, 32, 119, 105, 108, 108,
- 44, 10, 65, 110, 100, 32, 109, 97, 107, 101,
- 115, 32, 117, 115, 32, 114, 97, 116, 104, 101,
- 114, 32, 98, 101, 97, 114, 32, 116, 104, 111,
- 115, 101, 32, 105, 108, 108, 115, 32, 119, 101,
- 32, 104, 97, 118, 101, 10, 84, 104, 97, 110,
- 32, 102, 108, 121, 32, 116, 111, 32, 111, 116,
- 104, 101, 114, 115, 32, 116, 104, 97, 116, 32,
- 119, 101, 32, 107, 110, 111, 119, 32, 110, 111,
- 116, 32, 111, 102, 63, 10, 84, 104, 117, 115,
- 32, 99, 111, 110, 115, 99, 105, 101, 110, 99,
- 101, 32, 100, 111, 101, 115, 32, 109, 97, 107,
- 101, 32, 99, 111, 119, 97, 114, 100, 115, 32,
- 111, 102, 32, 117, 115, 32, 97, 108, 108, 59,
- 10, 65, 110, 100, 32, 116, 104, 117, 115, 32,
- 116, 104, 101, 32, 110, 97, 116, 105, 118, 101,
- 32, 104, 117, 101, 32, 111, 102, 32, 114, 101,
- 115, 111, 108, 117, 116, 105, 111, 110, 10, 73,
- 115, 32, 115, 105, 99, 107, 108, 105, 101, 100,
- 32, 111, 39, 101, 114, 32, 119, 105, 116, 104,
- 32, 116, 104, 101, 32, 112, 97, 108, 101, 32,
- 99, 97, 115, 116, 32, 111, 102, 32, 116, 104,
- 111, 117, 103, 104, 116, 59, 10, 65, 110, 100,
- 32, 101, 110, 116, 101, 114, 112, 114, 105, 115,
- 101, 115, 32, 111, 102, 32, 103, 114, 101, 97,
- 116, 32, 112, 105, 116, 104, 32, 97, 110, 100,
- 32, 109, 111, 109, 101, 110, 116, 44, 10, 87,
- 105, 116, 104, 32, 116, 104, 105, 115, 32, 114,
- 101, 103, 97, 114, 100, 44, 32, 116, 104, 101,
- 105, 114, 32, 99, 117, 114, 114, 101, 110, 116,
- 115, 32, 116, 117, 114, 110, 32, 97, 119, 114,
- 121, 44, 10, 65, 110, 100, 32, 108, 111, 115,
- 101, 32, 116, 104, 101, 32, 110, 97, 109, 101,
- 32, 111, 102, 32, 97, 99, 116, 105, 111, 110,
- 46, 45, 45, 83, 111, 102, 116, 32, 121, 111,
- 117, 32, 110, 111, 119, 33, 10, 84, 104, 101,
- 32, 102, 97, 105, 114, 32, 79, 112, 104, 101,
- 108, 105, 97, 33, 45, 45, 78, 121, 109, 112,
- 104, 44, 32, 105, 110, 32, 116, 104, 121, 32,
- 111, 114, 105, 115, 111, 110, 115, 10, 66, 101,
- 32, 97, 108, 108, 32, 109, 121, 32, 115, 105,
- 110, 115, 32, 114, 101, 109, 101, 109, 98, 101,
- 114, 39, 100, 46, 10, 0,
-}
-
-var (
- prefixMD5Crypt = []byte("$1$")
- prefixSHA1Crypt = []byte("$sha1$")
- prefixSunMD5Crypt = []byte("$md5$")
- prefixSunMD5CryptRounds = []byte("$md5,rounds=")
- sepCrypt = []byte("$")
-)
diff --git a/vendor/github.com/go-crypt/x/crypt/crypt.go b/vendor/github.com/go-crypt/x/crypt/crypt.go
deleted file mode 100644
index 3bd2cdb130..0000000000
--- a/vendor/github.com/go-crypt/x/crypt/crypt.go
+++ /dev/null
@@ -1,300 +0,0 @@
-package crypt
-
-import (
- "crypto/hmac"
- "crypto/md5"
- "crypto/sha1"
- "crypto/sha256"
- "crypto/sha512"
- "hash"
- "strconv"
-)
-
-// KeySHACrypt calculates the shacrypt SHA256/SHA512 key given an appropriate hash.Hash, password, salt, and number of rounds.
-func KeySHACrypt(hashFunc func() hash.Hash, password, salt []byte, rounds int) []byte {
- // Step 1.
- digest := hashFunc()
-
- size := digest.Size()
-
- switch size {
- case sha1.Size:
- return KeySHA1Crypt(password, salt, uint32(rounds))
- case sha256.Size, sha512.Size:
- break
- default:
- return nil
- }
-
- length := len(password)
-
- // Step 2.
- digest.Write(password)
-
- // Step 3.
- digest.Write(salt)
-
- // Step 4.
- digestB := hashFunc()
-
- // Step 5.
- digestB.Write(password)
-
- // Step 6.
- digestB.Write(salt)
-
- // Step 7.
- digestB.Write(password)
-
- // Step 8.
- sumB := digestB.Sum(nil)
- digestB.Reset()
- digestB = nil
-
- // Step 9 and 10:
- digest.Write(repeat(sumB, length))
-
- // Step 11.
- for i := length; i > 0; i >>= 1 {
- if even(i) {
- digest.Write(password)
- } else {
- digest.Write(sumB)
- }
- }
-
- clean(sumB)
- sumB = nil
-
- // Step 12.
- sumA := digest.Sum(nil)
- digest.Reset()
-
- // Step 13-14.
- for i := 0; i < length; i++ {
- digest.Write(password)
- }
-
- // Step 15.
- sumDP := digest.Sum(nil)
- digest.Reset()
-
- // Step 16.
- seqP := repeat(sumDP, length)
- sumDP = nil
-
- // Step 17-18.
- for i := 0; i < 16+int(sumA[0]); i++ {
- digest.Write(salt)
- }
-
- // Step 19.
- sumDS := digest.Sum(nil)
- digest.Reset()
-
- // Step 20.
- seqS := repeat(sumDS, len(salt))
-
- // Step 21.
- for i := 0; i < rounds; i++ {
- digest.Reset()
-
- // Step 21 Sub-Step B and C.
- if i&1 != 0 {
- // Step 21 Sub-Step B.
- digest.Write(seqP)
- } else {
- // Step 21 Sub-Step C.
- digest.Write(sumA)
- }
-
- // Step 21 Sub-Step D.
- if i%3 != 0 {
- digest.Write(seqS)
- }
-
- // Step 21 Sub-Step E.
- if i%7 != 0 {
- digest.Write(seqP)
- }
-
- // Step 21 Sub-Step F and G.
- if i&1 != 0 {
- // Step 21 Sub-Step F.
- digest.Write(sumA)
- } else {
- // Step 21 Sub-Step G.
- digest.Write(seqP)
- }
-
- // Sub-Step H.
- copy(sumA, digest.Sum(nil))
- }
-
- digest.Reset()
- digest = nil
-
- seqP, seqS = nil, nil
-
- switch size {
- case sha256.Size:
- // Step 22 Sub Step E.
- return permute(sumA, permuteTableSHACryptSHA256[:])
- case sha512.Size:
- // Step 22 Sub Step E.
- return permute(sumA, permuteTableSHACryptSHA512[:])
- }
-
- return nil
-}
-
-// KeySHA1Crypt calculates the sha1crypt key given a password, salt, and number of rounds.
-func KeySHA1Crypt(password, salt []byte, rounds uint32) []byte {
- digest := hmac.New(sha1.New, password)
- digest.Write(salt)
- digest.Write(prefixSHA1Crypt)
- digest.Write([]byte(strconv.FormatUint(uint64(rounds), 10)))
-
- sumA := digest.Sum(nil)
-
- if rounds == 0 {
- return permute(sumA, permuteTableSHA1Crypt[:])
- }
-
- for rounds--; rounds > 0; rounds-- {
- digest.Reset()
-
- digest.Write(sumA)
-
- copy(sumA, digest.Sum(nil))
- }
-
- return permute(sumA, permuteTableSHA1Crypt[:])
-}
-
-// KeyMD5Crypt calculates the md5crypt key given a password and salt.
-func KeyMD5Crypt(password, salt []byte) []byte {
- length := len(password)
-
- digest := md5.New()
-
- digest.Write(password)
- digest.Write(salt)
- digest.Write(password)
-
- sumB := digest.Sum(nil)
-
- digest.Reset()
-
- digest.Write(password)
- digest.Write(prefixMD5Crypt)
- digest.Write(salt)
- digest.Write(repeat(sumB, length))
-
- clean(sumB)
-
- for i := length; i > 0; i >>= 1 {
- if even(i) {
- digest.Write(password[0:1])
- } else {
- digest.Write([]byte{0})
- }
- }
-
- sumA := digest.Sum(nil)
-
- for i := 0; i < 1000; i++ {
- digest.Reset()
-
- if even(i) {
- digest.Write(sumA)
- } else {
- digest.Write(password)
- }
-
- if i%3 != 0 {
- digest.Write(salt)
- }
-
- if i%7 != 0 {
- digest.Write(password)
- }
-
- if i&1 == 0 {
- digest.Write(password)
- } else {
- digest.Write(sumA)
- }
-
- copy(sumA, digest.Sum(nil))
- }
-
- return permute(sumA, permuteTableMD5Crypt[:])
-}
-
-// KeyMD5CryptSun calculates the md5crypt (Sun Version) key given a password, salt, and number rounds.
-func KeyMD5CryptSun(password, salt []byte, rounds uint32) []byte {
- digest := md5.New()
-
- digest.Write(password)
-
- if rounds == 0 {
- digest.Write(prefixSunMD5Crypt)
- digest.Write(salt)
- digest.Write(sepCrypt)
- } else {
- digest.Write(prefixSunMD5CryptRounds)
- digest.Write([]byte(strconv.FormatUint(uint64(rounds), 10)))
- digest.Write(sepCrypt)
- digest.Write(salt)
- digest.Write(sepCrypt)
- }
-
- sumA := digest.Sum(nil)
-
- iterations := uint32(rounds + 4096)
-
- bit := func(off uint32) uint32 {
- off %= 128
- if (sumA[off/8] & (0x01 << (off % 8))) != 0 {
- return 1
- }
-
- return 0
- }
-
- var ind7 [md5.Size]byte
-
- for i := uint32(0); i < iterations; i++ {
- digest.Reset()
-
- digest.Write(sumA)
-
- for j := 0; j < md5.Size; j++ {
- off := (j + 3) % 16
- ind4 := (sumA[j] >> (sumA[off] % 5)) & 0x0F
- sh7 := (sumA[off] >> (sumA[j] % 8)) & 0x01
- ind7[j] = (sumA[ind4] >> sh7) & 0x7F
- }
-
- var indA, indB uint32
-
- for j := uint(0); j < 8; j++ {
- indA |= bit(uint32(ind7[j])) << j
- indB |= bit(uint32(ind7[j+8])) << j
- }
-
- indA = (indA >> bit(i)) & 0x7F
- indB = (indB >> bit(i+64)) & 0x7F
-
- if bit(indA)^bit(indB) == 1 {
- digest.Write(magicTableMD5CryptSunHamlet[:])
- }
-
- digest.Write([]byte(strconv.FormatUint(uint64(i), 10)))
-
- copy(sumA, digest.Sum(nil))
- }
-
- return permute(sumA, permuteTableMD5Crypt[:])
-}
diff --git a/vendor/github.com/go-crypt/x/crypt/util.go b/vendor/github.com/go-crypt/x/crypt/util.go
deleted file mode 100644
index c5e6fa4477..0000000000
--- a/vendor/github.com/go-crypt/x/crypt/util.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package crypt
-
-import (
- b64 "github.com/go-crypt/x/base64"
-)
-
-func permute(sum, table []byte) []byte {
- size := len(table)
-
- key := make([]byte, size)
-
- for i := 0; i < size; i++ {
- key[i] = sum[table[i]]
- }
-
- return b64.EncodeCrypt(key)
-}
-
-func even(i int) bool {
- return i%2 == 0
-}
-
-var (
- cleanBytes = make([]byte, 64)
-)
-
-func clean(b []byte) {
- l := len(b)
-
- for ; l > 64; l -= 64 {
- copy(b[l-64:l], cleanBytes)
- }
-
- if l > 0 {
- copy(b[0:l], cleanBytes[0:l])
- }
-}
-
-func repeat(input []byte, length int) []byte {
- var (
- seq = make([]byte, length)
- unit = len(input)
- )
-
- j := length / unit * unit
- for i := 0; i < j; i += unit {
- copy(seq[i:length], input)
- }
- if j < length {
- copy(seq[j:length], input[0:length-j])
- }
-
- return seq
-}
diff --git a/vendor/github.com/testcontainers/testcontainers-go/network/network.go b/vendor/github.com/testcontainers/testcontainers-go/network/network.go
new file mode 100644
index 0000000000..97e69bb6ba
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/network/network.go
@@ -0,0 +1,196 @@
+package network
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "maps"
+
+ "github.com/google/uuid"
+ "github.com/moby/moby/api/types/network"
+ "github.com/moby/moby/client"
+
+ "github.com/testcontainers/testcontainers-go"
+)
+
+// New creates a new network with a random UUID name, calling the already existing GenericNetwork APIs.
+// Those existing APIs are deprecated and will be removed in the future, so this function will
+// implement the new network APIs when they will be available.
+// By default, the network is created with the following options:
+// - Driver: bridge
+// - Labels: the Testcontainers for Go generic labels, to be managed by Ryuk. Please see the GenericLabels() function
+// And those options can be modified by the user, using the CreateModifier function field.
+func New(ctx context.Context, opts ...NetworkCustomizer) (*testcontainers.DockerNetwork, error) {
+ nc := client.NetworkCreateOptions{
+ Driver: "bridge",
+ Labels: testcontainers.GenericLabels(),
+ }
+
+ for _, opt := range opts {
+ if err := opt.Customize(&nc); err != nil {
+ return nil, err
+ }
+ }
+
+ //nolint:staticcheck
+ netReq := testcontainers.NetworkRequest{
+ Driver: nc.Driver,
+ Internal: nc.Internal,
+ EnableIPv6: nc.EnableIPv6,
+ Name: uuid.NewString(),
+ Labels: nc.Labels,
+ Attachable: nc.Attachable,
+ IPAM: nc.IPAM,
+ }
+
+ //nolint:staticcheck
+ n, err := testcontainers.GenericNetwork(ctx, testcontainers.GenericNetworkRequest{
+ NetworkRequest: netReq,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ // Return a DockerNetwork struct instead of the Network interface,
+ // following the "accept interface, return struct" pattern.
+ return n.(*testcontainers.DockerNetwork), nil
+}
+
+// NetworkCustomizer is an interface that can be used to configure the network create request.
+type NetworkCustomizer interface {
+ Customize(req *client.NetworkCreateOptions) error
+}
+
+// CustomizeNetworkOption is a type that can be used to configure the network create request.
+type CustomizeNetworkOption func(req *client.NetworkCreateOptions) error
+
+// Customize implements the NetworkCustomizer interface,
+// applying the option to the network create request.
+func (opt CustomizeNetworkOption) Customize(req *client.NetworkCreateOptions) error {
+ return opt(req)
+}
+
+// WithAttachable allows to set the network as attachable.
+func WithAttachable() CustomizeNetworkOption {
+ return func(original *client.NetworkCreateOptions) error {
+ original.Attachable = true
+
+ return nil
+ }
+}
+
+// WithCheckDuplicate allows to check if a network with the same name already exists.
+//
+// Deprecated: CheckDuplicate is deprecated since API v1.44, but it defaults to true when sent by the client package to older daemons.
+func WithCheckDuplicate() CustomizeNetworkOption {
+ return func(_ *client.NetworkCreateOptions) error {
+ return nil
+ }
+}
+
+// WithDriver allows to override the default network driver, which is "bridge".
+func WithDriver(driver string) CustomizeNetworkOption {
+ return func(original *client.NetworkCreateOptions) error {
+ original.Driver = driver
+
+ return nil
+ }
+}
+
+// WithEnableIPv6 allows to set the network as IPv6 enabled.
+// Please use this option if and only if IPv6 is enabled on the Docker daemon.
+func WithEnableIPv6() CustomizeNetworkOption {
+ return func(original *client.NetworkCreateOptions) error {
+ enableIPv6 := true
+ original.EnableIPv6 = &enableIPv6
+ return nil
+ }
+}
+
+// WithInternal allows to set the network as internal.
+func WithInternal() CustomizeNetworkOption {
+ return func(original *client.NetworkCreateOptions) error {
+ original.Internal = true
+
+ return nil
+ }
+}
+
+// WithLabels allows to set the network labels, adding the new ones
+// to the default Testcontainers for Go labels.
+func WithLabels(labels map[string]string) CustomizeNetworkOption {
+ return func(original *client.NetworkCreateOptions) error {
+ maps.Copy(original.Labels, labels)
+
+ return nil
+ }
+}
+
+// WithIPAM allows to change the default IPAM configuration.
+func WithIPAM(ipam *network.IPAM) CustomizeNetworkOption {
+ return func(original *client.NetworkCreateOptions) error {
+ original.IPAM = ipam
+
+ return nil
+ }
+}
+
+// WithNetwork reuses an already existing network, attaching the container to it.
+// Finally it sets the network alias on that network to the given alias.
+func WithNetwork(aliases []string, nw *testcontainers.DockerNetwork) testcontainers.CustomizeRequestOption {
+ return WithNetworkName(aliases, nw.Name)
+}
+
+// WithNetworkName attachs a container to an already existing network, by its name.
+// If the network is not "bridge", it sets the network alias on that network
+// to the given alias, else, it returns an error. This is because network-scoped alias
+// is supported only for containers in user defined networks.
+func WithNetworkName(aliases []string, networkName string) testcontainers.CustomizeRequestOption {
+ return func(req *testcontainers.GenericContainerRequest) error {
+ if networkName == "bridge" {
+ return errors.New("network-scoped aliases are supported only for containers in user defined networks")
+ }
+
+ // attaching to the network because it was created with success or it already existed.
+ req.Networks = append(req.Networks, networkName)
+
+ if req.NetworkAliases == nil {
+ req.NetworkAliases = make(map[string][]string)
+ }
+ req.NetworkAliases[networkName] = aliases
+
+ return nil
+ }
+}
+
+// WithBridgeNetwork attachs a container to the "bridge" network.
+// There is no need to set the network alias, as it is not supported for the "bridge" network.
+func WithBridgeNetwork() testcontainers.CustomizeRequestOption {
+ return func(req *testcontainers.GenericContainerRequest) error {
+ req.Networks = append(req.Networks, "bridge")
+ return nil
+ }
+}
+
+// WithNewNetwork creates a new network with random name and customizers, and attaches the container to it.
+// Finally it sets the network alias on that network to the given alias.
+func WithNewNetwork(ctx context.Context, aliases []string, opts ...NetworkCustomizer) testcontainers.CustomizeRequestOption {
+ return func(req *testcontainers.GenericContainerRequest) error {
+ newNetwork, err := New(ctx, opts...)
+ if err != nil {
+ return fmt.Errorf("new network: %w", err)
+ }
+
+ networkName := newNetwork.Name
+
+ // attaching to the network because it was created with success or it already existed.
+ req.Networks = append(req.Networks, networkName)
+
+ if req.NetworkAliases == nil {
+ req.NetworkAliases = make(map[string][]string)
+ }
+ req.NetworkAliases[networkName] = aliases
+
+ return nil
+ }
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 4944bdf929..79f26a56d0 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -487,16 +487,6 @@ github.com/go-chi/chi/v5/middleware
# github.com/go-chi/render v1.0.3
## explicit; go 1.16
github.com/go-chi/render
-# github.com/go-crypt/crypt v0.4.5
-## explicit; go 1.23
-github.com/go-crypt/crypt/algorithm
-github.com/go-crypt/crypt/algorithm/shacrypt
-github.com/go-crypt/crypt/internal/encoding
-github.com/go-crypt/crypt/internal/random
-# github.com/go-crypt/x v0.4.7
-## explicit; go 1.23.0
-github.com/go-crypt/x/base64
-github.com/go-crypt/x/crypt
# github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376
## explicit; go 1.13
github.com/go-git/gcfg
@@ -2141,6 +2131,7 @@ github.com/testcontainers/testcontainers-go/internal/config
github.com/testcontainers/testcontainers-go/internal/core
github.com/testcontainers/testcontainers-go/internal/core/network
github.com/testcontainers/testcontainers-go/log
+github.com/testcontainers/testcontainers-go/network
github.com/testcontainers/testcontainers-go/wait
# github.com/testcontainers/testcontainers-go/modules/opensearch v0.42.0
## explicit; go 1.25.0