Skip to content

Commit c45216c

Browse files
authored
Merge pull request #561 from M4xymm/RDBC-1085
RDBC-1085 Check whether the client node converts values in the request to PascalCase when they should not be in PascalCase.
2 parents 12df664 + 9fbb4d5 commit c45216c

9 files changed

Lines changed: 285 additions & 15 deletions
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ObjectUtil } from "../../../Utility/ObjectUtil.js";
2+
import { ObjectKeyCaseTransformStreamOptions } from "../../../Mapping/Json/Streams/ObjectKeyCaseTransformStream.js";
3+
4+
/**
5+
* Key-case transform for certificate read responses.
6+
*
7+
* The `permissions` map is keyed by database names, which must be read back verbatim: the default
8+
* deep camelCasing would lowercase the first letter of each key (e.g. "UPPER_db" -> "uPPER_db"),
9+
* breaking case-sensitive permission matching on the server (RDBC-1085). We ignore those keys while
10+
* still camelCasing every other response field. Database names may contain '.', so the ignore path
11+
* matches everything under `permissions` (the values are primitive strings, so there is no nesting
12+
* below the key that would need transforming).
13+
*
14+
* Shared by all certificate read operations so their handling of permission keys can't drift.
15+
* The `results.[]` path prefix is coupled to the array-wrapped response shape those operations all
16+
* rely on; an operation returning permissions at a different path would need its own transform.
17+
*/
18+
export const CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM: ObjectKeyCaseTransformStreamOptions = {
19+
defaultTransform: ObjectUtil.camel,
20+
ignorePaths: [/^results\.\[\]\.permissions\..+$/i]
21+
};

src/ServerWide/Operations/Certificates/CreateClientCertificateOperation.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { RavenCommand } from "../../../Http/RavenCommand.js";
1212
import { ServerNode } from "../../../Http/ServerNode.js";
1313
import { IRaftCommand } from "../../../Http/IRaftCommand.js";
1414
import { RaftIdGenerator } from "../../../Utility/RaftIdGenerator.js";
15+
import { JsonSerializer } from "../../../Mapping/Json/Serializer.js";
1516

