Skip to content
Snippets Groups Projects
Select Git revision
  • renovate/golang.org-x-net-0.x
  • renovate/golang.org-x-crypto-0.x
  • renovate/go-1.x
  • renovate/golang.org-x-sync-0.x
  • renovate/github.com-protonmail-gopenpgp-v3-3.x
  • renovate/github.com-pquerna-otp-1.x
  • renovate/github.com-go-ldap-ldap-v3-3.x
  • renovate/github.com-prometheus-client_golang-1.x
  • renovate/git.autistici.org-id-auth-digest
  • master default protected
  • renovate/github.com-protonmail-gopenpgp-v2-2.x
  • better-validation
12 results

template.go

Blame
  • template.go 6.04 KiB
    package accountserver
    
    import (
    	"context"
    	"errors"
    	"fmt"
    	"math/rand"
    	"path/filepath"
    	"strings"
    	"time"
    )
    
    // Templating, in this context, means filling up derived attributes in a resource.
    //
    // Derived attributes are always enforced (i.e. we don't check if the
    // attribute already has a value), but currently this is only done at
    // resource creation time. The plan is to extend this to cover any
    // Update call as well.
    type templateContext struct {
    	shards  shardBackend
    	webroot string
    }
    
    func (c *templateContext) pickShard(ctx context.Context, r *Resource) (string, error) {
    	avail := c.shards.GetAvailableShards(ctx, r.Type)
    	if len(avail) == 0 {
    		return "", fmt.Errorf("no available shards for resource type %s", r.Type)
    	}
    	return avail[rand.Intn(len(avail))], nil
    }
    
    func (c *templateContext) setResourceShard(ctx context.Context, r *Resource, ref *Resource) error {
    	if r.Shard == "" {
    		if ref != nil {
    			r.Shard = ref.Shard
    		} else {
    			s, err := c.pickShard(ctx, r)
    			if err != nil {
    				return err
    			}
    			r.Shard = s
    		}
    	}
    	if r.OriginalShard == "" {
    		r.OriginalShard = r.Shard
    	}
    	return nil
    }
    
    func (c *templateContext) setResourceStatus(r *Resource) {
    	if r.Status == "" {
    		r.Status = ResourceStatusActive
    	}
    }
    
    func (c *templateContext) setCommonResourceAttrs(ctx context.Context, r *Resource, ref *Resource, user *User) error {
    	// If we reference another resource, ensure it has been templated.
    	if ref != nil {
    		if err := c.applyTemplate(ctx, ref, user); err != nil {
    			return err
    		}
    	}
    
    	r.CreatedAt = time.Now().UTC().Format("2006-01-02")
    
    	c.setResourceStatus(r)
    	return c.setResourceShard(ctx, r, ref)
    }
    
    // Apply default values to an Email resource.
    func (c *templateContext) emailResourceTemplate(ctx context.Context, r *Resource, _ *User) error {
    	// Force the email address to lowercase.
    	r.Name = strings.ToLower(r.Name)
    
    	if r.Email == nil {
    		r.Email = new(Email)
    	}
    
    	addrParts := strings.Split(r.Name, "@")
    	if len(addrParts) != 2 {
    		return errors.New("malformed name")
    	}
    	r.Email.Maildir = fmt.Sprintf("%s/%s", addrParts[1], addrParts[0])
    	r.Email.QuotaLimit = 4096
    	return c.setCommonResourceAttrs(ctx, r, nil, nil)
    }
    
    // Apply default values to a Website or Domain resource.
    func (c *templateContext) websiteResourceTemplate(ctx context.Context, r *Resource, user *User) error {
    	if user == nil {
    		return errors.New("website resource needs owner")
    	}
    
    	// Force the website address to lowercase.
    	r.Name = strings.ToLower(r.Name)
    
    	if r.Website == nil {
    		r.Website = new(Website)
    	}
    
    	// If the client did not specify a DocumentRoot, find a DAV resource
    	// and associate the website with it.
    	if r.Website.DocumentRoot == "" {
    		dav := user.GetSingleResourceByType(ResourceTypeDAV)
    		if dav == nil {
    			return errors.New("user has no DAV accounts")
    		}
    
    		// The DAV resource may not have been templatized yet.
    		if dav.DAV == nil || dav.DAV.Homedir == "" {
    			if err := c.davResourceTemplate(ctx, dav, user); err != nil {
    				return err
    			}
    		}
    		r.Website.DocumentRoot = filepath.Join(dav.DAV.Homedir, "html-"+r.Name)
    	}
    	r.Website.DocumentRoot = filepath.Clean(r.Website.DocumentRoot)
    
    	if len(r.Website.Options) == 0 {
    		r.Website.Options = []string{"nomail"}
    	}
    
    	r.Website.UID = user.UID
    
    	dav := findMatchingDAVAccount(user, r)
    	if dav == nil {
    		return fmt.Errorf("no DAV resources matching website %s", r.String())
    	}
    	return c.setCommonResourceAttrs(ctx, r, dav, user)
    }
    
    // Apply default values to a DAV resource.
    func (c *templateContext) davResourceTemplate(ctx context.Context, r *Resource, user *User) error {
    	if user == nil {
    		return errors.New("dav resource needs owner")
    	}
    
    	// Force the account name to lowercase.
    	r.Name = strings.ToLower(r.Name)
    
    	if r.DAV == nil {
    		r.DAV = new(WebDAV)
    	}
    	if r.DAV.Homedir == "" {
    		r.DAV.Homedir = filepath.Join(c.webroot, r.Name)
    	}
    	r.DAV.Homedir = filepath.Clean(r.DAV.Homedir)
    	r.DAV.UID = user.UID
    
    	return c.setCommonResourceAttrs(ctx, r, nil, user)
    }
    
    // Apply default values to a Database resource.
    func (c *templateContext) databaseResourceTemplate(ctx context.Context, r *Resource, user *User) error {
    	if user == nil {
    		return errors.New("database resource needs owner")
    	}
    
    	// Force the database name to lowercase.
    	r.Name = strings.ToLower(r.Name)
    
    	if r.Database == nil {
    		r.Database = new(Database)
    	}
    	if r.Database.DBUser == "" {
    		r.Database.DBUser = r.Name
    	}
    
    	return c.setCommonResourceAttrs(ctx, r, user.GetResourceByID(r.ParentID), user)
    }
    
    // Apply default values to a MailingList resource.
    func (c *templateContext) listResourceTemplate(ctx context.Context, r *Resource, user *User) error {
    	// Force the list address to lowercase.
    	r.Name = strings.ToLower(r.Name)
    
    	if r.List == nil {
    		r.List = new(MailingList)
    	}
    
    	// As a convenience, if a user is passed in the context, we add it to
    	// the list admins.
    	if user != nil && len(r.List.Admins) == 0 {
    		r.List.Admins = []string{user.Name}
    	}
    
    	return c.setCommonResourceAttrs(ctx, r, nil, nil)
    }
    
    // Apply default values to a Newsletter resource.
    func (c *templateContext) newsletterResourceTemplate(ctx context.Context, r *Resource, user *User) error {
    	// Force the list address to lowercase.
    	r.Name = strings.ToLower(r.Name)
    
    	if r.Newsletter == nil {
    		r.Newsletter = new(Newsletter)
    	}
    
    	// As a convenience, if a user is passed in the context, we add it to
    	// the list admins.
    	if user != nil && len(r.Newsletter.Admins) == 0 {
    		r.Newsletter.Admins = []string{user.Name}
    	}
    
    	return c.setCommonResourceAttrs(ctx, r, nil, nil)
    }
    
    // Apply default values to a resource.
    func (c *templateContext) applyTemplate(ctx context.Context, r *Resource, user *User) error {
    	switch r.Type {
    	case ResourceTypeEmail:
    		return c.emailResourceTemplate(ctx, r, user)
    	case ResourceTypeWebsite, ResourceTypeDomain:
    		return c.websiteResourceTemplate(ctx, r, user)
    	case ResourceTypeDAV:
    		return c.davResourceTemplate(ctx, r, user)
    	case ResourceTypeDatabase:
    		return c.databaseResourceTemplate(ctx, r, user)
    	case ResourceTypeMailingList:
    		return c.listResourceTemplate(ctx, r, user)
    	case ResourceTypeNewsletter:
    		return c.newsletterResourceTemplate(ctx, r, user)
    	}
    	return nil
    }