package accountserver import ( "errors" "fmt" ) // GetResourceRequest requests a specific resource. type GetResourceRequest struct { AdminResourceRequestBase } // GetResourceResponse is the response type for GetResourceRequest. type GetResourceResponse struct { Resource *Resource `json:"resource"` Owner string `json:"owner"` } // Serve the request. func (r *GetResourceRequest) Serve(rctx *RequestContext) (interface{}, error) { resp := GetResourceResponse{ Resource: rctx.Resource, } if rctx.User != nil { resp.Owner = rctx.User.Name } return &resp, nil } // SearchResourceRequest searches for resources matching a pattern. type SearchResourceRequest struct { AdminRequestBase Pattern string `json:"pattern"` } // Validate the request. func (r *SearchResourceRequest) Validate(rctx *RequestContext) error { if r.Pattern == "" { return errors.New("empty pattern") } return nil } // SearchResourceResponse is the response type for SearchResourceRequest. type SearchResourceResponse struct { Results []*RawResource `json:"results"` } // Serve the request. func (r *SearchResourceRequest) Serve(rctx *RequestContext) (interface{}, error) { results, err := rctx.TX.SearchResource(rctx.Context, r.Pattern) if err != nil { return nil, err } return &SearchResourceResponse{Results: results}, nil } // setResourceStatus sets the status of a single resource (shared // logic between enable / disable resource methods). func setResourceStatus(rctx *RequestContext, status string) error { rsrc := rctx.Resource rsrc.Status = status if err := rctx.TX.UpdateResource(rctx.Context, rsrc); err != nil { return err } rctx.audit.Log(rctx, rsrc, fmt.Sprintf("status set to %s", status)) return nil } // SetResourceStatusRequest modifies the status of a resource // belonging to the user (admin-only). type SetResourceStatusRequest struct { AdminResourceRequestBase Status string `json:"status"` } // Validate the request. func (r *SetResourceStatusRequest) Validate(rctx *RequestContext) error { if !isValidStatusByResourceType(rctx.Resource.Type, r.Status) { return errors.New("invalid or unknown status") } return nil } // Serve the request. func (r *SetResourceStatusRequest) Serve(rctx *RequestContext) (interface{}, error) { return nil, setResourceStatus(rctx, r.Status) } // ResetResourcePasswordRequest will reset the password associated // with a resource (if the resource type supports it). It will // generate a random password and return it to the caller. type ResetResourcePasswordRequest struct { ResourceRequestBase } // ResetResourcePasswordResponse is the response type for // ResetResourcePasswordRequest. type ResetResourcePasswordResponse struct { Password string `json:"password"` } // Validate the request. func (r *ResetResourcePasswordRequest) Validate(rctx *RequestContext) error { switch rctx.Resource.Type { case ResourceTypeDAV, ResourceTypeDatabase, ResourceTypeMailingList: case ResourceTypeEmail: return errors.New("can't reset email passwords with this API") default: return errors.New("can't reset password on this resource type") } return nil } // Serve the request. func (r *ResetResourcePasswordRequest) Serve(rctx *RequestContext) (interface{}, error) { // TODO: this needs a resource-type switch, because in some // cases we may want to call out to other backends in order to // reset credentials for certain resources that have their own // secondary authentication databases (lists, mysql). password, err := doResetResourcePassword(rctx.Context, rctx.TX, rctx.Resource) if err != nil { return nil, err } return &ResetResourcePasswordResponse{ Password: password, }, nil } // MoveResourceRequest is an administrative operation to move resources // between shards. Resources that are part of a group are moved all at // once regardless of which individual ResourceID is provided as long // as it belongs to the group. type MoveResourceRequest struct { AdminResourceRequestBase Shard string `json:"shard"` } // Validate the request. func (r *MoveResourceRequest) Validate(rctx *RequestContext) error { // TODO: check shard return nil } // MoveResourceResponse is the response type for MoveResourceRequest. type MoveResourceResponse struct { MovedIDs []string `json:"moved_ids"` } // Serve the request. func (r *MoveResourceRequest) Serve(rctx *RequestContext) (interface{}, error) { resources := []*Resource{rctx.Resource} // If we have an associated user, collect all related // resources, as they should all be moved at once. if rctx.User != nil && rctx.Resource.Group != "" { resources = append(resources, rctx.User.GetResourcesByGroup(rctx.Resource.Group)...) } // We need to enforce consistency between email resources and // the user shard, so that temporary data can be colocated // with email storage. if rctx.Resource.Type == ResourceTypeEmail && rctx.User.Shard != r.Shard { rctx.User.Shard = r.Shard if err := rctx.TX.UpdateUser(rctx.Context, &rctx.User.User); err != nil { return nil, err } } var resp MoveResourceResponse for _, rsrc := range resources { rsrc.Shard = r.Shard if err := rctx.TX.UpdateResource(rctx.Context, rsrc); err != nil { return nil, err } resp.MovedIDs = append(resp.MovedIDs, rsrc.ID.String()) } return &resp, nil } // AddEmailAliasRequest adds an alias (additional address) to an email resource. type AddEmailAliasRequest struct { ResourceRequestBase Addr string `json:"addr"` } // Validate the request. func (r *AddEmailAliasRequest) Validate(rctx *RequestContext) error { if rctx.Resource.Type != ResourceTypeEmail { return errors.New("this operation only works on email resources") } // Allow at most 5 aliases. if len(rctx.Resource.Email.Aliases) >= maxEmailAliases { return errors.New("too many aliases") } if err := rctx.fieldValidators.email(rctx.Context, r.Addr); err != nil { return newValidationError(nil, "addr", err.Error()) } return nil } const maxEmailAliases = 5 // Serve the request. func (r *AddEmailAliasRequest) Serve(rctx *RequestContext) (interface{}, error) { rctx.Resource.Email.Aliases = append(rctx.Resource.Email.Aliases, r.Addr) if err := rctx.TX.UpdateResource(rctx.Context, rctx.Resource); err != nil { return nil, err } rctx.audit.Log(rctx, rctx.Resource, fmt.Sprintf("added alias %s", r.Addr)) return nil, nil } // DeleteEmailAliasRequest removes an alias from an email resource. type DeleteEmailAliasRequest struct { ResourceRequestBase Addr string `json:"addr"` } // Validate the request. func (r *DeleteEmailAliasRequest) Validate(rctx *RequestContext) error { if rctx.Resource.Type != ResourceTypeEmail { return errors.New("this operation only works on email resources") } return nil } // Serve the request. func (r *DeleteEmailAliasRequest) Serve(rctx *RequestContext) (interface{}, error) { var aliases []string for _, a := range rctx.Resource.Email.Aliases { if a != r.Addr { aliases = append(aliases, a) } } rctx.Resource.Email.Aliases = aliases if err := rctx.TX.UpdateResource(rctx.Context, rctx.Resource); err != nil { return nil, err } rctx.audit.Log(rctx, rctx.Resource, fmt.Sprintf("removed alias %s", r.Addr)) return nil, nil }