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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ObjectUtil } from "../../../Utility/ObjectUtil.js";
import { ObjectKeyCaseTransformStreamOptions } from "../../../Mapping/Json/Streams/ObjectKeyCaseTransformStream.js";

/**
* Key-case transform for certificate read responses.
*
* The `permissions` map is keyed by database names, which must be read back verbatim: the default
* deep camelCasing would lowercase the first letter of each key (e.g. "UPPER_db" -> "uPPER_db"),
* breaking case-sensitive permission matching on the server (RDBC-1085). We ignore those keys while
* still camelCasing every other response field. Database names may contain '.', so the ignore path
* matches everything under `permissions` (the values are primitive strings, so there is no nesting
* below the key that would need transforming).
*
* Shared by all certificate read operations so their handling of permission keys can't drift.
* The `results.[]` path prefix is coupled to the array-wrapped response shape those operations all
* rely on; an operation returning permissions at a different path would need its own transform.
*/
export const CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM: ObjectKeyCaseTransformStreamOptions = {
defaultTransform: ObjectUtil.camel,
ignorePaths: [/^results\.\[\]\.permissions\..+$/i]
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { RavenCommand } from "../../../Http/RavenCommand.js";
import { ServerNode } from "../../../Http/ServerNode.js";
import { IRaftCommand } from "../../../Http/IRaftCommand.js";
import { RaftIdGenerator } from "../../../Utility/RaftIdGenerator.js";
import { JsonSerializer } from "../../../Mapping/Json/Serializer.js";

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

const body = this._serializer
// Field names are already PascalCased here, so use the casing-preserving serializer:
// the default command-payload serializer would also PascalCase the first letter of every
// `permissions` key (database names), breaking case-sensitive permission matching (RDBC-1085).
const body = JsonSerializer.getDefault()
.serialize({
Name: this._name,
SecurityClearance: this._clearance,
Password: this._password || undefined,
Permissions: this._permissions,

Permissions: this._permissions
});

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { DocumentConventions } from "../../../Documents/Conventions/DocumentConv
import { RavenCommand } from "../../../Http/RavenCommand.js";
import { IRaftCommand } from "../../../Http/IRaftCommand.js";
import { ServerNode } from "../../../Http/ServerNode.js";
import { CertificateDefinition } from "./CertificateDefinition.js";
import { JsonSerializer } from "../../../Mapping/Json/Serializer.js";

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

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

const body = this._serializer.serialize(definition);
const body = JsonSerializer.getDefault().serialize(definition);

return {
method: "POST",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RavenCommand } from "../../../Http/RavenCommand.js";
import { ServerNode } from "../../../Http/ServerNode.js";
import { ServerResponse } from "../../../Types/index.js";
import { DateUtil } from "../../../Utility/DateUtil.js";
import { CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM } from "./CertificateResponseKeyCaseTransform.js";

export class GetCertificateMetadataOperation implements IServerOperation<CertificateMetadata> {
private readonly _thumbprint: string;
Expand Down Expand Up @@ -61,7 +62,9 @@ class GetCertificateMetadataCommand extends RavenCommand<CertificateMetadata> {
}

let body: string = null;
const response = await this._defaultPipeline<ServerResponse<{ results: CertificateMetadata[] }>>(_ => body = _).process(bodyStream);
const response = await this._defaultPipeline<ServerResponse<{ results: CertificateMetadata[] }>>(_ => body = _)
.objectKeysTransform(CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM)
.process(bodyStream);

const resultsMapped: CertificateMetadata[] = response.results.map(cert => {
const { notAfter, notBefore } = cert;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IServerOperation, OperationResultType } from "../../../Documents/Operat
import { DocumentConventions } from "../../../Documents/Conventions/DocumentConventions.js";
import { RavenCommand } from "../../../Http/RavenCommand.js";
import { ServerNode } from "../../../Http/ServerNode.js";
import { CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM } from "./CertificateResponseKeyCaseTransform.js";

export class GetCertificateOperation implements IServerOperation<CertificateDefinition> {
private readonly _thumbprint: string;
Expand Down Expand Up @@ -60,7 +61,9 @@ class GetCertificateCommand extends RavenCommand<CertificateDefinition> {
}

let body: string = null;
const results = await this._defaultPipeline(_ => body = _).process(bodyStream);
const results = await this._defaultPipeline(_ => body = _)
.objectKeysTransform(CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM)
.process(bodyStream);
const mapped = this._conventions.objectMapper.fromObjectLiteral<{ results: CertificateDefinition[] }>(results, {
nestedTypes: {
"results[].notAfter": "date",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IServerOperation, OperationResultType } from "../../../Documents/Operat
import { DocumentConventions } from "../../../Documents/Conventions/DocumentConventions.js";
import { RavenCommand } from "../../../Http/RavenCommand.js";
import { ServerNode } from "../../../Http/ServerNode.js";
import { CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM } from "./CertificateResponseKeyCaseTransform.js";
import { Stream } from "node:stream";

export class GetCertificatesMetadataOperation implements IServerOperation<CertificateMetadata[]> {
Expand Down Expand Up @@ -59,7 +60,9 @@ class GetCertificatesMetadataCommand extends RavenCommand<CertificateMetadata[]>
}

let body: string = null;
const results = await this._defaultPipeline(_ => body = _).process(bodyStream);
const results = await this._defaultPipeline<{ results: CertificateMetadata[] }>(_ => body = _)
.objectKeysTransform(CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM)
.process(bodyStream);
this.result = this._conventions.objectMapper.fromObjectLiteral<{ results: CertificateMetadata[] }>(results, {
nestedTypes: {
"results[].notAfter": "date",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IServerOperation, OperationResultType } from "../../../Documents/Operat
import { RavenCommand } from "../../../Http/RavenCommand.js";
import { DocumentConventions } from "../../../Documents/Conventions/DocumentConventions.js";
import { ServerNode } from "../../../Http/ServerNode.js";
import { CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM } from "./CertificateResponseKeyCaseTransform.js";

export class GetCertificatesOperation implements IServerOperation<CertificateDefinition[]> {

Expand Down Expand Up @@ -57,7 +58,9 @@ class GetCertificatesCommand extends RavenCommand<CertificateDefinition[]> {
}

let body: string = null;
const results = await this._defaultPipeline(_ => body = _).process(bodyStream);
const results = await this._defaultPipeline(_ => body = _)
.objectKeysTransform(CERTIFICATE_RESPONSE_KEY_CASE_TRANSFORM)
.process(bodyStream);
this.result = this._conventions.objectMapper.fromObjectLiteral<{ results: CertificateDefinition[] }>(results, {
nestedTypes: {
"results[].notAfter": "date",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { RavenCommand } from "../../../Http/RavenCommand.js";
import { ServerNode } from "../../../Http/ServerNode.js";
import { IRaftCommand } from "../../../Http/IRaftCommand.js";
import { RaftIdGenerator } from "../../../Utility/RaftIdGenerator.js";
import { JsonSerializer } from "../../../Mapping/Json/Serializer.js";

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

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