From 1cc8ae2975f65cae279b7153849c2d5614e85366 Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Tue, 28 May 2019 23:19:32 +0200 Subject: [PATCH] Added functions getModelBufferContents, getModelFolderContents and getModelContents; --- src/factory.ts | 413 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 269 insertions(+), 144 deletions(-) diff --git a/src/factory.ts b/src/factory.ts index 200ee61..f2ea1b0 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -1,144 +1,269 @@ -import { Pass } from "./pass"; -import { Certificates, isValid } from "./schema"; - -import { promisify } from "util"; -import { readFile as _readFile, readdir as _readdir } from "fs"; -import * as path from "path"; -import forge from "node-forge"; -import formatMessage from "./messages"; - -const readDir = promisify(_readdir); -const readFile = promisify(_readFile); - -interface FactoryOptions { - model: string | { [key: string]: Buffer }, - certificates: Certificates; - overrides?: Object; -} - -async function createPass(options: FactoryOptions) { - if (!(options && Object.keys(options).length)) { - throw new Error("Unable to create Pass: no options were passed"); - } - - // Voglio leggere i certificati - // Voglio leggere il model (se non è un oggetto) - - /* Model checks */ - - if (!options.model) { - throw new Error("Unable to create Pass: no model passed"); - } - - if (typeof options.model !== "string" && typeof options.model !== "object") { - throw new Error("Unable to create Pass: unsupported type"); - } - - if (typeof options.model === "object" && !Object.keys(options.model).length) { - throw new Error("Unable to create Pass: object model has no content"); - } - - /* Certificates checks */ - - const { certificates } = await Promise.all([ - readCertificatesFromOptions(options.certificates) - ]); - - // Controllo se il model è un oggetto o una stringa - // Se è un oggetto passo avanti - // Se è una stringa controllo se è un path. Se è un path - // faccio readdir - // altrimenti throw - - // Creare una funzione che possa controllare ed estrarre i certificati - // Creare una funzione che possa controllare ed estrarre i file - // Entrambe devono ritornare Promise, così faccio await Promise.all - - return new Pass(); -} - -/** - * Reads certificate contents, if the passed content is a path, - * and parses them as a PEM. - * @param options - */ - -interface FinalCertificates { - wwdr: forge.pki.Certificate; - signerCert: forge.pki.Certificate; - signerKey: forge.pki.PrivateKey; -} - -async function readCertificatesFromOptions(options: Certificates): Promise { - if (!isValid(options, "certificatesSchema")) { - throw new Error("Unable to create Pass: certificates schema validation failed."); - } - - // if the signerKey is an object, we want to get - // all the real contents and don't care of passphrase - const flattenedDocs = Object.assign({}, options, { - signerKey: ( - typeof options.signerKey === "string" - ? options.signerKey - : options.signerKey.keyFile - ) - }); - - // We read the contents - const rawContentsPromises = Object.keys(flattenedDocs) - .map(content => { - if (!!path.parse(content).ext) { - // The content is a path to the document - return readFile(path.resolve(content), { encoding: "utf8"}); - } else { - // Content is the real document content - return Promise.resolve(content); - } - }); - - try { - const parsedContents = await Promise.all(rawContentsPromises); - const pemParsedContents = parsedContents.map((file, index) => { - const certName = Object.keys(options)[index]; - const pem = parsePEM( - certName, - file, - typeof options.signerKey === "object" - ? options.signerKey.passphrase - : undefined - ); - - if (!pem) { - throw new Error(formatMessage("INVALID_CERTS", certName)); - } - - return { [certName]: pem }; - }); - - return Object.assign({}, ...pemParsedContents); - } catch (err) { - if (!err.path) { - throw err; - } - - throw new Error(formatMessage("INVALID_CERT_PATH", path.parse(err.path).base)); - } -} - -/** - * Parses the PEM-formatted passed text (certificates) - * - * @param element - Text content of .pem files - * @param passphrase - passphrase for the key - * @returns The parsed certificate or key in node forge format - */ - -function parsePEM(pemName: string, element: string, passphrase?: string) { - if (pemName === "signerKey" && passphrase) { - return forge.pki.decryptRsaPrivateKey(element, String(passphrase)); - } else { - return forge.pki.certificateFromPem(element); - } -} - -module.exports = { createPass }; +import { Pass } from "./pass"; +import { Certificates, isValid } from "./schema"; + +import { promisify } from "util"; +import { readFile as _readFile, readdir as _readdir } from "fs"; +import * as path from "path"; +import forge from "node-forge"; +import formatMessage from "./messages"; + +const readDir = promisify(_readdir); +const readFile = promisify(_readFile); + +interface FactoryOptions { + model: string | { [key: string]: Buffer }, + certificates: Certificates; + overrides?: Object; +} + +async function createPass(options: FactoryOptions) { + if (!(options && Object.keys(options).length)) { + throw new Error("Unable to create Pass: no options were passed"); + } + + // Voglio leggere i certificati + // Voglio leggere il model (se non è un oggetto) + + /* Model checks */ + + if (!options.model) { + throw new Error("Unable to create Pass: no model passed"); + } + + if (typeof options.model !== "string" && typeof options.model !== "object") { + throw new Error("Unable to create Pass: unsupported type"); + } + + if (typeof options.model === "object" && !Object.keys(options.model).length) { + throw new Error("Unable to create Pass: object model has no content"); + } + + /* Certificates checks */ + + const { certificates } = await Promise.all([ + readCertificatesFromOptions(options.certificates) + ]); + + // Controllo se il model è un oggetto o una stringa + // Se è un oggetto passo avanti + // Se è una stringa controllo se è un path. Se è un path + // faccio readdir + // altrimenti throw + + // Creare una funzione che possa controllare ed estrarre i certificati + // Creare una funzione che possa controllare ed estrarre i file + // Entrambe devono ritornare Promise, così faccio await Promise.all + + return new Pass(); +} + +interface BundleUnit { + [key: string]: Buffer; +} + +async function getModelContents(model: FactoryOptions["model"]) { + if (!(model && (typeof model === "string" || (typeof model === "object" && Object.keys(model).length)))) { + throw new Error("Unable to create Pass: invalid model provided"); + } + + let modelContents: { + bundle: BundleUnit, + l10nBundle: { + [key: string]: BundleUnit + }, + }; + + if (typeof model === "string") { + modelContents = await getModelFolderContents(model); + } else { + modelContents = getModelBufferContents(model); + } + + const modelFiles = Object.keys(modelContents); + + if (!(modelFiles.includes("pass.json") && modelFiles.some(file => file.includes("icon")))) { + throw new Error("missing icon or pass.json"); + } + + return modelContents; +} + +async function getModelFolderContents(model: string) { + const modelPath = path.resolve(model) + (!!model && !path.extname(model) ? ".pass" : ""); + const modelFilesList = await readDir(modelPath); + + // No dot-starting files, manifest and signature + const filteredFiles = removeHidden(modelFilesList).filter(f => !/(manifest|signature|pass)/i.test(f)); + + // Icon is required to proceed + if (!(filteredFiles.length && filteredFiles.some(file => file.toLowerCase().includes("icon")))) { + const eMessage = formatMessage("MODEL_UNINITIALIZED", path.parse(this.model).name); + throw new Error(eMessage); + } + + // Splitting files from localization folders + const bundle = filteredFiles.filter(entry => !entry.includes(".lproj")); + const l10nFolders = filteredFiles.filter(entry => entry.includes(".lproj")); + + const bundleBuffers = bundle.map(file => readFile(path.resolve(model, file))); + const buffers = await Promise.all(bundleBuffers); + + const bundleMap = Object.assign({}, + ...bundle.map((fileName, index) => ({ [fileName]: buffers[index] })) + ) as BundleUnit; + + // Reading concurrently localizations folder + // and their files and their buffers + const L10N_FilesListByFolder: Array = await Promise.all( + l10nFolders.map(folderPath => { + // Reading current folder + const currentLangPath = path.join(model, folderPath); + return readDir(currentLangPath) + .then(files => { + // Transforming files path to a model-relative path + const validFiles = removeHidden(files) + .map(file => path.join(currentLangPath, file)); + + // Getting all the buffers from file paths + return Promise.all([ + ...validFiles.map(file => readFile(file)) + ]).then(buffers => + // Assigning each file path to its buffer + Object.assign({}, ...validFiles.map((file, index) => + ({ [file]: buffers[index] }) + )) as BundleUnit + ); + }); + }) + ); + + return { + bundle: bundleMap, + l10nBundle: Object.assign({}, ...L10N_FilesListByFolder + .map((folder, index) => ({ [l10nFolders[index]]: folder })) + ) as { [key: string]: BundleUnit } + }; +} + +function getModelBufferContents(model: BundleUnit) { + const bundle = removeHidden(Object.keys(model)).reduce((acc, current) => { + // Checking if current file is one of the autogenerated ones or if its + // content is not available + if (/(manifest|signature)/.test(current) || !bundle[current]) { + return acc; + } + + return { ...acc, [current]: model[current] }; + }, {}); + + const bundleKeys = Object.keys(bundle); + + if (!bundleKeys.length) { + throw new Error("Cannot proceed with pass creation: bundle initialized") + } + + // separing localization folders + const l10nFolders = bundleKeys.filter(file => file.includes(".lproj")); + const l10nBundle: { [key: string]: BundleUnit } = Object.assign({}, + ...l10nFolders.map(folder => + ({ [folder]: bundle[folder] }) as BundleUnit + ) + ); + + const unLocalizedBundle = Object.assign({}, + ...bundleKeys + .filter(file => !file.includes(".lproj")) + .map(file => ({ [file]: bundle[file] })) + ) as BundleUnit; + + return { + bundle: unLocalizedBundle, + l10nBundle + }; +} + +/** + * Reads certificate contents, if the passed content is a path, + * and parses them as a PEM. + * @param options + */ + +interface FinalCertificates { + wwdr: forge.pki.Certificate; + signerCert: forge.pki.Certificate; + signerKey: forge.pki.PrivateKey; +} + +async function readCertificatesFromOptions(options: Certificates): Promise { + if (!isValid(options, "certificatesSchema")) { + throw new Error("Unable to create Pass: certificates schema validation failed."); + } + + // if the signerKey is an object, we want to get + // all the real contents and don't care of passphrase + const flattenedDocs = Object.assign({}, options, { + signerKey: ( + typeof options.signerKey === "string" + ? options.signerKey + : options.signerKey.keyFile + ) + }); + + // We read the contents + const rawContentsPromises = Object.keys(flattenedDocs) + .map(content => { + if (!!path.parse(content).ext) { + // The content is a path to the document + return readFile(path.resolve(content), { encoding: "utf8"}); + } else { + // Content is the real document content + return Promise.resolve(content); + } + }); + + try { + const parsedContents = await Promise.all(rawContentsPromises); + const pemParsedContents = parsedContents.map((file, index) => { + const certName = Object.keys(options)[index]; + const pem = parsePEM( + certName, + file, + typeof options.signerKey === "object" + ? options.signerKey.passphrase + : undefined + ); + + if (!pem) { + throw new Error(formatMessage("INVALID_CERTS", certName)); + } + + return { [certName]: pem }; + }); + + return Object.assign({}, ...pemParsedContents); + } catch (err) { + if (!err.path) { + throw err; + } + + throw new Error(formatMessage("INVALID_CERT_PATH", path.parse(err.path).base)); + } +} + +/** + * Parses the PEM-formatted passed text (certificates) + * + * @param element - Text content of .pem files + * @param passphrase - passphrase for the key + * @returns The parsed certificate or key in node forge format + */ + +function parsePEM(pemName: string, element: string, passphrase?: string) { + if (pemName === "signerKey" && passphrase) { + return forge.pki.decryptRsaPrivateKey(element, String(passphrase)); + } else { + return forge.pki.certificateFromPem(element); + } +} + +module.exports = { createPass };