Skip to content

Commit fa5f959

Browse files
authored
feat(demo): adds dev subcommand for jq selectors to generate and test selectors on a subject context JSON or JWT (#91)
Related to: opentdf/platform#477
1 parent 1a03039 commit fa5f959

File tree

9 files changed

+380
-7
lines changed

9 files changed

+380
-7
lines changed

cmd/dev-selectors.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/golang-jwt/jwt"
8+
"github.com/opentdf/otdfctl/pkg/cli"
9+
"github.com/opentdf/otdfctl/pkg/handlers"
10+
"github.com/opentdf/otdfctl/pkg/man"
11+
"github.com/opentdf/platform/protocol/go/policy"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
var (
16+
selectors []string
17+
18+
dev_selectorsCmd *cobra.Command
19+
)
20+
21+
func dev_selectorsGen(cmd *cobra.Command, args []string) {
22+
h := cli.NewHandler(cmd)
23+
defer h.Close()
24+
25+
flagHelper := cli.NewFlagHelper(cmd)
26+
subject := flagHelper.GetRequiredString("subject")
27+
contextType := flagHelper.GetRequiredString("type")
28+
29+
var value any
30+
if contextType == "json" || contextType == "" {
31+
if err := json.Unmarshal([]byte(subject), &value); err != nil {
32+
cli.ExitWithError(fmt.Sprintf("Could not unmarshal JSON subject context input: %s", subject), err)
33+
}
34+
} else if contextType == "jwt" {
35+
// get the payload from the decoded JWT
36+
token, _, err := new(jwt.Parser).ParseUnverified(subject, jwt.MapClaims{})
37+
if err != nil {
38+
cli.ExitWithError("Failed to parse JWT token", err)
39+
}
40+
41+
if claims, ok := token.Claims.(jwt.MapClaims); ok {
42+
value = claims
43+
} else {
44+
cli.ExitWithError("Failed to get claims from JWT token", nil)
45+
}
46+
} else {
47+
cli.ExitWithError("Invalid subject context type. Must be of type: [json, jwt]", nil)
48+
}
49+
50+
result, err := handlers.ProcessSubjectContext(value, "", []*policy.SubjectProperty{})
51+
if err != nil {
52+
cli.ExitWithError("Failed to process subject context keys and values", err)
53+
}
54+
55+
rows := [][]string{}
56+
for _, r := range result {
57+
rows = append(rows, []string{r.ExternalField, r.ExternalValue})
58+
}
59+
60+
t := cli.NewTabular().Rows(rows...)
61+
cli.PrintSuccessTable(cmd, "", t)
62+
}
63+
64+
func dev_selectorsTest(cmd *cobra.Command, args []string) {
65+
h := cli.NewHandler(cmd)
66+
defer h.Close()
67+
68+
flagHelper := cli.NewFlagHelper(cmd)
69+
subject := flagHelper.GetRequiredString("subject")
70+
contextType := flagHelper.GetRequiredString("type")
71+
selectors := flagHelper.GetStringSlice("selectors", selectors, cli.FlagHelperStringSliceOptions{Min: 1})
72+
73+
var value any
74+
if contextType == "json" || contextType == "" {
75+
if err := json.Unmarshal([]byte(subject), &value); err != nil {
76+
cli.ExitWithError(fmt.Sprintf("Could not unmarshal JSON subject context input: %s", subject), err)
77+
}
78+
} else if contextType == "jwt" {
79+
token, _, err := new(jwt.Parser).ParseUnverified(subject, jwt.MapClaims{})
80+
if err != nil {
81+
cli.ExitWithError("Failed to parse JWT token", err)
82+
}
83+
84+
if claims, ok := token.Claims.(jwt.MapClaims); ok {
85+
value = claims
86+
} else {
87+
cli.ExitWithError("Failed to get claims from JWT token", nil)
88+
}
89+
} else {
90+
cli.ExitWithError("Invalid subject context type. Must be of type: [json, jwt]", nil)
91+
}
92+
93+
result, err := handlers.TestSubjectContext(value, selectors)
94+
if err != nil {
95+
cli.ExitWithError("Failed to process subject context keys and values", err)
96+
}
97+
98+
rows := [][]string{}
99+
for _, r := range result {
100+
rows = append(rows, []string{r.ExternalField, r.ExternalValue})
101+
}
102+
103+
t := cli.NewTabular().Rows(rows...)
104+
cli.PrintSuccessTable(cmd, "", t)
105+
}
106+
107+
func init() {
108+
genCmd := man.Docs.GetCommand("dev/selectors/gen",
109+
man.WithRun(dev_selectorsGen),
110+
)
111+
genCmd.Flags().StringP(
112+
genCmd.GetDocFlag("subject").Name,
113+
genCmd.GetDocFlag("subject").Shorthand,
114+
genCmd.GetDocFlag("subject").Default,
115+
genCmd.GetDocFlag("subject").Description,
116+
)
117+
genCmd.Flags().StringP(
118+
genCmd.GetDocFlag("type").Name,
119+
genCmd.GetDocFlag("type").Shorthand,
120+
genCmd.GetDocFlag("type").Default,
121+
genCmd.GetDocFlag("type").Description,
122+
)
123+
124+
testCmd := man.Docs.GetCommand("dev/selectors/test",
125+
man.WithRun(dev_selectorsTest),
126+
)
127+
testCmd.Flags().StringP(
128+
testCmd.GetDocFlag("subject").Name,
129+
testCmd.GetDocFlag("subject").Shorthand,
130+
testCmd.GetDocFlag("subject").Default,
131+
testCmd.GetDocFlag("subject").Description,
132+
)
133+
testCmd.Flags().StringP(
134+
testCmd.GetDocFlag("type").Name,
135+
testCmd.GetDocFlag("type").Shorthand,
136+
testCmd.GetDocFlag("type").Default,
137+
testCmd.GetDocFlag("type").Description,
138+
)
139+
testCmd.Flags().StringArrayVarP(
140+
&selectors,
141+
testCmd.GetDocFlag("selector").Name,
142+
testCmd.GetDocFlag("selector").Shorthand,
143+
[]string{},
144+
testCmd.GetDocFlag("selector").Description,
145+
)
146+
147+
doc := man.Docs.GetCommand("dev/selectors",
148+
man.WithSubcommands(genCmd, testCmd),
149+
)
150+
151+
dev_selectorsCmd = &doc.Command
152+
devCmd.AddCommand(dev_selectorsCmd)
153+
}

cmd/dev.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import (
1313
"github.com/spf13/cobra"
1414
)
1515

16+
// devCmd is the command for playground-style development
17+
var devCmd = man.Docs.GetCommand("dev")
18+
1619
func dev_designSystem(cmd *cobra.Command, args []string) {
1720
fmt.Printf("Design system\n")
1821
fmt.Printf("=============\n\n")
@@ -115,13 +118,9 @@ func injectLabelFlags(cmd *cobra.Command, isUpdate bool) {
115118
}
116119

117120
func init() {
118-
119121
designCmd := man.Docs.GetCommand("dev/design-system",
120122
man.WithRun(dev_designSystem),
121123
)
122-
123-
cmd := man.Docs.GetCommand("dev",
124-
man.WithSubcommands(designCmd),
125-
)
126-
rootCmd.AddCommand(&cmd.Command)
124+
devCmd.AddCommand(&designCmd.Command)
125+
rootCmd.AddCommand(&devCmd.Command)
127126
}

docs/man/dev/selectors/_index.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: Selectors
3+
command:
4+
name: selectors
5+
---
6+
7+
Commands to develop selectors, with [jq syntax](https://jqlang.github.io/jq/manual/) for utilization
8+
within Subject Condition Sets to parse some external Subject Context into mapped Attribute
9+
Values.

docs/man/dev/selectors/gen.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
title: Generate a set of selector expressions for keys and values of a Subject Context
3+
command:
4+
name: gen
5+
flags:
6+
- name: subject
7+
shorthand: s
8+
description: A Subject Context string (JSON or JWT, default JSON)
9+
default: ''
10+
- name: type
11+
shorthand: t
12+
description: 'The type of the Subject Context: [json, jwt]'
13+
default: json
14+
---
15+
16+
Take in a representation of some Subject Context, such as that provided by
17+
an Identity Provider (idP), LDAP, or OIDC Access Token JWT, and generate
18+
sample [jq syntax expressions](https://jqlang.github.io/jq/manual/) to employ
19+
within Subject Condition Sets to parse that external Subject Context into mapped Attribute
20+
Values.

docs/man/dev/selectors/test.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
title: Test resolution of a set of selector expressions for keys and values of a Subject Context.
3+
command:
4+
name: test
5+
flags:
6+
- name: subject
7+
shorthand: s
8+
description: A Subject Context string (JSON or JWT, default JSON)
9+
default: ''
10+
- name: type
11+
shorthand: t
12+
description: 'The type of the Subject Context: [json, jwt]'
13+
default: json
14+
- name: selector
15+
shorthand: x
16+
description: 'Individual selectors to test against the Subject Context (i.e. .key, .example[1].group)'
17+
---
18+
19+
Test a given representation of some Subject Context, such as that provided by
20+
an Identity Provider (idP), LDAP, or OIDC Access Token JWT, against provided [jq syntax
21+
'selector' expressions](https://jqlang.github.io/jq/manual/) to validate their resolution
22+
to field values on the Subject Context.

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ require (
99
github.com/charmbracelet/huh v0.3.0
1010
github.com/charmbracelet/lipgloss v0.10.0
1111
github.com/creasty/defaults v1.7.0
12+
github.com/golang-jwt/jwt v3.2.2+incompatible
1213
github.com/golang-jwt/jwt/v4 v4.5.0
14+
github.com/itchyny/gojq v0.12.15
1315
github.com/muesli/reflow v0.3.0
1416
github.com/opentdf/platform/protocol/go v0.0.0-20240328192545-ab689ebe9123
1517
github.com/opentdf/platform/sdk v0.0.0-20240328192545-ab689ebe9123
@@ -44,6 +46,7 @@ require (
4446
github.com/hashicorp/hcl v1.0.0 // indirect
4547
github.com/imfing/hextra v0.7.3 // indirect
4648
github.com/inconshreveable/mousetrap v1.1.0 // indirect
49+
github.com/itchyny/timefmt-go v0.1.5 // indirect
4750
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
4851
github.com/lestrrat-go/httpcc v1.0.1 // indirect
4952
github.com/lestrrat-go/httprc v1.0.5 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
8585
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
8686
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
8787
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
88+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
89+
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
8890
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
8991
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
9092
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@@ -106,6 +108,10 @@ github.com/imfing/hextra v0.7.3 h1:dVGA1NTcWe+FaUMdrawEypPfrrmulq5NoK0we3nC330=
106108
github.com/imfing/hextra v0.7.3/go.mod h1:cEfel3lU/bSx7lTE/+uuR4GJaphyOyiwNR3PTqFTXpI=
107109
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
108110
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
111+
github.com/itchyny/gojq v0.12.15 h1:WC1Nxbx4Ifw5U2oQWACYz32JK8G9qxNtHzrvW4KEcqI=
112+
github.com/itchyny/gojq v0.12.15/go.mod h1:uWAHCbCIla1jiNxmeT5/B5mOjSdfkCq6p8vxWg+BM10=
113+
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
114+
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
109115
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
110116
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
111117
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=

pkg/cli/table.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ package cli
33
import (
44
"github.com/charmbracelet/lipgloss"
55
"github.com/charmbracelet/lipgloss/table"
6+
"golang.org/x/term"
67
)
78

89
type Table table.Table
910

10-
var defaultTableWidth = 120
11+
var defaultTableWidth int
1112

1213
func NewTable() *table.Table {
1314
t := table.New()
@@ -27,3 +28,18 @@ func NewTable() *table.Table {
2728
}
2829
})
2930
}
31+
32+
func init() {
33+
// dynamically set the default table width based on terminal size breakpoints
34+
w, _, err := term.GetSize(0)
35+
if err != nil {
36+
w = 80
37+
}
38+
if w > 180 {
39+
defaultTableWidth = 180
40+
} else if w > 120 {
41+
defaultTableWidth = 120
42+
} else {
43+
defaultTableWidth = 80
44+
}
45+
}

0 commit comments

Comments
 (0)