diff --git a/pkg/envoy/yaml/doc.go b/pkg/envoy/yaml/doc.go index d516652be..f22d1277a 100644 --- a/pkg/envoy/yaml/doc.go +++ b/pkg/envoy/yaml/doc.go @@ -11,6 +11,7 @@ type ( Document struct { compose *compose roles roleSet + users userSet *rbacRules } ) @@ -28,8 +29,11 @@ func (doc *Document) UnmarshalYAML(n *yaml.Node) (err error) { switch k.Value { case "roles": return v.Decode(&doc.roles) - } + case "users": + return v.Decode(&doc.users) + + } return nil }) } diff --git a/pkg/envoy/yaml/testdata/user_1.yaml b/pkg/envoy/yaml/testdata/user_1.yaml new file mode 100644 index 000000000..3fb4861fd --- /dev/null +++ b/pkg/envoy/yaml/testdata/user_1.yaml @@ -0,0 +1,5 @@ +users: + u1: u1@example.tld + u2: + name: John Doe + email: jdoe@example.tld diff --git a/pkg/envoy/yaml/user.go b/pkg/envoy/yaml/user.go new file mode 100644 index 000000000..3794eba8b --- /dev/null +++ b/pkg/envoy/yaml/user.go @@ -0,0 +1,126 @@ +package yaml + +import ( + "github.com/cortezaproject/corteza-server/system/types" + "gopkg.in/yaml.v3" +) + +type ( + user struct { + // when user is at least partially defined + res *types.User `yaml:",inline"` + + // all known modules on a user + modules ComposeModuleSet + + // module's RBAC rules + *rbacRules + } + userSet []*user +) + +// UnmarshalYAML resolves set of user definitions, either sequence or map +// +// When resolving map, key is used as handle +// Also supporting { handle: name } definitions +// +func (wset *userSet) UnmarshalYAML(n *yaml.Node) error { + return each(n, func(k, v *yaml.Node) (err error) { + var ( + wrap = &user{} + ) + + if v == nil { + return nodeErr(n, "malformed user definition") + } + + wrap.res = &types.User{EmailConfirmed: true} + + if isKind(v, yaml.ScalarNode) { + + if k != nil { + wrap.res.Handle = k.Value + } + + if err = decodeScalar(v, "user email", &wrap.res.Email); err != nil { + return + } + + *wset = append(*wset, wrap) + return + } + + if err = v.Decode(&wrap.res); err != nil { + return + } + + if k != nil { + if wrap.res.Handle != "" { + return nodeErr(k, "cannot define handle in mapped user definition") + } + + if err = decodeRef(k, "user", &wrap.res.Handle); err != nil { + return err + } + } + + *wset = append(*wset, wrap) + return + }) +} + +//func (wset userSet) MarshalEnvoy() ([]envoy.Node, error) { +// // user usually have bunch of sub-resources defined +// nn := make([]envoy.Node, 0, len(wset)*10) +// +// for _, res := range wset { +// if tmp, err := res.MarshalEnvoy(); err != nil { +// return nil, err +// } else { +// nn = append(nn, tmp...) +// } +// } +// +// return nn, nil +//} + +func (wrap *user) UnmarshalYAML(n *yaml.Node) (err error) { + if !isKind(n, yaml.MappingNode) { + return nodeErr(n, "user definition must be a map or scalar") + } + + if wrap.res == nil { + wrap.res = &types.User{} + } + + if err = n.Decode(&wrap.res); err != nil { + return + } + + if wrap.rbacRules, err = decodeResourceAccessControl(types.UserRBACResource, n); err != nil { + return + } + + return nil +} + +//func (wrap user) MarshalEnvoy() ([]envoy.Node, error) { +// nn := make([]envoy.Node, 0, 1+len(wrap.modules)) +// nn = append(nn, &envoy.UserNode{Ns: wrap.res}) +// +// if tmp, err := wrap.modules.MarshalEnvoy(); err != nil { +// return nil, err +// } else { +// nn = append(nn, tmp...) +// } +// +// // @todo rbac +// +// //if tmp, err := wrap.rules.MarshalEnvoy(); err != nil { +// // return nil, err +// //} else { +// // nn = append(nn, tmp...) +// //} +// +// return nn, nil +//} diff --git a/pkg/envoy/yaml/user_test.go b/pkg/envoy/yaml/user_test.go new file mode 100644 index 000000000..0addef920 --- /dev/null +++ b/pkg/envoy/yaml/user_test.go @@ -0,0 +1,52 @@ +package yaml + +import ( + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + "testing" +) + +func TestUser_UnmarshalYAML(t *testing.T) { + var ( + parseString = func(src string) (*user, error) { + w := &user{} + return w, yaml.Unmarshal([]byte(src), w) + } + ) + + t.Run("empty", func(t *testing.T) { + req := require.New(t) + + w, err := parseString(``) + req.NoError(err) + req.NotNil(w) + req.Nil(w.res) + }) + + t.Run("simple name", func(t *testing.T) { + req := require.New(t) + + w, err := parseString(`{ name: Test }`) + req.NoError(err) + req.NotNil(w) + req.NotNil(w.res) + req.NotEmpty(w.res.Name) + }) + + t.Run("user 1", func(t *testing.T) { + req := require.New(t) + + doc, err := parseDocument("user_1") + req.NoError(err) + req.NotNil(doc) + req.Len(doc.users, 2) + req.NotNil(doc.users[0]) + req.Equal("u1@example.tld", doc.users[0].res.Email) + req.Equal("u1", doc.users[0].res.Handle) + req.NotNil(doc.users[1]) + req.Equal("John Doe", doc.users[1].res.Name) + req.Equal("jdoe@example.tld", doc.users[1].res.Email) + req.Equal(true, doc.users[1].res.EmailConfirmed) + req.Equal("u2", doc.users[1].res.Handle) + }) +}