Commit 8bf80917 authored by ale's avatar ale

Implement resource-level validation

parent 3a30acc6
......@@ -23,6 +23,8 @@ type validationConfig struct {
forbiddenPasswords stringSet
minPasswordLength int
maxPasswordLength int
domains domainBackend
backend Backend
}
// A stringSet is just a list of strings with a quick membership test.
......@@ -39,6 +41,13 @@ func newStringSetFromList(list []string) stringSet {
return stringSet{set: set, list: list}
}
func newStringSetFromFileOrList(list []string, path string) (stringSet, error) {
if path != "" {
return loadStringSetFromFile(path)
}
return newStringSetFromList(list), nil
}
func (s stringSet) Contains(needle string) bool {
_, ok := s.set[needle]
return ok
......@@ -155,7 +164,7 @@ func isRegistered(domain string) bool {
return true
}
func validDomainName(value string) error {
func validDomainName(_ context.Context, value string) error {
if !domainRx.MatchString(value) {
return errors.New("invalid domain name")
}
......@@ -226,31 +235,31 @@ func relatedWebsites(ctx context.Context, be domainBackend, value string) []Find
return resourceIDs
}
func isAvailableEmailHostingDomain(be domainBackend) ValidatorFunc {
func isAvailableEmailHostingDomain(config *validationConfig) ValidatorFunc {
return func(ctx context.Context, value string) error {
if !be.IsAvailableDomain(ctx, ResourceTypeEmail, value) {
if !config.domains.IsAvailableDomain(ctx, ResourceTypeEmail, value) {
return errors.New("unavailable domain")
}
return nil
}
}
func isAvailableMailingListDomain(be domainBackend) ValidatorFunc {
func isAvailableMailingListDomain(config *validationConfig) ValidatorFunc {
return func(ctx context.Context, value string) error {
if !be.IsAvailableDomain(ctx, ResourceTypeMailingList, value) {
if !config.domains.IsAvailableDomain(ctx, ResourceTypeMailingList, value) {
return errors.New("unavailable domain")
}
return nil
}
}
func isAvailableEmailAddr(be domainBackend, cb Backend) ValidatorFunc {
func isAvailableEmailAddr(config *validationConfig) ValidatorFunc {
return func(ctx context.Context, value string) error {
rel := relatedEmails(ctx, be, value)
rel := relatedEmails(ctx, config.domains, value)
// Run the presence check in a new transaction. Unavailability
// of the server results in a validation error (fail close).
tx, err := cb.NewTransaction()
tx, err := config.backend.NewTransaction()
if err != nil {
return err
}
......@@ -261,23 +270,48 @@ func isAvailableEmailAddr(be domainBackend, cb Backend) ValidatorFunc {
}
}
func validHostedEmail(config *validationConfig, be domainBackend, cb Backend) ValidatorFunc {
func isAvailableDomain(config *validationConfig) ValidatorFunc {
return func(ctx context.Context, value string) error {
rel := relatedWebsites(ctx, config.domains, value)
// Run the presence check in a new transaction. Unavailability
// of the server results in a validation error (fail close).
tx, err := config.backend.NewTransaction()
if err != nil {
return err
}
if ok, _ := tx.HasAnyResource(ctx, rel); ok {
return errors.New("address unavailable")
}
return nil
}
}
func validHostedEmail(config *validationConfig) ValidatorFunc {
return allOf(
validateUsernameAndDomain(
allOf(matchUsernameRx(), minLength(4), maxLength(64), notInSet(config.forbiddenUsernames)),
allOf(isAvailableEmailHostingDomain(be)),
allOf(isAvailableEmailHostingDomain(config)),
),
isAvailableEmailAddr(be, cb),
isAvailableEmailAddr(config),
)
}
func validHostedMailingList(config *validationConfig, be domainBackend, cb Backend) ValidatorFunc {
func validHostedMailingList(config *validationConfig) ValidatorFunc {
return allOf(
validateUsernameAndDomain(
allOf(matchUsernameRx(), minLength(4), maxLength(64), notInSet(config.forbiddenUsernames)),
allOf(isAvailableMailingListDomain(be)),
allOf(isAvailableMailingListDomain(config)),
),
isAvailableEmailAddr(be, cb),
isAvailableEmailAddr(config),
)
}
func validHostedDomain(config *validationConfig) ValidatorFunc {
return allOf(
minLength(6),
validDomainName,
isAvailableDomain(config),
)
}
......@@ -299,3 +333,55 @@ func allOf(funcs ...ValidatorFunc) ValidatorFunc {
return nil
}
}
// ResourceValidatorFunc is a composite type validator that checks
// various fields in a Resource, depending on its type.
type ResourceValidatorFunc func(ctx context.Context, r *Resource) error
func validEmailResource(config *validationConfig) ResourceValidatorFunc {
emailValidator := validHostedEmail(config)
return func(ctx context.Context, r *Resource) error {
return emailValidator(ctx, r.ID.Name())
}
}
func validListResource(config *validationConfig) ResourceValidatorFunc {
listValidator := validHostedMailingList(config)
return func(ctx context.Context, r *Resource) error {
if err := listValidator(ctx, r.ID.Name()); err != nil {
return err
}
if len(r.List.Admins) < 1 {
return errors.New("can't create a list without admins")
}
return nil
}
}
func validDomain(config *validationConfig) ResourceValidatorFunc {
domainValidator := validHostedDomain(config)
return func(ctx context.Context, r *Resource) error {
return domainValidator(ctx, r.ID.Name())
}
}
type resourceValidator struct {
v map[string]ResourceValidatorFunc
}
func newResourceValidator(config *validationConfig) *resourceValidator {
return &resourceValidator{
v: map[string]ResourceValidatorFunc{
ResourceTypeEmail: validEmailResource(config),
ResourceTypeMailingList: validListResource(config),
ResourceTypeDomain: validDomain(config),
},
}
}
func (v *resourceValidator) validateResource(ctx context.Context, r *Resource) error {
return v.v[r.ID.Type()](ctx, r)
}
......@@ -124,11 +124,11 @@ func TestValidator_HostedEmail(t *testing.T) {
{"existing@example.com", false},
}
vc := newTestValidationConfig("forbidden")
cb := newFakeCheckBackend([]FindResourceRequest{
vc.backend = newFakeCheckBackend([]FindResourceRequest{
{Type: ResourceTypeEmail, Name: "existing@example.com"},
})
db := newFakeDomainBackend("example.com")
runValidationTest(t, validHostedEmail(vc, db, cb), td)
vc.domains = newFakeDomainBackend("example.com")
runValidationTest(t, validHostedEmail(vc), td)
}
func TestValidator_HostedMailingList(t *testing.T) {
......@@ -142,9 +142,9 @@ func TestValidator_HostedMailingList(t *testing.T) {
{"existing@domain2.com", false},
}
vc := newTestValidationConfig("forbidden")
cb := newFakeCheckBackend([]FindResourceRequest{
vc.backend = newFakeCheckBackend([]FindResourceRequest{
{Type: ResourceTypeMailingList, Name: "existing@domain2.com"},
})
db := newFakeDomainBackend("domain1.com", "domain2.com")
runValidationTest(t, validHostedMailingList(vc, db, cb), td)
vc.domains = newFakeDomainBackend("domain1.com", "domain2.com")
runValidationTest(t, validHostedMailingList(vc), td)
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment