groupware: port tests to Stalwart 0.16.7

This commit is contained in:
Pascal Bleser
2026-06-05 18:59:40 +02:00
parent 92fbdda1ec
commit 8690f7c6cb
37 changed files with 1108 additions and 1922 deletions

4
go.mod
View File

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

4
go.sum
View File

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

View File

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
// Package algorithm is a package which contains the individual algorithms and interfaces related to their
// implementation.
package algorithm

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +0,0 @@
package encoding
const (
// Delimiter rune for all encodings.
Delimiter = rune('$')
// DelimiterStr is the string variation of Delimiter.
DelimiterStr = string(Delimiter)
)

View File

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

View File

@@ -1,2 +0,0 @@
// Package encoding is an internal encoding helper package.
package encoding

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
// Package random is an internal helper package.
package random

27
vendor/github.com/go-crypt/x/LICENSE generated vendored
View File

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

View File

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

View File

@@ -1,7 +0,0 @@
package base64
const (
cryptB64Alphabet = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
bcryptB64Alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
encodeAdapted = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./"
)

View File

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

View File

@@ -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[:])
}

View File

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

View File

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

11
vendor/modules.txt vendored
View File

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