1617
export class CreateClientCertificateOperation implements IServerOperation<CertificateRawData> {
1718
private readonly _name: string;
@@ -74,13 +75,15 @@ class CreateClientCertificateCommand extends RavenCommand<CertificateRawData> im
7475
createRequest(node: ServerNode): HttpRequestParameters {
7576
const uri = node.url + "/admin/certificates";
7677

77-
const body = this._serializer
78+
// Field names are already PascalCased here, so use the casing-preserving serializer:
79+
// the default command-payload serializer would also PascalCase the first letter of every
80+
// `permissions` key (database names), breaking case-sensitive permission matching (RDBC-1085).
81+
const body = JsonSerializer.getDefault()
7882
.serialize({
7983
Name: this._name,
8084
SecurityClearance: this._clearance,
8185
Password: this._password || undefined,
82-
Permissions: this._permissions,
83-
86+
Permissions: this._permissions
8487
});
8588

8689
return {

src/ServerWide/Operations/Certificates/EditClientCertificateOperation.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { DocumentConventions } from "../../../Documents/Conventions/DocumentConv
88
import { RavenCommand } from "../../../Http/RavenCommand.js";
99
import { IRaftCommand } from "../../../Http/IRaftCommand.js";
1010
import { ServerNode } from "../../../Http/ServerNode.js";
11-
import { CertificateDefinition } from "./CertificateDefinition.js";
11+
import { JsonSerializer } from "../../../Mapping/Json/Serializer.js";
1212

1313
export class EditClientCertificateOperation implements IServerOperation<void> {
1414
private readonly _thumbprint: string;
@@ -74,14 +74,18 @@ class EditClientCertificateCommand extends RavenCommand<void> implements IRaftCo
7474
createRequest(node: ServerNode): HttpRequestParameters {
7575
const uri = node.url + "/admin/certificates/edit";
7676

77+
// Field names are PascalCased explicitly here (rather than via the default command-payload
78+
// serializer) so the casing-preserving serializer can be used: the default one would also
79+
// PascalCase the first letter of every `permissions` key (database names), breaking
80+
// case-sensitive permission matching (RDBC-1085).
7781
const definition = {
78-
thumbprint: this._thumbprint,
79-
permissions: this._permissions,
80-
securityClearance: this._clearance,
81-
name: this._name
82-
} as CertificateDefinition;
82+
Thumbprint: this._thumbprint,
83+
Permissions: this._permissions,
84+
SecurityClearance: this._clearance,
85+
Name: this._name
86+
};
8387

84-
const body = this._serializer.serialize(definition);
88+
const body = JsonSerializer.getDefault().serialize(definition);
8589

8690
return {
8791
method: "POST",

src/ServerWide/Operations/Certificates/GetCertificateMetadataOperation.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { RavenCommand } from "../../../Http/RavenCommand.js";
88
import { ServerNode } from "../../../Http/ServerNode.js";
99
import { ServerResponse } from "../../../Types/index.js";
1010
import { DateUtil } from "../../../Utility/DateUtil.js";
11+
import { CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM } from "./CertificateResponseKeyCaseTransform.js";
1112

1213
export class GetCertificateMetadataOperation implements IServerOperation<CertificateMetadata> {
1314
private readonly _thumbprint: string;
@@ -61,7 +62,9 @@ class GetCertificateMetadataCommand extends RavenCommand<CertificateMetadata> {
6162
}
6263

6364
let body: string = null;
64-
const response = await this._defaultPipeline<ServerResponse<{ results: CertificateMetadata[] }>>(_ => body = _).process(bodyStream);
65+
const response = await this._defaultPipeline<ServerResponse<{ results: CertificateMetadata[] }>>(_ => body = _)
66+
.objectKeysTransform(CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM)
67+
.process(bodyStream);
6568

6669
const resultsMapped: CertificateMetadata[] = response.results.map(cert => {
6770
const { notAfter, notBefore } = cert;

src/ServerWide/Operations/Certificates/GetCertificateOperation.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { IServerOperation, OperationResultType } from "../../../Documents/Operat
66
import { DocumentConventions } from "../../../Documents/Conventions/DocumentConventions.js";
77
import { RavenCommand } from "../../../Http/RavenCommand.js";
88
import { ServerNode } from "../../../Http/ServerNode.js";
9+
import { CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM } from "./CertificateResponseKeyCaseTransform.js";
910

1011
export class GetCertificateOperation implements IServerOperation<CertificateDefinition> {
1112
private readonly _thumbprint: string;
@@ -60,7 +61,9 @@ class GetCertificateCommand extends RavenCommand<CertificateDefinition> {
6061
}
6162

6263
let body: string = null;
63-
const results = await this._defaultPipeline(_ => body = _).process(bodyStream);
64+
const results = await this._defaultPipeline(_ => body = _)
65+
.objectKeysTransform(CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM)
66+
.process(bodyStream);
6467
const mapped = this._conventions.objectMapper.fromObjectLiteral<{ results: CertificateDefinition[] }>(results, {
6568
nestedTypes: {
6669
"results[].notAfter": "date",

src/ServerWide/Operations/Certificates/GetCertificatesMetadataOperation.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IServerOperation, OperationResultType } from "../../../Documents/Operat
55
import { DocumentConventions } from "../../../Documents/Conventions/DocumentConventions.js";
66
import { RavenCommand } from "../../../Http/RavenCommand.js";
77
import { ServerNode } from "../../../Http/ServerNode.js";
8+
import { CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM } from "./CertificateResponseKeyCaseTransform.js";
89
import { Stream } from "node:stream";
910

1011
export class GetCertificatesMetadataOperation implements IServerOperation<CertificateMetadata[]> {
@@ -59,7 +60,9 @@ class GetCertificatesMetadataCommand extends RavenCommand<CertificateMetadata[]>
5960
}
6061

6162
let body: string = null;
62-
const results = await this._defaultPipeline(_ => body = _).process(bodyStream);
63+
const results = await this._defaultPipeline<{ results: CertificateMetadata[] }>(_ => body = _)
64+
.objectKeysTransform(CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM)
65+
.process(bodyStream);
6366
this.result = this._conventions.objectMapper.fromObjectLiteral<{ results: CertificateMetadata[] }>(results, {
6467
nestedTypes: {
6568
"results[].notAfter": "date",

src/ServerWide/Operations/Certificates/GetCertificatesOperation.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IServerOperation, OperationResultType } from "../../../Documents/Operat
55
import { RavenCommand } from "../../../Http/RavenCommand.js";
66
import { DocumentConventions } from "../../../Documents/Conventions/DocumentConventions.js";
77
import { ServerNode } from "../../../Http/ServerNode.js";
8+
import { CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM } from "./CertificateResponseKeyCaseTransform.js";
89

910
export class GetCertificatesOperation implements IServerOperation<CertificateDefinition[]> {
1011

@@ -57,7 +58,9 @@ class GetCertificatesCommand extends RavenCommand<CertificateDefinition[]> {
5758
}
5859

5960
let body: string = null;
60-
const results = await this._defaultPipeline(_ => body = _).process(bodyStream);
61+
const results = await this._defaultPipeline(_ => body = _)
62+
.objectKeysTransform(CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM)
63+
.process(bodyStream);
6164
this.result = this._conventions.objectMapper.fromObjectLiteral<{ results: CertificateDefinition[] }>(results, {
6265
nestedTypes: {
6366
"results[].notAfter": "date",

src/ServerWide/Operations/Certificates/PutClientCertificateOperation.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { RavenCommand } from "../../../Http/RavenCommand.js";
99
import { ServerNode } from "../../../Http/ServerNode.js";
1010
import { IRaftCommand } from "../../../Http/IRaftCommand.js";
1111
import { RaftIdGenerator } from "../../../Utility/RaftIdGenerator.js";
12+
import { JsonSerializer } from "../../../Mapping/Json/Serializer.js";
1213

1314
export class PutClientCertificateOperation implements IServerOperation<void> {
1415
private readonly _certificate: string;
@@ -82,7 +83,10 @@ class PutClientCertificateCommand extends RavenCommand<void> implements IRaftCom
8283
createRequest(node: ServerNode): HttpRequestParameters {
8384
const uri = node.url + "/admin/certificates";
8485

85-
const body = this._serializer
86+
// Field names are already PascalCased here, so use the casing-preserving serializer:
87+
// the default command-payload serializer would also PascalCase the first letter of every
88+
// `permissions` key (database names), breaking case-sensitive permission matching (RDBC-1085).
89+
const body = JsonSerializer.getDefault()
8690
.serialize({
8791
Name: this._name,
8892
Certificate: this._certificate,

0 commit comments

Comments
 (0)