Skip to content

Commit

Permalink
fix(core): dev selectors employ flattening from platform instead of jq (
Browse files Browse the repository at this point in the history
#411)

Related to #125
Related to #410
  • Loading branch information
jakedoublev authored Nov 4, 2024
1 parent 2ffd3c7 commit 57966ff
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 261 deletions.
2 changes: 2 additions & 0 deletions .github/spellcheck.ignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ TODO
TUI
URI
Unassign
abc
acmeco
args
attr
Expand Down Expand Up @@ -85,6 +86,7 @@ quickstart
resm
resolvers
scs
sel
sm
stdin
stdout
Expand Down
98 changes: 20 additions & 78 deletions cmd/dev-selectors.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package cmd

import (
"encoding/json"
"fmt"

"github.com/golang-jwt/jwt"
"github.com/opentdf/otdfctl/pkg/cli"
"github.com/opentdf/otdfctl/pkg/handlers"
"github.com/opentdf/otdfctl/pkg/man"
"github.com/opentdf/platform/protocol/go/policy"
"github.com/spf13/cobra"
)

Expand All @@ -20,38 +17,15 @@ func dev_selectorsGen(cmd *cobra.Command, args []string) {
defer h.Close()

subject := c.Flags.GetRequiredString("subject")
contextType := c.Flags.GetRequiredString("type")

var value any
//nolint:gocritic,nestif // this is more readable than a switch statement
if contextType == "json" || contextType == "" {
if err := json.Unmarshal([]byte(subject), &value); err != nil {
cli.ExitWithError(fmt.Sprintf("Could not unmarshal JSON subject context input: %s", subject), err)
}
} else if contextType == "jwt" {
// get the payload from the decoded JWT
token, _, err := new(jwt.Parser).ParseUnverified(subject, jwt.MapClaims{})
if err != nil {
cli.ExitWithError("Failed to parse JWT token", err)
}

if claims, ok := token.Claims.(jwt.MapClaims); ok {
value = claims
} else {
cli.ExitWithError("Failed to get claims from JWT token", nil)
}
} else {
cli.ExitWithError("Invalid subject context type. Must be of type: [json, jwt]", nil)
}

result, err := handlers.ProcessSubjectContext(value, "", []*policy.SubjectProperty{})
flattened, err := handlers.FlattenSubjectContext(subject)
if err != nil {
cli.ExitWithError("Failed to process subject context keys and values", err)
cli.ExitWithError("Failed to parse subject context keys and values", err)
}

rows := [][]string{}
for _, r := range result {
rows = append(rows, []string{r.GetExternalSelectorValue(), r.GetExternalValue()})
for _, item := range flattened {
rows = append(rows, []string{item.Key, fmt.Sprintf("%v", item.Value)})
}

t := cli.NewTabular(rows...)
Expand All @@ -64,46 +38,28 @@ func dev_selectorsTest(cmd *cobra.Command, args []string) {
defer h.Close()

subject := c.Flags.GetRequiredString("subject")
contextType := c.Flags.GetRequiredString("type")
selectors = c.Flags.GetStringSlice("selectors", selectors, cli.FlagsStringSliceOptions{Min: 1})

var value any
//nolint:gocritic,nestif // this is more readable than a switch statement
if contextType == "json" || contextType == "" {
if err := json.Unmarshal([]byte(subject), &value); err != nil {
cli.ExitWithError(fmt.Sprintf("Could not unmarshal JSON subject context input: %s", subject), err)
}
} else if contextType == "jwt" {
token, _, err := new(jwt.Parser).ParseUnverified(subject, jwt.MapClaims{})
if err != nil {
cli.ExitWithError("Failed to parse JWT token", err)
}
selectors = c.Flags.GetStringSlice("selector", selectors, cli.FlagsStringSliceOptions{Min: 1})

if claims, ok := token.Claims.(jwt.MapClaims); ok {
value = claims
} else {
cli.ExitWithError("Failed to get claims from JWT token", nil)
}
} else {
cli.ExitWithError("Invalid subject context type. Must be of type: [json, jwt]", nil)
}

result, err := handlers.TestSubjectContext(value, selectors)
flattened, err := handlers.FlattenSubjectContext(subject)
if err != nil {
cli.ExitWithError("Failed to process subject context keys and values", err)
}

rows := [][]string{}
for _, r := range result {
rows = append(rows, []string{r.GetExternalSelectorValue(), r.GetExternalValue()})
for _, item := range flattened {
for _, selector := range selectors {
if selector == item.Key {
rows = append(rows, []string{item.Key, fmt.Sprintf("%v", item.Value)})
}
}
}

t := cli.NewTabular(rows...)
cli.PrintSuccessTable(cmd, "", t)
}

func init() {
genCmd := man.Docs.GetCommand("dev/selectors/gen",
genCmd := man.Docs.GetCommand("dev/selectors/generate",
man.WithRun(dev_selectorsGen),
)
genCmd.Flags().StringP(
Expand All @@ -112,12 +68,6 @@ func init() {
genCmd.GetDocFlag("subject").Default,
genCmd.GetDocFlag("subject").Description,
)
genCmd.Flags().StringP(
genCmd.GetDocFlag("type").Name,
genCmd.GetDocFlag("type").Shorthand,
genCmd.GetDocFlag("type").Default,
genCmd.GetDocFlag("type").Description,
)

testCmd := man.Docs.GetCommand("dev/selectors/test",
man.WithRun(dev_selectorsTest),
Expand All @@ -128,26 +78,18 @@ func init() {
testCmd.GetDocFlag("subject").Default,
testCmd.GetDocFlag("subject").Description,
)
testCmd.Flags().StringP(
testCmd.GetDocFlag("type").Name,
testCmd.GetDocFlag("type").Shorthand,
testCmd.GetDocFlag("type").Default,
testCmd.GetDocFlag("type").Description,
)
testCmd.Flags().StringArrayP(
testCmd.Flags().StringSliceVarP(
&selectors,
testCmd.GetDocFlag("selector").Name,
testCmd.GetDocFlag("selector").Shorthand,
[]string{},
testCmd.GetDocFlag("selector").Description,
)

// TODO: put back dev selectors command once the flattening lib is provided by platform
// issue: https://github.com/opentdf/otdfctl/issues/125

// doc := man.Docs.GetCommand("dev/selectors",
// man.WithSubcommands(genCmd, testCmd),
// )
doc := man.Docs.GetCommand("dev/selectors",
man.WithSubcommands(genCmd, testCmd),
)

// dev_selectorsCmd = &doc.Command
// devCmd.AddCommand(dev_selectorsCmd)
dev_selectorsCmd := &doc.Command
devCmd.AddCommand(dev_selectorsCmd)
}
12 changes: 0 additions & 12 deletions cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,6 @@ func getMetadataRows(m *common.Metadata) [][]string {
return nil
}

// TODO can we use it or remove it?
// func unMarshalMetadata(m string) *common.MetadataMutable {
// if m != "" {
// metadata := &common.MetadataMutable{}
// if err := json.Unmarshal([]byte(m), metadata); err != nil {
// cli.ExitWithError("Failed to unmarshal metadata", err)
// }
// return metadata
// }
// return nil
// }

const keyValLength = 2

func getMetadataMutable(labels []string) *common.MetadataMutable {
Expand Down
7 changes: 4 additions & 3 deletions docs/man/dev/selectors/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
title: Selectors
command:
name: selectors
aliases:
- sel
---

Commands to develop selectors, with [jq syntax](https://jqlang.github.io/jq/manual/) for utilization
within Subject Condition Sets to parse some external Subject Context into mapped Attribute
Values.
Commands to generate and test selectors on Subject Entity Representations. For more information, see the help manual for each subcommand
or additional context within Subject Condition Sets.
20 changes: 0 additions & 20 deletions docs/man/dev/selectors/gen.md

This file was deleted.

45 changes: 45 additions & 0 deletions docs/man/dev/selectors/generate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: Generate a set of selector expressions for keys and values of a Subject Context
command:
name: generate
aliases:
- gen
flags:
- name: subject
shorthand: s
description: A Subject Context string (JSON or JWT, default JSON)
default: ''
---

Take in an Entity Representation as a JWT or JSON object, such as that provided by
an Identity Provider (idP), LDAP, or OIDC Access Token JWT, and generate
sample selectors employing [flattening syntax](#flattening-syntax) to utilize within
within Subject Condition Sets that resolve an external Subject Context into mapped Attribute
Values.

# Flattening-syntax

The platform maintains a very simple flattening library such that the below structure flattens into the key/value pairs beneath.

Subject input (`--subject`):

```json
{
"key": "abc",
"something": {
"nested": "nested_value",
"list": ["item_1", "item_2"]
}
}
```

Generated Selectors:

| Selector | Value | Significance |
| -------------------- | -------------- | ------------------------- |
| ".key" | "abc" | specified field |
| ".something.nested" | "nested_value" | nested field |
| ".something.list[0]" | "item_1" | first index specifically |
| ".something.list[]" | "item_1" | any index in the list |
| ".something.list[1]" | "item_2" | second index specifically |
| ".something.list[]" | "item_2" | any index in the list |
46 changes: 36 additions & 10 deletions docs/man/dev/selectors/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,44 @@ command:
flags:
- name: subject
shorthand: s
description: A Subject Context string (JSON or JWT, default JSON)
description: A Subject Context string (JSON or JWT, auto-detected)
default: ''
- name: type
shorthand: t
description: 'The type of the Subject Context: [json, jwt]'
default: json
- name: selector
shorthand: x
description: 'Individual selectors to test against the Subject Context (i.e. .key, .example[1].group)'
description: "Individual selectors to test against the Subject Context (i.e. '.key,.realm_access.roles[]')"
---

Test a given representation of some Subject Context, such as that provided by
an Identity Provider (idP), LDAP, or OIDC Access Token JWT, against provided [jq syntax
'selector' expressions](https://jqlang.github.io/jq/manual/) to validate their resolution
to field values on the Subject Context.
Test a subject Entity Representation as a JWT or JSON object, such as that provided by
an Identity Provider (idP), LDAP, or OIDC Access Token JWT, against provided selectors employing [flattening syntax](#flattening-syntax) to
validate their resolution to field values on the subject's entity representation.

# Flattening-syntax

The platform maintains a very simple flattening library such that the below structure flattens into the key/value pairs beneath.

Original:

```json
{
"key": "abc",
"something": {
"nested": "nested_value",
"list": ["item_1", "item_2"]
}
}
```

Flattened:

| Selector | Value | Significance |
| -------------------- | -------------- | ------------------------- |
| ".key" | "abc" | specified field |
| ".something.nested" | "nested_value" | nested field |
| ".something.list[0]" | "item_1" | first index specifically |
| ".something.list[]" | "item_1" | any index in the list |
| ".something.list[1]" | "item_2" | second index specifically |
| ".something.list[]" | "item_2" | any index in the list |

Testing the example above with `--selector '.key'` would find the value `abc` on the `key` field and return it in the command output.

Testing the example above with `--selector .values[]` would not find a list at a field named `values` because it is missing entirely from the input object.
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/gabriel-vasile/mimetype v1.4.5
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.6.0
github.com/itchyny/gojq v0.12.16
github.com/opentdf/platform/lib/flattening v0.1.1
github.com/opentdf/platform/protocol/go v0.2.18
github.com/opentdf/platform/sdk v0.3.15
github.com/spf13/cobra v1.8.1
Expand Down Expand Up @@ -62,7 +62,6 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect
Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g=
github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM=
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc=
github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
Expand Down Expand Up @@ -221,6 +217,8 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opentdf/platform/lib/fixtures v0.2.7 h1:2LxWmLBBISONVJnVDH8yMsV72VHQyirua0DwDBBoq+g=
github.com/opentdf/platform/lib/fixtures v0.2.7/go.mod h1:8yCSe+oUzW9jbM573r9qgE68rjwDMNzktObiGVsO/W8=
github.com/opentdf/platform/lib/flattening v0.1.1 h1:la1f6PcRsc+yLH8+9UEr0ux6IRKu+6+oMaMVt05+8HU=
github.com/opentdf/platform/lib/flattening v0.1.1/go.mod h1:eyG7pe5UZlV+GI5/CymQD3xTAJxNhnP9M4QnBzaad1M=
github.com/opentdf/platform/lib/ocrypto v0.1.6 h1:rd4ctCZOE/c3qDJORtkSK9tw6dEXb+jbJXRRk4LcxII=
github.com/opentdf/platform/lib/ocrypto v0.1.6/go.mod h1:ne+l8Q922OdzA0xesK3XJmfECBnn5vLSGYU3/3OhiHM=
github.com/opentdf/platform/protocol/go v0.2.18 h1:s+TVZkOPGCzy7WyObtJWJNaFeOGDUTuSmAsq3omvugY=
Expand Down
Loading

0 comments on commit 57966ff

Please sign in to comment.