Skip to content

Commit

Permalink
make getCertificates work again without chained name (#2318)
Browse files Browse the repository at this point in the history
- and fix types for getCertificates
- closes #2308
  • Loading branch information
foxriver76 authored Jul 2, 2023
1 parent 851d3cf commit cb37f86
Show file tree
Hide file tree
Showing 7 changed files with 595 additions and 599 deletions.
10 changes: 6 additions & 4 deletions packages/adapter/src/lib/_Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,17 @@ export interface InternalCalculatePermissionsOptions {
}

export type GetCertificatesCallback = (
err: string | null,
err?: Error | null,
certs?: ioBroker.Certificates,
useLetsEncryptCert?: boolean
) => void;

export type GetCertificatesPromiseReturnType = [cert: ioBroker.Certificates, useLetsEncryptCert?: boolean];

export interface InternalGetCertificatesOptions {
publicName: string;
privateName: string;
chainedName: string;
publicName?: string;
privateName?: string;
chainedName?: string;
callback?: GetCertificatesCallback;
}

Expand Down
149 changes: 91 additions & 58 deletions packages/adapter/src/lib/adapter/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ import type {
CheckStatesResult,
Pattern,
MessageCallbackObject,
SendToOptions
SendToOptions,
GetCertificatesPromiseReturnType
} from '../_Types';

tools.ensureDNSOrder();
Expand Down Expand Up @@ -324,7 +325,11 @@ export interface AdapterClass {
options?: unknown
): ioBroker.SetObjectPromise;
// TODO: correct types
getCertificatesAsync(...args: any[]): Promise<any>;
getCertificatesAsync(
publicName?: string,
privateName?: string,
chainedName?: string
): Promise<GetCertificatesPromiseReturnType>;
/** Get all states, channels, devices and folders of this adapter */
getAdapterObjectsAsync(): Promise<Record<string, ioBroker.AdapterScopedObject>>;

Expand Down Expand Up @@ -2259,89 +2264,117 @@ export class AdapterClass extends EventEmitter {
* }
* ```
*/
getCertificates(publicName: unknown, privateName: unknown, chainedName: unknown, callback: unknown): void {
getCertificates(
publicName: unknown,
privateName: unknown,
chainedName: unknown,
callback: unknown
): Promise<GetCertificatesPromiseReturnType | void> {
if (!this.config) {
throw new Error(tools.ERRORS.ERROR_NOT_READY);
}

if (typeof publicName === 'function') {
callback = publicName;
publicName = null;
publicName = undefined;
}
if (typeof privateName === 'function') {
callback = privateName;
privateName = null;
privateName = undefined;
}
if (typeof chainedName === 'function') {
callback = chainedName;
chainedName = null;
chainedName = undefined;
}
publicName = publicName || this.config.certPublic;
privateName = privateName || this.config.certPrivate;
chainedName = chainedName || this.config.certChained;

Validator.assertString(publicName, 'publicName');
Validator.assertString(privateName, 'privateName');
Validator.assertString(chainedName, 'chainedName');
if (publicName !== undefined) {
Validator.assertString(publicName, 'publicName');
}

if (privateName !== undefined) {
Validator.assertString(privateName, 'privateName');
}

if (chainedName !== undefined) {
Validator.assertString(chainedName, 'chainedName');
}

Validator.assertOptionalCallback(callback, 'callback');

return this._getCertificates({ publicName, privateName, chainedName, callback });
}

private _getCertificates(options: InternalGetCertificatesOptions): void {
// Load certificates
this.getForeignObject('system.certificates', null, (err, obj) => {
if (
err ||
!obj ||
!obj.native.certificates ||
!options.publicName ||
!options.privateName ||
!obj.native.certificates[options.publicName] ||
!obj.native.certificates[options.privateName] ||
(options.chainedName && !obj.native.certificates[options.chainedName])
) {
this._logger.error(
`${this.namespaceLog} Cannot configure secure web server, because no certificates found: ${options.publicName}, ${options.privateName}, ${options.chainedName}`
private async _getCertificates(
options: InternalGetCertificatesOptions
): Promise<[cert: ioBroker.Certificates, useLetsEncryptCert?: boolean] | void> {
const { publicName, chainedName, privateName, callback } = options;
let obj: ioBroker.OtherObject | undefined | null;

if (!adapterObjects) {
this._logger.info(
`${this.namespaceLog} getCertificates not processed because Objects database not connected`
);
return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_DB_CLOSED);
}

try {
// Load certificates
obj = await adapterObjects.getObject('system.certificates');
} catch {
// ignore
}

if (
!obj ||
!obj.native.certificates ||
!publicName ||
!privateName ||
!obj.native.certificates[publicName] ||
!obj.native.certificates[privateName] ||
(chainedName && !obj.native.certificates[chainedName])
) {
this._logger.error(
`${this.namespaceLog} Cannot configure secure web server, because no certificates found: ${publicName}, ${privateName}, ${chainedName}`
);
if (publicName === 'defaultPublic' || privateName === 'defaultPrivate') {
this._logger.info(
`${this.namespaceLog} Default certificates seem to be configured but missing. You can execute "iobroker cert create" in your shell to create these.`
);
if (options.publicName === 'defaultPublic' || options.privateName === 'defaultPrivate') {
this._logger.info(
`${this.namespaceLog} Default certificates seem to be configured but missing. You can execute "iobroker cert create" in your shell to create these.`
);
}
// @ts-expect-error
return tools.maybeCallbackWithError(options.callback, tools.ERRORS.ERROR_NOT_FOUND);
} else {
let ca;
if (options.chainedName) {
const chained = this._readFileCertificate(obj.native.certificates[options.chainedName]).split(
'-----END CERTIFICATE-----\r\n'
);
// it is still file name and the file maybe does not exist, but we can omit this error
if (chained.join('').length >= 512) {
ca = [];
for (const cert of chained) {
if (cert.replace(/(\r\n|\r|\n)/g, '').trim()) {
ca.push(`${cert}-----END CERTIFICATE-----\r\n`);
}
}

return tools.maybeCallbackWithError(callback, tools.ERRORS.ERROR_NOT_FOUND);
} else {
let ca: string | undefined;
if (chainedName) {
const chained = this._readFileCertificate(obj.native.certificates[chainedName]).split(
'-----END CERTIFICATE-----\r\n'
);
// it is still file name and the file maybe does not exist, but we can omit this error
if (chained.join('').length >= 512) {
const caArr = [];
for (const cert of chained) {
if (cert.replace(/(\r\n|\r|\n)/g, '').trim()) {
caArr.push(`${cert}-----END CERTIFICATE-----\r\n`);
}
ca = ca.join('');
}
ca = caArr.join('');
}

return tools.maybeCallbackWithError(
// @ts-expect-error
options.callback,
null,
{
key: this._readFileCertificate(obj.native.certificates[options.privateName]),
cert: this._readFileCertificate(obj.native.certificates[options.publicName]),
ca
},
obj.native.letsEncrypt
);
}
});

return tools.maybeCallbackWithError(
callback,
null,
{
key: this._readFileCertificate(obj.native.certificates[privateName]),
cert: this._readFileCertificate(obj.native.certificates[publicName]),
ca
},
obj.native.letsEncrypt
);
}
}

/**
Expand Down
59 changes: 0 additions & 59 deletions packages/controller/test/lib/testAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,65 +286,6 @@ function testAdapter(options: Record<string, any>): void {
test.register(it, expect, context);
}

// sendTo => controller => adapter
// sendToHost - cannot be tested

// this test is 15 seconds long. Enable it only if ready to push
/*
it(`${options.name} ${context.adapterShortName} adapter: Check if uptime changes`, function (done) {
this.timeout(20_000);
context.states.getState(`system.adapter.${context.adapterShortName}.0.uptime`, function (err, state1) {
expect(err).to.be.not.ok;
expect(state1).to.be.ok;
expect(state1.val).to.be.ok;
setTimeout(function () {
context.states.getState(
`system.adapter.${context.adapterShortName}.0.uptime`,
function (err, state2) {
expect(err).to.be.not.ok;
expect(state2).to.be.ok;
expect(state2.val).to.be.ok;
if (state2.val !== state1.val) {
expect(state2.val).to.be.above(state1.val);
done();
} else {
setTimeout(function () {
context.states.getState(
`system.adapter.${context.adapterShortName}.0.uptime`,
function (err, state2) {
expect(err).to.be.not.ok;
expect(state2).to.be.ok;
expect(state2.val).to.be.ok;
if (state2.val !== state1.val) {
expect(state2.val).to.be.above(state1.val);
done();
} else {
setTimeout(function () {
context.states.getState(
`system.adapter.${context.adapterShortName}.0.uptime`,
function (err, state2) {
expect(err).to.be.not.ok;
expect(state2).to.be.ok;
expect(state2.val).to.be.ok;
expect(state2.val).to.be.above(state1.val);
done();
}
);
}, 6_000);
}
}
);
}, 5_000);
}
}
);
}, 5_000);
});
});
*/

after(`${options.name} ${context.adapterShortName} adapter: Stop js-controller`, async function () {
this.timeout(35_000);

Expand Down
11 changes: 5 additions & 6 deletions packages/controller/test/lib/testAdapterHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,11 @@ export function register(it: Mocha.TestFunction, expect: Chai.ExpectStatic, cont
done();
});

//getCertificates
it(context.name + ' ' + context.adapterShortName + ' adapter: returns SSL certificates by name', function (done) {
this.timeout(3_000);
// TODO: sync
// TODO: async
done();
// getCertificates
it(context.name + ' ' + context.adapterShortName + ' adapter: returns SSL certificates by name', async () => {
// has to work without chained certificate
const certs = await context.adapter.getCertificatesAsync('defaultPublic', 'defaultPrivate');
expect(certs).to.be.ok;
});

it(context.name + ' ' + context.adapterShortName + ' adapter: get the user id', async () => {
Expand Down
8 changes: 4 additions & 4 deletions packages/types-dev/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,11 @@ declare global {

interface Certificates {
/** private key file */
key: string | Buffer;
key: string;
/** public certificate */
cert: string | Buffer;
cert: string;
/** chained CA certificates */
ca: Array<string | Buffer>;
ca?: string;
}

type MessagePayload = any;
Expand Down Expand Up @@ -395,7 +395,7 @@ declare global {
SecondParameterOf<T>,
null | undefined
>;
/** Infers the return type from a callback-style API and and leaves null and undefined in */
/** Infers the return type from a callback-style API and leaves null and undefined in */
type CallbackReturnTypeOf<T extends (...args: any[]) => any> = SecondParameterOf<T>;

type GetStateCallback = (err?: Error | null, state?: State | null) => void;
Expand Down
4 changes: 2 additions & 2 deletions packages/types-dev/objects.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,8 @@ declare global {
}

interface MetaCommon extends ObjectCommon {
// Meta objects have two additional CommonTypes
type: CommonType | 'meta.user' | 'meta.folder';
// Can be of type `user` for folders, where a user can store files or `folder` for adapter internal structures
type: 'meta.user' | 'meta.folder';

// Make it possible to narrow the object type using the custom property
custom?: undefined;
Expand Down
Loading

0 comments on commit cb37f86

Please sign in to comment.