Select Git revision
template.go
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
}