package accountserver import ( "errors" "fmt" ) // 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.ID, fmt.Sprintf("status set to %s", status)) return nil } // DisableResourceRequest disables a resource belonging to the user. type DisableResourceRequest struct { ResourceRequestBase } // Serve the request. func (r *DisableResourceRequest) Serve(rctx *RequestContext) (interface{}, error) { return nil, setResourceStatus(rctx, ResourceStatusInactive) } // EnableResourceRequest enables a resource belonging to the user (admin-only). type EnableResourceRequest struct { AdminResourceRequestBase } // Serve the request. func (r *EnableResourceRequest) Serve(rctx *RequestContext) (interface{}, error) { return nil, setResourceStatus(rctx, ResourceStatusActive) } // 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"` } func resourceHasPassword(r *Resource) bool { switch r.ID.Type() { case ResourceTypeDAV, ResourceTypeDatabase, ResourceTypeMailingList: return true default: return false } } // Validate the request. func (r *ResetResourcePasswordRequest) Validate(_ *RequestContext) error { switch r.ResourceID.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)...) } 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.ID.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, r.ResourceID, 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.ID.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, r.ResourceID, fmt.Sprintf("removed alias %s", r.Addr)) return nil, nil }