Base CUE, def, schemas
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
package codegen
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/codegen/schema"
|
||||
)
|
||||
|
||||
all: [...schema.#codegen] &
|
||||
rbacAccessControl +
|
||||
rbacTypes +
|
||||
[] // placeholder
|
||||
@@ -0,0 +1,5 @@
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
@@ -0,0 +1,232 @@
|
||||
package {{ .package }}
|
||||
|
||||
{{ template "gocode/header-gentext.tpl" }}
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cast"
|
||||
"strings"
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
{{- range .imports }}
|
||||
"{{ . }}"
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
|
||||
type (
|
||||
accessControl struct {
|
||||
actionlog actionlog.Recorder
|
||||
|
||||
rbac interface {
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
CloneRulesByRoleID(ctx context.Context, fromRoleID uint64, toRoleID ...uint64) error
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
func AccessControl() *accessControl {
|
||||
return &accessControl{
|
||||
rbac: rbac.Global(),
|
||||
actionlog: DefaultActionlog,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
func (svc accessControl) Effective(ctx context.Context, rr ... rbac.Resource) (ee rbac.EffectiveSet) {
|
||||
for _, res := range rr {
|
||||
r := res.RbacResource()
|
||||
for op := range rbacResourceOperations(r) {
|
||||
ee.Push(r, op, svc.can(ctx, op, res))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc accessControl) List() (out []map[string]string) {
|
||||
def := []map[string]string{
|
||||
{{- range .operations }}
|
||||
{
|
||||
"type": {{ .const }},
|
||||
"any": {{ .ctor }},
|
||||
"op": {{ printf "%q" .op }},
|
||||
},
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
func(svc interface{}) {
|
||||
if svc, is := svc.(interface{}).(interface{ list() []map[string]string }); is {
|
||||
def = append(def, svc.list()...)
|
||||
}
|
||||
}(svc)
|
||||
|
||||
return def
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Grant applies one or more RBAC rules
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (svc accessControl) Grant(ctx context.Context, rr ...*rbac.Rule) error {
|
||||
if !svc.CanGrant(ctx) {
|
||||
// @todo should be altered to check grant permissions PER resource
|
||||
return AccessControlErrNotAllowedToSetPermissions()
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
err := rbacResourceValidator(r.Resource, r.Operation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if err := svc.rbac.Grant(ctx, rr...); err != nil {
|
||||
return AccessControlErrGeneric().Wrap(err)
|
||||
}
|
||||
|
||||
svc.logGrants(ctx, rr)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function is auto-generated
|
||||
func (svc accessControl) logGrants(ctx context.Context, rr []*rbac.Rule) {
|
||||
if svc.actionlog == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
g := AccessControlActionGrant(&accessControlActionProps{r})
|
||||
g.log = r.String()
|
||||
g.resource = r.Resource
|
||||
|
||||
svc.actionlog.Record(ctx, g.ToAction())
|
||||
}
|
||||
}
|
||||
|
||||
// FindRulesByRoleID find all rules for a specific role
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (svc accessControl) FindRulesByRoleID(ctx context.Context, roleID uint64) (rbac.RuleSet, error) {
|
||||
if !svc.CanGrant(ctx) {
|
||||
return nil, AccessControlErrNotAllowedToSetPermissions()
|
||||
}
|
||||
|
||||
return svc.rbac.FindRulesByRoleID(roleID), nil
|
||||
}
|
||||
|
||||
// CloneRulesByRoleID clone all rules of a Role S to a specific Role T
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (svc accessControl) CloneRulesByRoleID(ctx context.Context, fromRoleID uint64, toRoleID ...uint64) error {
|
||||
if !svc.CanGrant(ctx) {
|
||||
return AccessControlErrNotAllowedToSetPermissions()
|
||||
}
|
||||
|
||||
return svc.rbac.CloneRulesByRoleID(ctx, fromRoleID, toRoleID...)
|
||||
}
|
||||
|
||||
{{- range .operations }}
|
||||
// {{ .checkFuncName }} checks if current user can {{ lower .description }}
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (svc accessControl) {{ .checkFuncName }}(ctx context.Context{{ if not .component }}, r *{{ .goType }}{{ end }}) bool {
|
||||
{{- if .component }}r := &{{ .goType }}{}{{ end }}
|
||||
return svc.can(ctx, {{ printf "%q" .op }}, r)
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
// rbacResourceValidator validates known component's resource by routing it to the appropriate validator
|
||||
//
|
||||
// This function is auto-generated
|
||||
func rbacResourceValidator(r string, oo ...string) error {
|
||||
switch rbac.ResourceType(r) {
|
||||
{{- range .validation }}
|
||||
case {{ .const }}:
|
||||
return {{ .funcName }}(r, oo...)
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown resource type '%q'", r)
|
||||
}
|
||||
|
||||
// rbacResourceOperations returns defined operations for a requested resource
|
||||
//
|
||||
// This function is auto-generated
|
||||
func rbacResourceOperations(r string) map[string]bool {
|
||||
switch rbac.ResourceType(r) {
|
||||
{{- range .validation }}
|
||||
case {{ .const }}:
|
||||
return map[string]bool{
|
||||
{{- range .operations }}
|
||||
{{ printf "%q" . }}: true,
|
||||
{{- end }}
|
||||
}
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
{{- range .validation }}
|
||||
|
||||
// {{ .funcName }} checks validity of RBAC resource and operations
|
||||
//
|
||||
// Can be called without operations to check for validity of resource string only
|
||||
//
|
||||
// This function is auto-generated
|
||||
func {{ .funcName }}(r string, oo ...string) error {
|
||||
if !strings.HasPrefix(r, {{ .const }}) {
|
||||
// expecting resource to always include path
|
||||
return fmt.Errorf("invalid resource type")
|
||||
}
|
||||
|
||||
defOps := rbacResourceOperations(r)
|
||||
for _, o := range oo {
|
||||
if !defOps[o] {
|
||||
return fmt.Errorf("invalid operation '%s' for {{ .label }} resource", o)
|
||||
}
|
||||
}
|
||||
|
||||
{{ if .references }}
|
||||
const sep = "/"
|
||||
var (
|
||||
pp = strings.Split(strings.Trim(r[len({{ .const }}):], sep), sep)
|
||||
prc = []string{
|
||||
{{- range .references }}
|
||||
{{ printf "%q" . }},
|
||||
{{- end }}
|
||||
}
|
||||
)
|
||||
|
||||
if len(pp) != len(prc) {
|
||||
return fmt.Errorf("invalid resource path structure")
|
||||
}
|
||||
|
||||
for i := 0; i < len(pp); i++ {
|
||||
if pp[i] != "*" {
|
||||
if i > 0 && pp[i-1] == "*" {
|
||||
return fmt.Errorf("invalid path wildcard level (%d) for {{ .label }} resource", i)
|
||||
}
|
||||
|
||||
if _, err := cast.ToUint64E(pp[i]); err != nil {
|
||||
return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
{{- end }}
|
||||
return nil
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package {{ .package }}
|
||||
|
||||
{{ template "gocode/header-gentext.tpl" }}
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type (
|
||||
// Component struct serves as a virtual resource type for the {{ .cmpIdent }} component
|
||||
//
|
||||
// This struct is auto-generated
|
||||
Component struct {}
|
||||
)
|
||||
|
||||
var (
|
||||
{{/*
|
||||
making sure that generated code does not break
|
||||
when these packages are not used
|
||||
*/}}
|
||||
_ = fmt.Printf
|
||||
_ = strconv.FormatUint
|
||||
)
|
||||
|
||||
const (
|
||||
{{- range .types }}
|
||||
{{ .const }} = {{ printf "%q" .type }}
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
|
||||
{{- range .types }}
|
||||
|
||||
// RbacResource returns string representation of RBAC resource for {{ .goType }} by calling {{ .resFunc }} fn
|
||||
//
|
||||
// RBAC resource is in the {{ .type }}/... format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (r {{ .goType }}) RbacResource() string {
|
||||
return {{ .resFunc }}({{ if not .component }}{{ range .references }}r.{{ . }},{{ end }}{{ end }})
|
||||
}
|
||||
|
||||
// {{ .resFunc }} returns string representation of RBAC resource for {{ .goType }}
|
||||
//
|
||||
// RBAC resource is in the {{ .type }}/{{- if .references }}...{{ end }} format
|
||||
//
|
||||
// This function is auto-generated
|
||||
func {{ .resFunc }}({{ if not .component }}{{ range .references }}{{ . }} uint64,{{ end }}{{ end }}) string {
|
||||
{{- if .references }}
|
||||
cpts := []interface{{"{}"}}{{"{"}}{{ .goType }}ResourceType{{"}"}}
|
||||
{{- range .references }}
|
||||
if {{ . }} != 0 {
|
||||
cpts = append(cpts, strconv.FormatUint({{ . }}, 10))
|
||||
} else {
|
||||
cpts = append(cpts, "*")
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
return fmt.Sprintf({{ .tplFunc }}(), cpts...)
|
||||
{{- else }}
|
||||
return {{ .goType }}ResourceType + "/"
|
||||
{{- end }}
|
||||
|
||||
}
|
||||
|
||||
func {{ .tplFunc }}() string {
|
||||
{{- if .references }}
|
||||
return "%s
|
||||
{{- range .references }}/%s{{- end }}"
|
||||
|
||||
{{- else }}
|
||||
return "%s"
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package codegen
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/codegen/schema"
|
||||
"github.com/cortezaproject/corteza-server/app"
|
||||
)
|
||||
|
||||
rbacAccessControl:
|
||||
[...schema.#codegen] &
|
||||
[
|
||||
for cmp in app.corteza.components {
|
||||
template: "gocode/rbac/access_control.go.tpl"
|
||||
output: "\(cmp.ident)/service/access_control.gen.go"
|
||||
payload: {
|
||||
imports: [
|
||||
"github.com/cortezaproject/corteza-server/\(cmp.ident)/types",
|
||||
]
|
||||
package: "service"
|
||||
|
||||
// All possible RBAC operations on component and resources
|
||||
// flattened
|
||||
operations: [
|
||||
for res in cmp.resources for op in res.rbac.operations {
|
||||
"op": op.handle
|
||||
"const": "types.\(res.expIdent)ResourceType"
|
||||
"ctor": "types.\(res.expIdent)RbacResource(\(len(res.rbac.resource.references)*"0,"))"
|
||||
"goType": res.goType
|
||||
"description": op.description
|
||||
"checkFuncName": op.checkFuncName
|
||||
},
|
||||
for op in cmp.rbac.operations {
|
||||
"op": op.handle
|
||||
"const": "types.ComponentResourceType"
|
||||
"ctor": "types.ComponentRbacResource()"
|
||||
"goType": "types.Component"
|
||||
"description": op.description
|
||||
"checkFuncName": op.checkFuncName
|
||||
"component": true
|
||||
},
|
||||
]
|
||||
|
||||
// Operation/resource validators, grouped by resource
|
||||
validation: [
|
||||
for res in cmp.resources {
|
||||
"label": res.ident
|
||||
"const": "types.\(res.expIdent)ResourceType"
|
||||
"funcName": "rbac\(res.expIdent)ResourceValidator"
|
||||
"references": res.rbac.resource.references
|
||||
"operations": [ for op in res.rbac.operations {op.handle}]
|
||||
},
|
||||
{
|
||||
"label": "\(cmp.ident) component"
|
||||
"const": "types.ComponentResourceType"
|
||||
"funcName": "rbacComponentResourceValidator"
|
||||
"operations": [ for op in cmp.rbac.operations {op.handle}]
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
package codegen
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza-server/app"
|
||||
"github.com/cortezaproject/corteza-server/codegen/schema"
|
||||
"strings"
|
||||
)
|
||||
|
||||
rbacTypes:
|
||||
[...schema.#codegen] &
|
||||
[
|
||||
for cmp in app.corteza.components {
|
||||
template: "gocode/rbac/types.go.tpl"
|
||||
output: "\(cmp.ident)/types/rbac.gen.go"
|
||||
payload: {
|
||||
package: "types"
|
||||
|
||||
cmpIdent: cmp.ident
|
||||
// Operation/resource validators, grouped by resource
|
||||
types: [
|
||||
for res in cmp.resources {
|
||||
"const": "\(res.expIdent)ResourceType"
|
||||
"type": res.rbac.resource.type
|
||||
"resFunc": "\(res.expIdent)RbacResource"
|
||||
"tplFunc": "\(res.expIdent)RbacResourceTpl"
|
||||
"attFunc": "\(res.expIdent)RbacAttributes"
|
||||
"goType": res.expIdent
|
||||
|
||||
"references": [ for field in res.rbac.resource.references { strings.ToTitle(field) } ]
|
||||
},
|
||||
{
|
||||
"const": "ComponentResourceType"
|
||||
"type": cmp.rbac.resource.type
|
||||
"resFunc": "ComponentRbacResource"
|
||||
"tplFunc": "ComponentRbacResourceTpl"
|
||||
"attFunc": "ComponentRbacAttributes"
|
||||
"goType": "Component"
|
||||
"component": true
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,13 @@
|
||||
package schema
|
||||
|
||||
#codegen: {
|
||||
template: string
|
||||
output: string
|
||||
|
||||
syntax: "go"
|
||||
if output =~ "\\.adoc" {
|
||||
syntax: "adoc"
|
||||
}
|
||||
|
||||
payload: _
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/cli"
|
||||
)
|
||||
|
||||
type (
|
||||
task struct {
|
||||
Template string `json:"template"`
|
||||
Output string `json:"output"`
|
||||
Syntax string `json:"syntax"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
verbose bool
|
||||
showHelp bool
|
||||
tplRootPath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&showHelp, "h", false, "show help")
|
||||
flag.BoolVar(&verbose, "v", false, "be verbose")
|
||||
flag.StringVar(&tplRootPath, "p", "codegen/assets/templates", "location of the template files")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
// Takes JSON input with codegen tasks and definitions and generates files
|
||||
func main() {
|
||||
if showHelp {
|
||||
flag.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var (
|
||||
input = json.NewDecoder(os.Stdin)
|
||||
tasks = make([]*task, 0)
|
||||
tpl *template.Template
|
||||
err error
|
||||
)
|
||||
|
||||
print("Waiting for stdin ...\n")
|
||||
if err = input.Decode(&tasks); err != nil {
|
||||
cli.HandleError(fmt.Errorf("failed to decode input from standard input: %v", err))
|
||||
}
|
||||
|
||||
if tpl, err = LoadTemplates(BaseTemplate(), tplRootPath); err != nil {
|
||||
cli.HandleError(fmt.Errorf("failed to load templates: %v", err))
|
||||
}
|
||||
|
||||
for _, j := range tasks {
|
||||
switch j.Syntax {
|
||||
case "go":
|
||||
print(fmt.Sprintf("generating %s (from %s) ...", j.Output, j.Template))
|
||||
if err = GoTemplate(j.Output, tpl.Lookup(j.Template), j.Payload); err != nil {
|
||||
cli.HandleError(fmt.Errorf("failed to write template: %v", err))
|
||||
}
|
||||
print("done\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func print(msg string) {
|
||||
if verbose {
|
||||
_, _ = fmt.Fprint(os.Stderr, msg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
)
|
||||
|
||||
func BaseTemplate() *template.Template {
|
||||
return template.New("").
|
||||
Funcs(sprig.TxtFuncMap())
|
||||
}
|
||||
|
||||
func LoadTemplates(rTpl *template.Template, rootDir string) (*template.Template, error) {
|
||||
cleanRoot := filepath.Clean(rootDir)
|
||||
pfx := len(cleanRoot) + 1
|
||||
|
||||
return rTpl, filepath.Walk(cleanRoot, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() || !strings.HasSuffix(path, ".tpl") || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name := path[pfx:]
|
||||
rTpl, err = rTpl.New(name).Parse(string(b))
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func GoTemplate(dst string, tpl *template.Template, payload interface{}) (err error) {
|
||||
var output io.WriteCloser
|
||||
buf := bytes.Buffer{}
|
||||
|
||||
if tpl == nil {
|
||||
return fmt.Errorf("could not find template for %s", dst)
|
||||
}
|
||||
|
||||
if err := tpl.Execute(&buf, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmtsrc, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%s fmt warn: %v\n", dst, err)
|
||||
|
||||
err = nil
|
||||
fmtsrc = buf.Bytes()
|
||||
}
|
||||
|
||||
if dst == "" || dst == "-" {
|
||||
output = os.Stdout
|
||||
} else {
|
||||
if output, err = os.Create(dst); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer output.Close()
|
||||
}
|
||||
|
||||
if _, err = output.Write(fmtsrc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user