Skip to content

Commit

Permalink
Pluggable installation of preferences of schema validator (#236)
Browse files Browse the repository at this point in the history
* Pluggable installation of preferences of schema validator

* POC of dynamic import module

* Fix npm script for validator installation and integration of javac validator

* Integration of libxmljs validator

* Add back instruction in README for windows user using libxml as the schema validator

* Make the travis test workflow with javac schema validator
  • Loading branch information
tngan committed Jan 12, 2019
1 parent f91fde4 commit 7004bee
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 50 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ types/
#vscode
.vscode

*.tgz
*.tgz

package-lock.json
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ before_install:
- sudo add-apt-repository ppa:openjdk-r/ppa -y
- sudo apt-get -qq update
- sudo apt-get install -y openjdk-9-jdk
- export SAML_VALIDATOR=javac

script:
- npm test
Expand All @@ -19,7 +20,5 @@ branches:
- master
- /^.*-alpha$/
- /^.*-beta$/
- /^.*-rc*$/
- /^.*-exp*$/

after_success: npm run coverage
18 changes: 17 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,23 @@ pretest: ;
cp -a schemas build; \
cp -a test/key test/misc build/test;

validator: ;
ifeq ($(SAML_VALIDATOR), javac)
@echo "Installing java xsd schema validator ...";
# for java runtime support library
# need to run with npm install, yarn add --ignore-scripts will ignore the postinstall script
# check more information in the package.json of @passify/xsd-schema-validator
npm install @passify/xsd-schema-validator;

else ifeq ($(SAML_VALIDATOR), libxml)
@echo "Installing libxml-xsd ...";
npm install libxml-xsd

else
@echo "No valid SAML_VALIDATOR is chosen";
endif

doc: ;@echo "prepare and serve the docs"; \
docsify serve ./docs

.PHONY: rebuild pretest doc
.PHONY: rebuild pretest doc validator
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@ Welcome all PRs for maintaining this project, or provide a link to the repositor
### Installation
To install the stable version

Starting from v2.5, schema validation becomes selectable and optional, we restrict to apply it in your production environment, you can only ignore schema validation in development mode. When you install samlify in your current project, please assign the environment variable `SAML_VALIDATOR` to either `javac` or `libxml`.

```console
$ SAML_VALIDATOR=javac yarn add samlify
```

For those using Windows, `windows-build-tools` should be installed globally before installing samlify if you are using `libxml` validator.

```console
$ yarn add samlify
$ yarn global add windows-build-tools
```

### Development
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"test": "npm run build && NODE_ENV=test nyc ava --verbose build/test",
"test:pure": "NODE_ENV=test nyc ava --verbose build/test",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"preinstall": "make validator",
"hooks:postinstall": "ln -sf $PWD/.pre-commit.sh $PWD/.git/hooks/pre-commit"
},
"contributors": [
Expand All @@ -33,7 +34,6 @@
"license": "MIT",
"dependencies": {
"@passify/xml-encryption": "^0.11.1",
"@passify/xsd-schema-validator": "^0.7.1",
"deflate-js": "^0.2.3",
"lodash": "^4.17.10",
"node-forge": "^0.7.5",
Expand Down
46 changes: 8 additions & 38 deletions src/libsaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ import { isObject, isUndefined, includes, flattenDeep, camelCase } from 'lodash'
import * as nrsa from 'node-rsa';
import { SignedXml, FileKeyInfo } from 'xml-crypto';
import * as xmlenc from '@passify/xml-encryption';
import * as path from 'path';
import * as fs from 'fs';
import * as Validator from '@passify/xsd-schema-validator';
import { extract } from './extractor';
import { getValidatorModule } from './schema-validator';

const signatureAlgorithms = algorithms.signature;
const digestAlgorithms = algorithms.digest;
Expand Down Expand Up @@ -102,29 +100,6 @@ export interface LibSamlInterface {
}

const libSaml = () => {
const validator = new Validator();
function setSchemaDir() {
let schemaDir;
try {
schemaDir = path.resolve(__dirname, '../schemas');
fs.accessSync(schemaDir, fs.constants.F_OK);
} catch (err) {
// for built-from git folder layout
try {
schemaDir = path.resolve(__dirname, '../../schemas');
fs.accessSync(schemaDir, fs.constants.F_OK);
} catch (err) {
//console.warn('Unable to specify schema directory', err);
// QUESTION should this be swallowed?
console.error(err);
throw new Error('ERR_FAILED_FETCH_SCHEMA_FILE');
}
}
// set schema directory
validator.cwd = schemaDir;
validator.debug = process.env.NODE_ENV === 'test';
}
setSchemaDir();

/**
* @desc helper function to get back the query param for redirect binding for SLO/SSO
Expand Down Expand Up @@ -623,18 +598,13 @@ const libSaml = () => {
* @desc Check if the xml string is valid and bounded
*/
async isValidXml(input: string) {
return new Promise((resolve, reject) => {
validator.validateXML(input, 'saml-schema-protocol-2.0.xsd', (err, result) => {
if (err) {
console.error(err);
return reject('ERR_EXCEPTION_VALIDATE_SAML_RESPONSE');
}
if (result.valid) {
return resolve(true);
}
return reject('ERR_INVALID_SAML_RESPONSE');
});
});
try {
const mod = await getValidatorModule();
await mod.validate(input);
return Promise.resolve();
} catch (e) {
throw e;
}
},
};
};
Expand Down
116 changes: 116 additions & 0 deletions src/schema-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as fs from 'fs';
import * as path from 'path';

enum SchemaValidators {
JAVAC = '@passify/xsd-schema-validator',
LIBXML = 'libxml-xsd'
}

interface SchemaValidator {
validate: (xml: string) => Promise<string>;
}

type GetValidatorModuleSpec = () => Promise<SchemaValidator>;

const moduleResolver = (name: string) => {
try {
require.resolve(name);
return name;
} catch (e) {
return null;
}
};

const getValidatorModule: GetValidatorModuleSpec = async () => {

const selectedValidator: string = moduleResolver(SchemaValidators.JAVAC) || moduleResolver(SchemaValidators.LIBXML);

const xsd = 'saml-schema-protocol-2.0.xsd';

if (selectedValidator === SchemaValidators.JAVAC) {

// TODO: refactor
const setSchemaDir = (v: any) => {
let schemaDir;
try {
schemaDir = path.resolve(__dirname, '../schemas');
fs.accessSync(schemaDir, fs.constants.F_OK);
} catch (err) {
// for built-from git folder layout
try {
schemaDir = path.resolve(__dirname, '../../schemas');
fs.accessSync(schemaDir, fs.constants.F_OK);
} catch (err) {
//console.warn('Unable to specify schema directory', err);
// QUESTION should this be swallowed?
console.error(err);
throw new Error('ERR_FAILED_FETCH_SCHEMA_FILE');
}
}
v.cwd = schemaDir;
v.debug = process.env.NODE_ENV === 'test';
return v;
};

const validator = await import (SchemaValidators.JAVAC);
const mod = setSchemaDir(new validator());

return {
validate: (xml: string) => {
return new Promise((resolve, reject) => {
mod.validateXML(xml, xsd, (err, result) => {
if (err) {
console.error('[ERROR] validateXML', err);
return reject('ERR_EXCEPTION_VALIDATE_XML');
}
if (result.valid) {
return resolve('SUCCESS_VALIDATE_XML');
}
return reject('ERR_INVALID_XML');
});
});
}
};
}

if (selectedValidator === SchemaValidators.LIBXML) {
const mod = await import (SchemaValidators.LIBXML);
return {
validate: (xml: string) => {
return new Promise((resolve, reject) => {
// https://github.com/albanm/node-libxml-xsd/issues/11
process.chdir(path.resolve(__dirname, '../schemas'));
mod.parseFile(path.resolve(xsd), (err, schema) => {
if (err) {
console.error('[ERROR] validateXML', err);
return reject('ERR_INVALID_XML');
}
schema.validate(xml, (techErrors, validationErrors) => {
if (techErrors !== null || validationErrors !== null) {
console.error(`this is not a valid saml response with errors: ${validationErrors}`);
return reject('ERR_EXCEPTION_VALIDATE_XML');
}
return resolve('SUCCESS_VALIDATE_XML');
});
});
});
}
};
}

// allow to skip the validate function if it's in development or test mode if no schema validator is provided
if (process.env.NODE_ENV === 'dev' || process.env.NODE_ENV === 'test') {
return {
validate: (_xml: string) => {
return new Promise((resolve, _reject) => resolve('SKIP_XML_VALIDATION'));
}
};
}

throw new Error('ERR_UNDEFINED_SCHEMA_VALIDATOR_MODULE');

};

export {
getValidatorModule
};
4 changes: 2 additions & 2 deletions src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ function verifyTime(utcNotBefore?: string, utcNotOnOrAfter?: string): boolean {
}

export {
verifyTime
};
verifyTime,
};
4 changes: 0 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@
xmldom "~0.1.15"
xpath "0.0.24"

"@passify/xsd-schema-validator@^0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@passify/xsd-schema-validator/-/xsd-schema-validator-0.7.1.tgz#19d4318320d0ae52ac74076761c9285f2bac00ee"

"@types/lodash@^4.14.112":
version "4.14.116"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.116.tgz#5ccf215653e3e8c786a58390751033a9adca0eb9"
Expand Down

0 comments on commit 7004bee

Please sign in to comment.