Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: decode string as JSON Web Token (JWT) #19

Merged
merged 1 commit into from
Jan 30, 2024
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
65 changes: 64 additions & 1 deletion src/Heartbeat/ClientApp/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,40 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
'/api/dump/object/{address}/as-jwt':
get:
tags:
- Dump
operationId: GetClrObjectAsJwt
parameters:
- name: address
in: path
required: true
style: simple
schema:
type: integer
format: int64
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/JwtInfo'
'204':
description: No Content
'404':
description: Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
'/api/dump/object/{address}/fields':
get:
tags:
Expand Down Expand Up @@ -469,7 +503,6 @@ components:
required:
- address
- index
- value
type: object
properties:
index:
Expand All @@ -480,6 +513,7 @@ components:
format: int64
value:
type: string
nullable: true
additionalProperties: false
ClrObjectField:
required:
Expand Down Expand Up @@ -651,6 +685,35 @@ components:
format: int64
readOnly: true
additionalProperties: false
JwtInfo:
required:
- header
- payload
type: object
properties:
header:
type: array
items:
$ref: '#/components/schemas/JwtValue'
payload:
type: array
items:
$ref: '#/components/schemas/JwtValue'
additionalProperties: false
JwtValue:
required:
- key
- value
type: object
properties:
key:
type: string
value:
type: string
description:
type: string
nullable: true
additionalProperties: false
Module:
required:
- address
Expand Down
5 changes: 5 additions & 0 deletions src/Heartbeat/ClientApp/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ ul {

.monoFont {
font-family: monospace;
}

.hardWrap {
overflow-wrap: break-word;
word-break: break-all;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* tslint:disable */
/* eslint-disable */
// Generated by Microsoft Kiota
import { createJwtInfoFromDiscriminatorValue, createProblemDetailsFromDiscriminatorValue, deserializeIntoProblemDetails, serializeProblemDetails, type JwtInfo, type ProblemDetails } from '../../../../../models/';
import { BaseRequestBuilder, HttpMethod, RequestInformation, type Parsable, type ParsableFactory, type RequestAdapter, type RequestConfiguration, type RequestOption } from '@microsoft/kiota-abstractions';

/**
* Builds and executes requests for operations under /api/dump/object/{address}/as-jwt
*/
export class AsJwtRequestBuilder extends BaseRequestBuilder<AsJwtRequestBuilder> {
/**
* Instantiates a new AsJwtRequestBuilder and sets the default values.
* @param pathParameters The raw url or the Url template parameters for the request.
* @param requestAdapter The request adapter to use to execute the requests.
*/
public constructor(pathParameters: Record<string, unknown> | string | undefined, requestAdapter: RequestAdapter) {
super(pathParameters, requestAdapter, "{+baseurl}/api/dump/object/{address}/as-jwt", (x, y) => new AsJwtRequestBuilder(x, y));
}
/**
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of JwtInfo
*/
public get(requestConfiguration?: RequestConfiguration<object> | undefined) : Promise<JwtInfo | undefined> {
const requestInfo = this.toGetRequestInformation(
requestConfiguration
);
const errorMapping = {
"404": createProblemDetailsFromDiscriminatorValue,
"500": createProblemDetailsFromDiscriminatorValue,
} as Record<string, ParsableFactory<Parsable>>;
return this.requestAdapter.sendAsync<JwtInfo>(requestInfo, createJwtInfoFromDiscriminatorValue, errorMapping);
}
/**
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
public toGetRequestInformation(requestConfiguration?: RequestConfiguration<object> | undefined) : RequestInformation {
const requestInfo = new RequestInformation(HttpMethod.GET, this.urlTemplate, this.pathParameters);
requestInfo.configure(requestConfiguration);
requestInfo.headers.tryAdd("Accept", "application/json");
return requestInfo;
}
}
/* tslint:enable */
/* eslint-enable */
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Generated by Microsoft Kiota
import { createGetClrObjectResultFromDiscriminatorValue, createProblemDetailsFromDiscriminatorValue, deserializeIntoProblemDetails, serializeProblemDetails, type GetClrObjectResult, type ProblemDetails } from '../../../../models/';
import { AsArrayRequestBuilder } from './asArray/';
import { AsJwtRequestBuilder } from './asJwt/';
import { FieldsRequestBuilder } from './fields/';
import { RootsRequestBuilder } from './roots/';
import { BaseRequestBuilder, HttpMethod, RequestInformation, type Parsable, type ParsableFactory, type RequestAdapter, type RequestConfiguration, type RequestOption } from '@microsoft/kiota-abstractions';
Expand All @@ -17,6 +18,12 @@ export class WithAddressItemRequestBuilder extends BaseRequestBuilder<WithAddres
public get asArray(): AsArrayRequestBuilder {
return new AsArrayRequestBuilder(this.pathParameters, this.requestAdapter);
}
/**
* The asJwt property
*/
public get asJwt(): AsJwtRequestBuilder {
return new AsJwtRequestBuilder(this.pathParameters, this.requestAdapter);
}
/**
* The fields property
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Heartbeat/ClientApp/src/client/kiota-lock.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"descriptionHash": "C2E02AEF66804BDF43A6B9DAE16266E2193A8418F53798D9AD2BBD832A9C33A511C8C9D02638AB3C5C3D249C515A2D838D5564A4A22D99E716D918BFFAB95F77",
"descriptionHash": "D2636368E24B63D166B3F432C5AD1C9DD4B984EEE69C76339F31F181C38DE793AA1741B79A96DF5B0CBCD8D84501856A18931CC90AB365F2A17465A3A57A9461",
"descriptionLocation": "..\\..\\api.yml",
"lockFileVersion": "1.0.0",
"kiotaVersion": "1.10.1",
Expand Down
52 changes: 52 additions & 0 deletions src/Heartbeat/ClientApp/src/client/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ export function createGetObjectInstancesResultFromDiscriminatorValue(parseNode:
export function createHeapSegmentFromDiscriminatorValue(parseNode: ParseNode | undefined) {
return deserializeIntoHeapSegment;
}
export function createJwtInfoFromDiscriminatorValue(parseNode: ParseNode | undefined) {
return deserializeIntoJwtInfo;
}
export function createJwtValueFromDiscriminatorValue(parseNode: ParseNode | undefined) {
return deserializeIntoJwtValue;
}
export function createModuleFromDiscriminatorValue(parseNode: ParseNode | undefined) {
return deserializeIntoModule;
}
Expand Down Expand Up @@ -213,6 +219,19 @@ export function deserializeIntoHeapSegment(heapSegment: HeapSegment | undefined
"start": n => { heapSegment.start = n.getNumberValue(); },
}
}
export function deserializeIntoJwtInfo(jwtInfo: JwtInfo | undefined = {} as JwtInfo) : Record<string, (node: ParseNode) => void> {
return {
"header": n => { jwtInfo.header = n.getCollectionOfObjectValues<JwtValue>(createJwtValueFromDiscriminatorValue); },
"payload": n => { jwtInfo.payload = n.getCollectionOfObjectValues<JwtValue>(createJwtValueFromDiscriminatorValue); },
}
}
export function deserializeIntoJwtValue(jwtValue: JwtValue | undefined = {} as JwtValue) : Record<string, (node: ParseNode) => void> {
return {
"description": n => { jwtValue.description = n.getStringValue(); },
"key": n => { jwtValue.key = n.getStringValue(); },
"value": n => { jwtValue.value = n.getStringValue(); },
}
}
export function deserializeIntoModule(module: Module | undefined = {} as Module) : Record<string, (node: ParseNode) => void> {
return {
"address": n => { module.address = n.getNumberValue(); },
Expand Down Expand Up @@ -384,6 +403,30 @@ export interface HeapSegment extends Parsable {
*/
start?: number;
}
export interface JwtInfo extends Parsable {
/**
* The header property
*/
header?: JwtValue[];
/**
* The payload property
*/
payload?: JwtValue[];
}
export interface JwtValue extends Parsable {
/**
* The description property
*/
description?: string;
/**
* The key property
*/
key?: string;
/**
* The value property
*/
value?: string;
}
export interface Module extends Parsable {
/**
* The address property
Expand Down Expand Up @@ -557,6 +600,15 @@ export function serializeHeapSegment(writer: SerializationWriter, heapSegment: H
writer.writeEnumValue<GCSegmentKind>("kind", heapSegment.kind);
writer.writeNumberValue("start", heapSegment.start);
}
export function serializeJwtInfo(writer: SerializationWriter, jwtInfo: JwtInfo | undefined = {} as JwtInfo) : void {
writer.writeCollectionOfObjectValues<JwtValue>("header", jwtInfo.header, serializeJwtValue);
writer.writeCollectionOfObjectValues<JwtValue>("payload", jwtInfo.payload, serializeJwtValue);
}
export function serializeJwtValue(writer: SerializationWriter, jwtValue: JwtValue | undefined = {} as JwtValue) : void {
writer.writeStringValue("description", jwtValue.description);
writer.writeStringValue("key", jwtValue.key);
writer.writeStringValue("value", jwtValue.value);
}
export function serializeModule(writer: SerializationWriter, module: Module | undefined = {} as Module) : void {
writer.writeNumberValue("address", module.address);
writer.writeStringValue("name", module.name);
Expand Down
2 changes: 1 addition & 1 deletion src/Heartbeat/ClientApp/src/components/PropertiesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const PropertiesTable = (props: PropertiesTableProps) => {
const rows = props.rows.map(row =>
<tr key={row.title}>
<td className="px-1 py-0">{row.title}</td>
<td className="px-1 py-0">{row.value}</td>
<td className="px-1 py-0 hardWrap">{row.value}</td>
</tr>)

return (
Expand Down
68 changes: 66 additions & 2 deletions src/Heartbeat/ClientApp/src/pages/clrObject/ClrObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {DataGrid, GridColDef, GridRenderCellParams, GridToolbar} from '@mui/x-da
import {Stack} from "@mui/material";
import {TabbedShowLayout} from "react-admin";

import {ClrObjectArrayItem, ClrObjectField, ClrObjectRootPath, GetClrObjectResult} from '../../client/models';
import {ClrObjectArrayItem, ClrObjectField, ClrObjectRootPath, GetClrObjectResult, JwtInfo} from '../../client/models';

import {useStateWithLoading} from "../../hooks/useStateWithLoading";
import {useNotifyError} from "../../hooks/useNotifyError";
Expand All @@ -19,6 +19,7 @@ import toSizeString from "../../lib/toSizeString";
import {PropertiesTable, PropertyRow} from '../../components/PropertiesTable'
import {ClrObjectRoot} from "../../components/ClrObjectRoot";
import {ProgressContainer} from "../../components/ProgressContainer";
import Box from "@mui/material/Box";

// TODO add Dictionary, Queue, Stack and other collections view to a new tab
// TODO add ConcurrentDictionary view to a new tab (dcd, dumpconcurrentdictionary <address> Displays concurrent dictionary content.)
Expand All @@ -35,6 +36,7 @@ export const ClrObject = () => {
const [fields, setFields, isFieldsLoading, setIsFieldsLoading] = useStateWithLoading<ClrObjectField[]>()
const [roots, setRoots, isRootsLoading, setIsRootsLoading] = useStateWithLoading<ClrObjectRootPath[]>()
const [arrayItems, setArrayItems, isArrayItemsLoading, setArrayItemsLoading] = useStateWithLoading<ClrObjectArrayItem[]>()
const [jwt, setJwt, isJwtLoading, setJwtLoading] = useStateWithLoading<JwtInfo>()

useEffect(() => {
const getObject = async () => {
Expand Down Expand Up @@ -72,6 +74,15 @@ export const ClrObject = () => {
fetchData(fetchArrayItems, setArrayItems, setArrayItemsLoading, notifyError)
}, [address, notify]);

useEffect(() => {
const fetchJwt = async () => {
const client = getClient();
return await client.api.dump.object.byAddress(address).asJwt.get()
}

fetchData(fetchJwt, setJwt, setJwtLoading, notifyError)
}, [address, notify]);

const getChildrenContent = (objectResult?: GetClrObjectResult) => {
if (!objectResult)
return undefined;
Expand Down Expand Up @@ -109,9 +120,12 @@ export const ClrObject = () => {
<TabbedShowLayout.Tab label="Roots">
<RootsTabContent isLoading={isRootsLoading} roots={roots}/>
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="Array">
<TabbedShowLayout.Tab label="Array" hidden={!isArrayItemsLoading && !arrayItems}>
<ArrayTabContent isLoading={isArrayItemsLoading} arrayItems={arrayItems}/>
</TabbedShowLayout.Tab>
<TabbedShowLayout.Tab label="JWT" hidden={!isJwtLoading && !jwt}>
<JwtTabContent isLoading={isJwtLoading} jwt={jwt}/>
</TabbedShowLayout.Tab>
</TabbedShowLayout>
</Stack>
);
Expand Down Expand Up @@ -244,4 +258,54 @@ const ArrayTabContent = (props: { isLoading: boolean, arrayItems?: ClrObjectArra
return (<ProgressContainer isLoading={props.isLoading}>
{getArrayItemsContent(props.arrayItems)}
</ProgressContainer>);
}

const JwtTabContent = (props: { isLoading: boolean, jwt?: JwtInfo }) => {
const getRootsContent = (jwt?: JwtInfo) => {
if (!jwt)
return undefined;

const columns: GridColDef[] = [
{
field: 'key',
headerName: 'Key',
minWidth: 200
},
{
field: 'value',
headerName: 'Value',
width: 600
},
{
field: 'description',
headerName: 'Description',
flex: 1
}
];

return (
<Stack>
<Box>Header</Box>
<DataGrid
rows={props.jwt?.header!}
getRowId={(row) => row.key!}
columns={columns}
rowHeight={25}
density='compact'
/>
<Box>Payload</Box>
<DataGrid
rows={props.jwt?.payload!}
getRowId={(row) => row.key!}
columns={columns}
rowHeight={25}
density='compact'
/>
</Stack>
);
}

return (<ProgressContainer isLoading={props.isLoading}>
{getRootsContent(props.jwt)}
</ProgressContainer>)
}
1 change: 1 addition & 0 deletions src/Heartbeat/Endpoints/EndpointJsonSerializerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Heartbeat.Host.Endpoints;
[JsonSerializable(typeof(DumpInfo))]
[JsonSerializable(typeof(GetObjectInstancesResult))]
[JsonSerializable(typeof(GetClrObjectResult))]
[JsonSerializable(typeof(JwtInfo))]
[JsonSerializable(typeof(Module[]))]
[JsonSerializable(typeof(ClrObjectField[]))]
[JsonSerializable(typeof(List<ClrObjectRootPath>))]
Expand Down
Loading
Loading