Skip to content
153 changes: 153 additions & 0 deletions cmd/dev-selectors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
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"
)

var (
selectors []string

dev_selectorsCmd *cobra.Command
)

func dev_selectorsGen(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
subject := flagHelper.GetRequiredString("subject")
contextType := flagHelper.GetRequiredString("type")

var value any
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{})
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.ExternalField, r.ExternalValue})
}

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

func dev_selectorsTest(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
subject := flagHelper.GetRequiredString("subject")
contextType := flagHelper.GetRequiredString("type")
selectors := flagHelper.GetStringSlice("selectors", selectors, cli.FlagHelperStringSliceOptions{Min: 1})

var value any
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)
}

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)
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.ExternalField, r.ExternalValue})
}

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

func init() {
genCmd := man.Docs.GetCommand("dev/selectors/gen",
man.WithRun(dev_selectorsGen),
)
genCmd.Flags().StringP(
genCmd.GetDocFlag("subject").Name,
genCmd.GetDocFlag("subject").Shorthand,
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),
)
testCmd.Flags().StringP(
testCmd.GetDocFlag("subject").Name,
testCmd.GetDocFlag("subject").Shorthand,
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().StringArrayVarP(
&selectors,
testCmd.GetDocFlag("selector").Name,
testCmd.GetDocFlag("selector").Shorthand,
[]string{},
testCmd.GetDocFlag("selector").Description,
)

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

dev_selectorsCmd = &doc.Command
devCmd.AddCommand(dev_selectorsCmd)
}
11 changes: 5 additions & 6 deletions cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import (
"github.com/spf13/cobra"
)

// devCmd is the command for playground-style development
var devCmd = man.Docs.GetCommand("dev")

func dev_designSystem(cmd *cobra.Command, args []string) {
fmt.Printf("Design system\n")
fmt.Printf("=============\n\n")
Expand Down Expand Up @@ -115,13 +118,9 @@ func injectLabelFlags(cmd *cobra.Command, isUpdate bool) {
}

func init() {

designCmd := man.Docs.GetCommand("dev/design-system",
man.WithRun(dev_designSystem),
)

cmd := man.Docs.GetCommand("dev",
man.WithSubcommands(designCmd),
)
rootCmd.AddCommand(&cmd.Command)
devCmd.AddCommand(&designCmd.Command)
rootCmd.AddCommand(&devCmd.Command)
}
9 changes: 9 additions & 0 deletions docs/man/dev/selectors/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: Selectors
command:
name: selectors
---

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.
20 changes: 20 additions & 0 deletions docs/man/dev/selectors/gen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: Generate a set of selector expressions for keys and values of a Subject Context
command:
name: gen
flags:
- name: subject
shorthand: s
description: A Subject Context string (JSON or JWT, default JSON)
default: ''
- name: type
shorthand: t
description: 'The type of the Subject Context: [json, jwt]'
default: json
---

Take in a representation of some Subject Context, such as that provided by
an Identity Provider (idP), LDAP, or OIDC Access Token JWT, and generate
sample [jq syntax expressions](https://jqlang.github.io/jq/manual/) to employ
within Subject Condition Sets to parse that external Subject Context into mapped Attribute
Values.
22 changes: 22 additions & 0 deletions docs/man/dev/selectors/test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: Test resolution of a set of selector expressions for keys and values of a Subject Context.
command:
name: test
flags:
- name: subject
shorthand: s
description: A Subject Context string (JSON or JWT, default JSON)
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)'
---

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.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ require (
github.com/charmbracelet/huh v0.3.0
github.com/charmbracelet/lipgloss v0.10.0
github.com/creasty/defaults v1.7.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/itchyny/gojq v0.12.15
github.com/muesli/reflow v0.3.0
github.com/opentdf/platform/protocol/go v0.0.0-20240328192545-ab689ebe9123
github.com/opentdf/platform/sdk v0.0.0-20240328192545-ab689ebe9123
Expand Down Expand Up @@ -44,6 +46,7 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imfing/hextra v0.7.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.5 // 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.5 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
Expand All @@ -106,6 +108,10 @@ github.com/imfing/hextra v0.7.3 h1:dVGA1NTcWe+FaUMdrawEypPfrrmulq5NoK0we3nC330=
github.com/imfing/hextra v0.7.3/go.mod h1:cEfel3lU/bSx7lTE/+uuR4GJaphyOyiwNR3PTqFTXpI=
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.15 h1:WC1Nxbx4Ifw5U2oQWACYz32JK8G9qxNtHzrvW4KEcqI=
github.com/itchyny/gojq v0.12.15/go.mod h1:uWAHCbCIla1jiNxmeT5/B5mOjSdfkCq6p8vxWg+BM10=
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand Down
18 changes: 17 additions & 1 deletion pkg/cli/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package cli
import (
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
"golang.org/x/term"
)

type Table table.Table

var defaultTableWidth = 120
var defaultTableWidth int

func NewTable() *table.Table {
t := table.New()
Expand All @@ -27,3 +28,18 @@ func NewTable() *table.Table {
}
})
}

func init() {
// dynamically set the default table width based on terminal size breakpoints
w, _, err := term.GetSize(0)
if err != nil {
w = 80
}
if w > 180 {
defaultTableWidth = 180
} else if w > 120 {
defaultTableWidth = 120
} else {
defaultTableWidth = 80
}
}
Loading