Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions cmd/jwtinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,62 @@ import (

var (
flagNameRequestJSONValues = "request-values-json"
flagNameRequestValuesFile = "request-values-file"
flagNameRequestURL = "request-url"
flagNameJwksURL = "validation-url"
requestJSONValues string
requestValuesFile string
requestURL string
jwksURL string
keyfuncDefOverride keyfunc.Override
)

var jwtinfoCmd = &cobra.Command{
Use: "jwtinfo",
Short: "Request and display JWT token data",
Long: `Request and display JWT token data.`,
Short: "JwtInfo request and display JWT token data",
Long: `JwtInfo request and display JWT token data

Examples:
export REQ_URL="https://sample.provider/oauth/token"
export REQ_VALUES="{\"login\":\"values\"}"
export VALIDATION_URL="https://url.to/jkws.json"

# Get the JWT token using inline values
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES

# Get the JWT token using values file
https-wrench jwtinfo --request-url $REQ_URL --request-values-file request-values.json

# Get and validate the JWT token
https-wrench jwtinfo --request-url $REQ_URL --request-values-json $REQ_VALUES --validation-url $VALIDATION_URL
`,
Run: func(cmd *cobra.Command, args []string) {
// TODO: display version and exit
// TODO: remove global --config option

if len(requestJSONValues+requestURL) == 0 && len(requestValuesFile+requestURL) == 0 {
_ = cmd.Help()
return
}

var err error
client := &http.Client{}
requestValuesMap := make(map[string]string)

if requestValuesFile != "" {
requestValuesMap, err = jwtinfo.ReadRequestValuesFile(
requestValuesFile,
requestValuesMap,
)
if err != nil {
fmt.Printf(
"error while reading request's values from file: %s",
err,
)
return
}
}

if requestJSONValues != "" {
requestValuesMap, err = jwtinfo.ParseRequestJSONValues(
requestJSONValues,
Expand Down Expand Up @@ -98,6 +137,13 @@ func init() {
"JSON encoded values to use for the JWT token request",
)

jwtinfoCmd.Flags().StringVar(
&requestValuesFile,
flagNameRequestValuesFile,
"",
"File containing the JSON encoded values to use for the JWT token request",
)

jwtinfoCmd.Flags().StringVar(
&jwksURL,
flagNameJwksURL,
Expand Down
18 changes: 18 additions & 0 deletions devenv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,15 @@ in {
./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-json "$JWTINFO_TEST_AUTH0"
'';

scripts.run-jwtinfo-test-auth0-values-file.exec = ''
gum format "### JwtInfo request against Auth0 with values file"

REQ_URL="https://dev-x3cci6dykofnlj5z.eu.auth0.com/oauth/token"
VALIDATION_URL="https://dev-x3cci6dykofnlj5z.eu.auth0.com/.well-known/jwks.json"

./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-file ~/.config/https-wrench/jwtinfo_test_auth0_req_values.json --validation-url "$VALIDATION_URL"
'';

scripts.run-jwtinfo-test-keycloak.exec = ''
gum format "### JwtInfo request against priv Keycloak"

Expand All @@ -617,6 +626,15 @@ in {
./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-json "$JWTINFO_TEST_KEYCLOAK" --validation-url "$VALIDATION_URL"
'';

scripts.run-jwtinfo-test-keycloak-values-file.exec = ''
gum format "### JwtInfo request against priv Keycloak with values file"

REQ_URL="https://keycloak.k3s.os76.xyz/realms/os76/protocol/openid-connect/token"
VALIDATION_URL="https://keycloak.k3s.os76.xyz/realms/os76/protocol/openid-connect/certs"

./dist/https-wrench jwtinfo --request-url "$REQ_URL" --request-values-file ~/.config/https-wrench/jwtinfo_test_keycloak_req_values.json --validation-url "$VALIDATION_URL"
'';

scripts.run-go-tests.exec = ''
gum format "## Run GO tests"

Expand Down
21 changes: 21 additions & 0 deletions internal/jwtinfo/jwtinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"mime"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -148,6 +149,26 @@ func ParseRequestJSONValues(
return reqValuesMap, nil
}

func ReadRequestValuesFile(
fileName string,
reqValuesMap map[string]string,
) (
map[string]string,
error,
) {
data, err := os.ReadFile(fileName)
if err != nil {
return nil, fmt.Errorf("unable to read request's values file: %w", err)
}

returnValuesMap, err := ParseRequestJSONValues(string(data), reqValuesMap)
if err != nil {
return nil, fmt.Errorf("unable to parse JSON from request's values file: %w", err)
}

return returnValuesMap, nil
}

func isValidJSON(data []byte) bool {
var v any
return json.Unmarshal(data, &v) == nil
Expand Down
84 changes: 79 additions & 5 deletions internal/jwtinfo/jwtinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/base64"
"io"
"maps"
"os"
"testing"
"time"

Expand All @@ -13,13 +14,86 @@ import (
"github.com/stretchr/testify/require"
)

func TestReadRequestValuesFile(t *testing.T) {
tests := []struct {
name string
fileContent []byte
expErr bool
errMsg string
}{
{
name: "success",
fileContent: []byte("{\"jsonKey\":\"jsonValue\"}"),
},
{
name: "No JSON content",
fileContent: []byte("not json"),
expErr: true,
errMsg: "unable to parse JSON from request's values file",
},
}

for _, tc := range tests {
tt := tc
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

inputMap := map[string]string{
"testKey": "testValue",
}

tmpDir := t.TempDir()
tempFile, err := createTmpFileWithContent(
tmpDir,
"fileReadTest.txt",
tt.fileContent,
)

require.NoError(t, err)

outputMap, err := ReadRequestValuesFile(
tempFile,
inputMap,
)

if tt.expErr {
require.ErrorContains(t, err, tt.errMsg)
return
}

require.NoError(t, err)
require.Equal(t, "jsonValue", outputMap["jsonKey"])
require.Equal(t, "testValue", outputMap["testKey"])

t.Cleanup(func() { os.RemoveAll(tmpDir) })
})
}

t.Run("fileNotExist", func(t *testing.T) {
t.Parallel()

inputMap := map[string]string{
"testKey": "testValue",
}

_, err := ReadRequestValuesFile(
"fileNotExist",
inputMap,
)

require.ErrorContains(t, err, "no such file or directory")
})
}

func TestParseRequestJSONValues(t *testing.T) {
inputMap := make(map[string]string)
inputMap["testKey"] = "testValue"
inputMap := map[string]string{
"testKey": "testValue",
}

mapToValidJSON := make(map[string]string)
mapToValidJSON["testKey2"] = "testValue2"
mapToValidJSON["testKey3"] = "testValue3"
mapToValidJSON := map[string]string{
"testKey2": "testValue2",
"testKey3": "testValue3",
}

tests := []struct {
name string
Expand Down
23 changes: 23 additions & 0 deletions internal/jwtinfo/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,26 @@ func NewJwtTestServer() (*httptest.Server, error) {

return ts, nil
}

func createTmpFileWithContent(
tempDir string,
filePattern string,
fileContent []byte,
) (filePath string, err error) {
f, err := os.CreateTemp(tempDir, filePattern)
if err != nil {
return emptyString, err
}

defer func() {
if closeErr := f.Close(); closeErr != nil {
err = errors.Join(err, closeErr)
}
}()

if err = os.WriteFile(f.Name(), fileContent, 0o600); err != nil {
return emptyString, err
}

return f.Name(), nil
}
Loading