Skip to content

Commit db0e21e

Browse files
authored
feat: add file input option to JwtInfo (#23)
* feat: add file input option to JwtInfo
1 parent 6631032 commit db0e21e

5 files changed

Lines changed: 189 additions & 7 deletions

File tree

cmd/jwtinfo.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,62 @@ import (
1717

1818
var (
1919
flagNameRequestJSONValues = "request-values-json"
20+
flagNameRequestValuesFile = "request-values-file"
2021
flagNameRequestURL = "request-url"
2122
flagNameJwksURL = "validation-url"
2223
requestJSONValues string
24+
requestValuesFile string
2325
requestURL string
2426
jwksURL string
2527
keyfuncDefOverride keyfunc.Override
2628
)
2729

2830
var jwtinfoCmd = &cobra.Command{
2931
Use: "jwtinfo",
30-
Short: "Request and display JWT token data",
31-
Long: `Request and display JWT token data.`,
32+
Short: "JwtInfo request and display JWT token data",
33+
Long: `JwtInfo request and display JWT token data
34+
35+
Examples:
36+
export REQ_URL="https://sample.provider/oauth/token"
37+
export REQ_VALUES="{\"login\":\"values\"}"
38+
export VALIDATION_URL="https://url.to/jkws.json"
39+
40+
# Get the JWT token using inline values
41+
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES
42+
43+
# Get the JWT token using values file
44+
https-wrench jwtinfo --request-url $REQ_URL --request-values-file request-values.json
45+
46+
# Get and validate the JWT token
47+
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --validation-url $VALIDATION_URL
48+
`,
3249
Run: func(cmd *cobra.Command, args []string) {
50+
// TODO: display version and exit
51+
// TODO: remove global --config option
52+
53+
if len(requestJSONValues+requestURL) == 0 && len(requestValuesFile+requestURL) == 0 {
54+
_ = cmd.Help()
55+
return
56+
}
57+
3358
var err error
3459
client := &http.Client{}
3560
requestValuesMap := make(map[string]string)
3661

62+
if requestValuesFile != "" {
63+
requestValuesMap, err = jwtinfo.ReadRequestValuesFile(
64+
requestValuesFile,
65+
requestValuesMap,
66+
)
67+
if err != nil {
68+
fmt.Printf(
69+
"error while reading request's values from file: %s",
70+
err,
71+
)
72+
return
73+
}
74+
}
75+
3776
if requestJSONValues != "" {
3877
requestValuesMap, err = jwtinfo.ParseRequestJSONValues(
3978
requestJSONValues,
@@ -98,6 +137,13 @@ func init() {
98137
"JSON encoded values to use for the JWT token request",
99138
)
100139

140+
jwtinfoCmd.Flags().StringVar(
141+
&requestValuesFile,
142+
flagNameRequestValuesFile,
143+
"",
144+
"File containing the JSON encoded values to use for the JWT token request",
145+
)
146+
101147
jwtinfoCmd.Flags().StringVar(
102148
&jwksURL,
103149
flagNameJwksURL,

devenv.nix

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,15 @@ in {
608608
./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-json "$JWTINFO_TEST_AUTH0"
609609
'';
610610

611+
scripts.run-jwtinfo-test-auth0-values-file.exec = ''
612+
gum format "### JwtInfo request against Auth0 with values file"
613+
614+
REQ_URL="https://dev-x3cci6dykofnlj5z.eu.auth0.com/oauth/token"
615+
VALIDATION_URL="https://dev-x3cci6dykofnlj5z.eu.auth0.com/.well-known/jwks.json"
616+
617+
./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-file ~/.config/https-wrench/jwtinfo_test_auth0_req_values.json --validation-url "$VALIDATION_URL"
618+
'';
619+
611620
scripts.run-jwtinfo-test-keycloak.exec = ''
612621
gum format "### JwtInfo request against priv Keycloak"
613622
@@ -617,6 +626,15 @@ in {
617626
./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-json "$JWTINFO_TEST_KEYCLOAK" --validation-url "$VALIDATION_URL"
618627
'';
619628

629+
scripts.run-jwtinfo-test-keycloak-values-file.exec = ''
630+
gum format "### JwtInfo request against priv Keycloak with values file"
631+
632+
REQ_URL="https://keycloak.k3s.os76.xyz/realms/os76/protocol/openid-connect/token"
633+
VALIDATION_URL="https://keycloak.k3s.os76.xyz/realms/os76/protocol/openid-connect/certs"
634+
635+
./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-file ~/.config/https-wrench/jwtinfo_test_keycloak_req_values.json --validation-url "$VALIDATION_URL"
636+
'';
637+
620638
scripts.run-go-tests.exec = ''
621639
gum format "## Run GO tests"
622640

internal/jwtinfo/jwtinfo.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"mime"
1313
"net/http"
1414
"net/url"
15+
"os"
1516
"strconv"
1617
"strings"
1718
"time"
@@ -148,6 +149,26 @@ func ParseRequestJSONValues(
148149
return reqValuesMap, nil
149150
}
150151

152+
func ReadRequestValuesFile(
153+
fileName string,
154+
reqValuesMap map[string]string,
155+
) (
156+
map[string]string,
157+
error,
158+
) {
159+
data, err := os.ReadFile(fileName)
160+
if err != nil {
161+
return nil, fmt.Errorf("unable to read request's values file: %w", err)
162+
}
163+
164+
returnValuesMap, err := ParseRequestJSONValues(string(data), reqValuesMap)
165+
if err != nil {
166+
return nil, fmt.Errorf("unable to parse JSON from request's values file: %w", err)
167+
}
168+
169+
return returnValuesMap, nil
170+
}
171+
151172
func isValidJSON(data []byte) bool {
152173
var v any
153174
return json.Unmarshal(data, &v) == nil

internal/jwtinfo/jwtinfo_test.go

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/base64"
66
"io"
77
"maps"
8+
"os"
89
"testing"
910
"time"
1011

@@ -13,13 +14,86 @@ import (
1314
"github.com/stretchr/testify/require"
1415
)
1516

17+
func TestReadRequestValuesFile(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
fileContent []byte
21+
expErr bool
22+
errMsg string
23+
}{
24+
{
25+
name: "success",
26+
fileContent: []byte("{\"jsonKey\":\"jsonValue\"}"),
27+
},
28+
{
29+
name: "No JSON content",
30+
fileContent: []byte("not json"),
31+
expErr: true,
32+
errMsg: "unable to parse JSON from request's values file",
33+
},
34+
}
35+
36+
for _, tc := range tests {
37+
tt := tc
38+
t.Run(tt.name, func(t *testing.T) {
39+
t.Parallel()
40+
41+
inputMap := map[string]string{
42+
"testKey": "testValue",
43+
}
44+
45+
tmpDir := t.TempDir()
46+
tempFile, err := createTmpFileWithContent(
47+
tmpDir,
48+
"fileReadTest.txt",
49+
tt.fileContent,
50+
)
51+
52+
require.NoError(t, err)
53+
54+
outputMap, err := ReadRequestValuesFile(
55+
tempFile,
56+
inputMap,
57+
)
58+
59+
if tt.expErr {
60+
require.ErrorContains(t, err, tt.errMsg)
61+
return
62+
}
63+
64+
require.NoError(t, err)
65+
require.Equal(t, "jsonValue", outputMap["jsonKey"])
66+
require.Equal(t, "testValue", outputMap["testKey"])
67+
68+
t.Cleanup(func() { os.RemoveAll(tmpDir) })
69+
})
70+
}
71+
72+
t.Run("fileNotExist", func(t *testing.T) {
73+
t.Parallel()
74+
75+
inputMap := map[string]string{
76+
"testKey": "testValue",
77+
}
78+
79+
_, err := ReadRequestValuesFile(
80+
"fileNotExist",
81+
inputMap,
82+
)
83+
84+
require.ErrorContains(t, err, "no such file or directory")
85+
})
86+
}
87+
1688
func TestParseRequestJSONValues(t *testing.T) {
17-
inputMap := make(map[string]string)
18-
inputMap["testKey"] = "testValue"
89+
inputMap := map[string]string{
90+
"testKey": "testValue",
91+
}
1992

20-
mapToValidJSON := make(map[string]string)
21-
mapToValidJSON["testKey2"] = "testValue2"
22-
mapToValidJSON["testKey3"] = "testValue3"
93+
mapToValidJSON := map[string]string{
94+
"testKey2": "testValue2",
95+
"testKey3": "testValue3",
96+
}
2397

2498
tests := []struct {
2599
name string

internal/jwtinfo/main_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,26 @@ func NewJwtTestServer() (*httptest.Server, error) {
247247

248248
return ts, nil
249249
}
250+
251+
func createTmpFileWithContent(
252+
tempDir string,
253+
filePattern string,
254+
fileContent []byte,
255+
) (filePath string, err error) {
256+
f, err := os.CreateTemp(tempDir, filePattern)
257+
if err != nil {
258+
return emptyString, err
259+
}
260+
261+
defer func() {
262+
if closeErr := f.Close(); closeErr != nil {
263+
err = errors.Join(err, closeErr)
264+
}
265+
}()
266+
267+
if err = os.WriteFile(f.Name(), fileContent, 0o600); err != nil {
268+
return emptyString, err
269+
}
270+
271+
return f.Name(), nil
272+
}

0 commit comments

Comments
 (0)