From d21164af5800b53fc223fd9e4b0ad25249c8bca1 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Thu, 19 Jan 2017 01:01:37 +0100 Subject: [PATCH 01/19] Validate first factor through a post request --- package.json | 1 + src/lib/authentication.js | 39 --------- src/lib/{ldap_checker.js => ldap.js} | 7 +- src/lib/routes.js | 20 ++--- src/lib/routes/first_factor.js | 30 +++++++ src/lib/server.js | 3 +- test/unitary/routes/test_first_factor.js | 58 +++++++++++++ test/unitary/test_authentication.js | 65 ++++---------- test/unitary/test_ldap_checker.js | 8 +- test/unitary/test_server.js | 105 +++++++++++++---------- 10 files changed, 184 insertions(+), 152 deletions(-) rename src/lib/{ldap_checker.js => ldap.js} (52%) create mode 100644 src/lib/routes/first_factor.js create mode 100644 test/unitary/routes/test_first_factor.js diff --git a/package.json b/package.json index 7fd7ba080..c0d93b5e0 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "url": "https://github.com/clems4ever/two-factor-auth-server/issues" }, "dependencies": { + "bluebird": "^3.4.7", "body-parser": "^1.15.2", "cookie-parser": "^1.4.3", "ejs": "^2.5.5", diff --git a/src/lib/authentication.js b/src/lib/authentication.js index cf8e66f10..ee45da350 100644 --- a/src/lib/authentication.js +++ b/src/lib/authentication.js @@ -1,52 +1,13 @@ module.exports = { - 'authenticate': authenticate, 'verify': verify_authentication } var objectPath = require('object-path'); -var ldap_checker = require('./ldap_checker'); var totp_checker = require('./totp_checker'); var replies = require('./replies'); -var Q = require('q'); var utils = require('./utils'); - -function authenticate(req, res) { - var defer = Q.defer(); - var username = req.body.username; - var password = req.body.password; - var token = req.body.token; - console.log('Start authentication of user %s', username); - - if(!username || !password || !token) { - replies.authentication_failed(res); - return; - } - - var jwt_engine = req.app.get('jwt engine'); - var ldap_client = req.app.get('ldap client'); - var totp_engine = req.app.get('totp engine'); - var config = req.app.get('config'); - - var totp_promise = totp_checker.validate(totp_engine, token, config.totp_secret); - var credentials_promise = ldap_checker.validate(ldap_client, username, password, config.ldap_users_dn); - - Q.all([totp_promise, credentials_promise]) - .then(function() { - var token = jwt_engine.sign({ user: username }, config.jwt_expiration_time); - replies.authentication_succeeded(res, username, token); - console.log('Authentication succeeded'); - defer.resolve(); - }) - .fail(function(err1, err2) { - console.log('Authentication failed', err1, err2); - replies.authentication_failed(res); - defer.reject(); - }); - return defer.promise; -} - function verify_authentication(req, res) { console.log('Verify authentication'); diff --git a/src/lib/ldap_checker.js b/src/lib/ldap.js similarity index 52% rename from src/lib/ldap_checker.js rename to src/lib/ldap.js index 8f416a76a..77a157872 100644 --- a/src/lib/ldap_checker.js +++ b/src/lib/ldap.js @@ -3,12 +3,11 @@ module.exports = { 'validate': validateCredentials } -var Q = require('q'); var util = require('util'); -var utils = require('./utils'); +var Promise = require('bluebird'); function validateCredentials(ldap_client, username, password, users_dn) { - var userDN = util.format("cn=%s,%s", username, users_dn); - var bind_promised = utils.promisify(ldap_client.bind, ldap_client); + var userDN = util.format("binding entry cn=%s,%s", username, users_dn); + var bind_promised = Promise.promisify(ldap_client.bind, ldap_client); return bind_promised(userDN, password); } diff --git a/src/lib/routes.js b/src/lib/routes.js index 49120c9af..ada664bf0 100644 --- a/src/lib/routes.js +++ b/src/lib/routes.js @@ -1,20 +1,18 @@ +var first_factor = require('./routes/first_factor'); + module.exports = { - 'auth': serveAuth, - 'login': serveLogin, - 'logout': serveLogout + auth: serveAuth, + login: serveLogin, + logout: serveLogout, + first_factor: first_factor } var authentication = require('./authentication'); var replies = require('./replies'); function serveAuth(req, res) { - if(req.method == 'POST') { - serveAuthPost(req, res); - } - else { - serveAuthGet(req, res); - } + serveAuthGet(req, res); } function serveAuthGet(req, res) { @@ -28,10 +26,6 @@ function serveAuthGet(req, res) { }); } -function serveAuthPost(req, res) { - authentication.authenticate(req, res); -} - function serveLogin(req, res) { res.render('login'); } diff --git a/src/lib/routes/first_factor.js b/src/lib/routes/first_factor.js new file mode 100644 index 000000000..4c4ca779a --- /dev/null +++ b/src/lib/routes/first_factor.js @@ -0,0 +1,30 @@ + +module.exports = first_factor; + +var ldap = require('../ldap'); + +function first_factor(req, res) { + var username = req.body.username; + var password = req.body.password; + console.log('Start authentication of user %s', username); + + if(!username || !password) { + replies.authentication_failed(res); + return; + } + + var ldap_client = req.app.get('ldap client'); + var config = req.app.get('config'); + + ldap.validate(ldap_client, username, password, config.ldap_users_dn) + .then(function() { + res.status(204); + res.send(); + console.log('LDAP binding successful'); + }) + .error(function(err) { + res.status(401); + res.send(); + console.log('LDAP binding failed:', err); + }); +} diff --git a/src/lib/server.js b/src/lib/server.js index 73c6bb220..526297367 100644 --- a/src/lib/server.js +++ b/src/lib/server.js @@ -33,7 +33,8 @@ function run(config, ldap_client) { app.get ('/logout', routes.logout); app.get ('/_auth', routes.auth); - app.post ('/_auth', routes.auth); + + app.post ('/_auth/1stfactor', routes.first_factor); app.listen(config.port, function(err) { console.log('Listening on %d...', config.port); diff --git a/test/unitary/routes/test_first_factor.js b/test/unitary/routes/test_first_factor.js new file mode 100644 index 000000000..af8018739 --- /dev/null +++ b/test/unitary/routes/test_first_factor.js @@ -0,0 +1,58 @@ + +var sinon = require('sinon'); +var Promise = require('bluebird'); +var assert = require('assert'); +var first_factor = require('../../../src/lib/routes/first_factor'); + +describe('test the first factor validation route', function() { + it('should return status code 204 when LDAP binding succeeds', function() { + return test_first_factor_promised({ error: undefined, data: undefined }, 204); + }); + + it('should return status code 401 when LDAP binding fails', function() { + return test_first_factor_promised({ error: 'ldap failed', data: undefined }, 401); + }); +}); + + +function test_first_factor_promised(bind_params, statusCode) { + return new Promise(function(resolve, reject) { + test_first_factor(bind_params, statusCode, resolve, reject); + }); +} + +function test_first_factor(bind_params, statusCode, resolve, reject) { + var send = sinon.spy(function(data) { + resolve(); + }); + var status = sinon.spy(function(code) { + assert.equal(code, statusCode); + }); + + var bind_mock = sinon.stub().yields(bind_params.error, bind_params.data); + var ldap_interface_mock = { + bind: bind_mock + } + var config = { + ldap_users_dn: 'dc=example,dc=com' + } + + var app_get = sinon.stub(); + app_get.withArgs('ldap client').returns(ldap_interface_mock); + app_get.withArgs('config').returns(ldap_interface_mock); + var req = { + app: { + get: app_get + }, + body: { + username: 'username', + password: 'password' + } + } + var res = { + send: send, + status: status + } + + first_factor(req, res); +} diff --git a/test/unitary/test_authentication.js b/test/unitary/test_authentication.js index 03bea70e4..1c81c89d0 100644 --- a/test/unitary/test_authentication.js +++ b/test/unitary/test_authentication.js @@ -75,59 +75,30 @@ function create_mocks() { } } -describe('test jwt', function() { - describe('test authentication', function() { - it('should authenticate user successfuly', function(done) { - var jwt_token = 'jwt_token'; - var clock = sinon.useFakeTimers(); - var mocks = create_mocks(); - authentication.authenticate(mocks.req, mocks.res) - .then(function() { - clock.restore(); - assert(mocks.res.status.calledWith(200)); - assert(mocks.res.send.calledWith(jwt_token)); - done(); - }) +describe('test authentication token verification', function() { + it('should be already authenticated', function(done) { + var mocks = create_mocks(); + var data = { user: 'username' }; + mocks.req.app.get.withArgs('jwt engine').returns({ + verify: sinon.promise().resolves(data) }); - it('should fail authentication', function(done) { - var clock = sinon.useFakeTimers(); - var mocks = create_mocks(); - mocks.totp.returns('wrong token'); - authentication.authenticate(mocks.req, mocks.res) - .fail(function(err) { - clock.restore(); - done(); - }) + authentication.verify(mocks.req, mocks.res) + .then(function(actual_data) { + assert.equal(actual_data, data); + done(); }); }); - - describe('test verify authentication', function() { - it('should be already authenticated', function(done) { - var mocks = create_mocks(); - var data = { user: 'username' }; - mocks.req.app.get.withArgs('jwt engine').returns({ - verify: sinon.promise().resolves(data) - }); - - authentication.verify(mocks.req, mocks.res) - .then(function(actual_data) { - assert.equal(actual_data, data); - done(); - }); + it('should not be already authenticated', function(done) { + var mocks = create_mocks(); + var data = { user: 'username' }; + mocks.req.app.get.withArgs('jwt engine').returns({ + verify: sinon.promise().rejects('Error with JWT token') }); - - it('should not be already authenticated', function(done) { - var mocks = create_mocks(); - var data = { user: 'username' }; - mocks.req.app.get.withArgs('jwt engine').returns({ - verify: sinon.promise().rejects('Error with JWT token') - }); - return authentication.verify(mocks.req, mocks.res, mocks.args) - .fail(function() { - done(); - }); + return authentication.verify(mocks.req, mocks.res, mocks.args) + .fail(function() { + done(); }); }); }); diff --git a/test/unitary/test_ldap_checker.js b/test/unitary/test_ldap_checker.js index 8dfd65228..9cc023cda 100644 --- a/test/unitary/test_ldap_checker.js +++ b/test/unitary/test_ldap_checker.js @@ -1,5 +1,5 @@ -var ldap_checker = require('../../src/lib/ldap_checker'); +var ldap = require('../../src/lib/ldap'); var sinon = require('sinon'); var sinonPromise = require('sinon-promise'); @@ -17,10 +17,10 @@ function test_validate(bind_mock) { bind: bind_mock } - return ldap_checker.validate(ldap_client_mock, username, password, ldap_url, users_dn); + return ldap.validate(ldap_client_mock, username, password, ldap_url, users_dn); } -describe('test ldap checker', function() { +describe('test ldap validation', function() { it('should bind the user if good credentials provided', function() { var bind_mock = sinon.mock().yields(); return test_validate(bind_mock); @@ -29,7 +29,7 @@ describe('test ldap checker', function() { it('should not bind the user if wrong credentials provided', function() { var bind_mock = sinon.mock().yields('wrong credentials'); var promise = test_validate(bind_mock); - return promise.fail(autoResolving); + return promise.error(autoResolving); }); }); diff --git a/test/unitary/test_server.js b/test/unitary/test_server.js index 18e030e9d..f2c4be57a 100644 --- a/test/unitary/test_server.js +++ b/test/unitary/test_server.js @@ -6,6 +6,9 @@ var request = require('request'); var assert = require('assert'); var speakeasy = require('speakeasy'); var sinon = require('sinon'); +var Promise = require('bluebird'); + +var request = Promise.promisifyAll(request); var BASE_URL = 'http://localhost:8090'; @@ -46,8 +49,8 @@ describe('test the server', function() { test_get_auth(jwt); }); - describe('test POST /_auth', function() { - test_post_auth(jwt); + describe('test POST /_auth/1stfactor', function() { + test_post_auth_1st_factor(); }); }); @@ -95,50 +98,64 @@ function test_get_auth(jwt) { }); } -function test_post_auth() { - it('should return the JWT token when authentication is successful', function(done) { - var clock = sinon.useFakeTimers(); - var real_token = speakeasy.totp({ - secret: 'totp_secret', - encoding: 'base32' - }); - var expectedJwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdF9vayIsImlhdCI6MCwiZXhwIjozNjAwfQ.ihvaljGjO5h3iSO_h3PkNNSCYeePyB8Hr5lfVZZYyrQ'; - - request.post(BASE_URL + '/_auth', { +function test_post_auth_1st_factor() { + it('should return status code 204 when ldap bind is successful', function() { + request.postAsync(BASE_URL + '/_auth/1stfactor', { form: { - username: 'test_ok', - password: 'password', - token: real_token - } - }, - function (error, response, body) { - if (!error && response.statusCode == 200) { - assert.equal(body, expectedJwt); - clock.restore(); - done(); - } - }); - }); - - it('should return invalid authentication status code', function(done) { - var clock = sinon.useFakeTimers(); - var real_token = speakeasy.totp({ - secret: 'totp_secret', - encoding: 'base32' - }); - var data = { - form: { - username: 'test_nok', - password: 'password', - token: real_token - } - } - - request.post(BASE_URL + '/_auth', data, function (error, response, body) { - if(response.statusCode == 401) { - clock.restore(); - done(); + username: 'username', + password: 'password' } + }) + .then(function(response) { + assert.equal(response.statusCode, 204); + return Promise.resolve(); }); }); } +// function test_post_auth_totp() { +// it('should return the JWT token when authentication is successful', function(done) { +// var clock = sinon.useFakeTimers(); +// var real_token = speakeasy.totp({ +// secret: 'totp_secret', +// encoding: 'base32' +// }); +// var expectedJwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdF9vayIsImlhdCI6MCwiZXhwIjozNjAwfQ.ihvaljGjO5h3iSO_h3PkNNSCYeePyB8Hr5lfVZZYyrQ'; +// +// request.post(BASE_URL + '/_auth/totp', { +// form: { +// username: 'test_ok', +// password: 'password', +// token: real_token +// } +// }, +// function (error, response, body) { +// if (!error && response.statusCode == 200) { +// assert.equal(body, expectedJwt); +// clock.restore(); +// done(); +// } +// }); +// }); +// +// it('should return invalid authentication status code', function(done) { +// var clock = sinon.useFakeTimers(); +// var real_token = speakeasy.totp({ +// secret: 'totp_secret', +// encoding: 'base32' +// }); +// var data = { +// form: { +// username: 'test_nok', +// password: 'password', +// token: real_token +// } +// } +// +// request.post(BASE_URL + '/_auth/totp', data, function (error, response, body) { +// if(response.statusCode == 401) { +// clock.restore(); +// done(); +// } +// }); +// }); +// } From 8c743228bff46608dad1b7dadb3f65bf9d74bb96 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Thu, 19 Jan 2017 01:44:24 +0100 Subject: [PATCH 02/19] Use promises in jwt component --- src/lib/authentication.js | 4 +--- src/lib/jwt.js | 29 ++++++++++++++----------- src/lib/routes.js | 2 +- src/lib/{totp_checker.js => totp.js} | 0 test/unitary/test_jwt.js | 32 +++++++++++++++++----------- test/unitary/test_server.js | 18 +++++++++------- test/unitary/test_totp_checker.js | 6 +++--- 7 files changed, 50 insertions(+), 41 deletions(-) rename src/lib/{totp_checker.js => totp.js} (100%) diff --git a/src/lib/authentication.js b/src/lib/authentication.js index ee45da350..74993038d 100644 --- a/src/lib/authentication.js +++ b/src/lib/authentication.js @@ -1,11 +1,9 @@ module.exports = { - 'verify': verify_authentication + verify: verify_authentication } var objectPath = require('object-path'); -var totp_checker = require('./totp_checker'); -var replies = require('./replies'); var utils = require('./utils'); function verify_authentication(req, res) { diff --git a/src/lib/jwt.js b/src/lib/jwt.js index 1fcba6794..7cc00d001 100644 --- a/src/lib/jwt.js +++ b/src/lib/jwt.js @@ -3,27 +3,30 @@ module.exports = Jwt; var jwt = require('jsonwebtoken'); var utils = require('./utils'); -var Q = require('q'); +var Promise = require('bluebird'); function Jwt(secret) { - var _secret; - this._secret = secret; } Jwt.prototype.sign = function(data, expiration_time) { - return jwt.sign(data, this._secret, { expiresIn: expiration_time }); + var that = this; + return new Promise(function(resolve, reject) { + var token = jwt.sign(data, that._secret, { expiresIn: expiration_time }) + resolve(token); + }); } Jwt.prototype.verify = function(token) { - var defer = Q.defer(); - try { - var decoded = jwt.verify(token, this._secret); - defer.resolve(decoded); - } - catch(err) { - defer.reject(err); - } - return defer.promise; + var that = this; + return new Promise(function(resolve, reject) { + try { + var decoded = jwt.verify(token, that._secret); + resolve(decoded); + } + catch(err) { + reject(err.message); + } + }); } diff --git a/src/lib/routes.js b/src/lib/routes.js index ada664bf0..507726e3b 100644 --- a/src/lib/routes.js +++ b/src/lib/routes.js @@ -20,7 +20,7 @@ function serveAuthGet(req, res) { .then(function(user) { replies.already_authenticated(res, user); }) - .fail(function(err) { + .catch(function(err) { replies.authentication_failed(res); console.error(err); }); diff --git a/src/lib/totp_checker.js b/src/lib/totp.js similarity index 100% rename from src/lib/totp_checker.js rename to src/lib/totp.js diff --git a/test/unitary/test_jwt.js b/test/unitary/test_jwt.js index 2f37d5009..0285fb48f 100644 --- a/test/unitary/test_jwt.js +++ b/test/unitary/test_jwt.js @@ -1,33 +1,39 @@ var Jwt = require('../../src/lib/jwt'); var sinon = require('sinon'); -var sinonPromise = require('sinon-promise'); -sinonPromise(sinon); - -var autoResolving = sinon.promise().resolves(); describe('test jwt', function() { it('should sign and verify the token', function() { var data = {user: 'user'}; var secret = 'secret'; var jwt = new Jwt(secret); - var token = jwt.sign(data, '1m'); - return jwt.verify(token); + return jwt.sign(data, '1m') + .then(function(token) { + return jwt.verify(token); + }); }); it('should verify and fail on wrong token', function() { var jwt = new Jwt('secret'); - return jwt.verify('wrong token').fail(autoResolving); + var token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlciIsImlhdCI6MTQ4NDc4NTExMywiZXhwIjoaNDg0Nzg1MTczfQ.yZOZEaMDyOn0tSDiDSPYl4ZP2oL3FQ-Vrzds7hYcNio'; + return jwt.verify(token).catch(function() { + return Promise.resolve(); + }); }); - it('should fail after expiry', function(done) { + it('should fail after expiry', function() { var clock = sinon.useFakeTimers(0); - var data = {user: 'user'}; + var data = { user: 'user' }; var jwt = new Jwt('secret'); - var token = jwt.sign(data, '1m'); - clock.tick(1000 * 61); // 61 seconds - jwt.verify(token).fail(function() { done(); }); - clock.restore(); + return jwt.sign(data, '1m') + .then(function(token) { + clock.tick(1000 * 61); // 61 seconds + return jwt.verify(token); + }) + .catch(function() { + clock.restore(); + return Promise.resolve(); + }); }); }); diff --git a/test/unitary/test_server.js b/test/unitary/test_server.js index f2c4be57a..5374073b0 100644 --- a/test/unitary/test_server.js +++ b/test/unitary/test_server.js @@ -86,15 +86,17 @@ function test_get_auth(jwt) { it('should return status code 204 when user is authenticated', function(done) { var j = request.jar(); var r = request.defaults({jar: j}); - var token = jwt.sign({ user: 'test' }, '1h'); - var cookie = r.cookie('access_token=' + token); - j.setCookie(cookie, BASE_URL + '/_auth'); + jwt.sign({ user: 'test' }, '1h') + .then(function(token) { + var cookie = r.cookie('access_token=' + token); + j.setCookie(cookie, BASE_URL + '/_auth'); - r.get(BASE_URL + '/_auth') - .on('response', function(response) { - assert.equal(response.statusCode, 204); - done(); - }) + r.get(BASE_URL + '/_auth') + .on('response', function(response) { + assert.equal(response.statusCode, 204); + done(); + }); + }); }); } diff --git a/test/unitary/test_totp_checker.js b/test/unitary/test_totp_checker.js index c1582f513..948813b21 100644 --- a/test/unitary/test_totp_checker.js +++ b/test/unitary/test_totp_checker.js @@ -1,5 +1,5 @@ -var totp_checker = require('../../src/lib/totp_checker'); +var totp = require('../../src/lib/totp'); var sinon = require('sinon'); var sinonPromise = require('sinon-promise'); sinonPromise(sinon); @@ -15,7 +15,7 @@ describe('test TOTP checker', function() { var speakeasy_mock = { totp: totp_mock } - return totp_checker.validate(speakeasy_mock, token, totp_secret); + return totp.validate(speakeasy_mock, token, totp_secret); }); it('should not validate a wrong TOTP token', function() { @@ -26,7 +26,7 @@ describe('test TOTP checker', function() { var speakeasy_mock = { totp: totp_mock } - return totp_checker.validate(speakeasy_mock, token, totp_secret).fail(autoResolving); + return totp.validate(speakeasy_mock, token, totp_secret).fail(autoResolving); }); }); From 9670b23a8b5e2ff93f7ff48f4f717d90556d666c Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sat, 21 Jan 2017 17:41:06 +0100 Subject: [PATCH 03/19] Implement FIDO u2f authentication --- .travis.yml | 2 +- docker-compose.dev.yml | 8 + docker-compose.yml | 7 +- nginx_conf/nginx.conf | 26 +- nginx_conf/ssl/server.crt | 13 + nginx_conf/ssl/server.csr | 11 + nginx_conf/ssl/server.key | 15 + package.json | 12 +- src/index.js | 7 +- src/lib/authentication.js | 19 - src/lib/jwt.js | 32 - src/lib/ldap.js | 4 +- src/lib/replies.js | 27 - src/lib/routes.js | 34 +- src/lib/routes/deny_not_logged.js | 24 + src/lib/routes/first_factor.js | 19 +- src/lib/routes/second_factor.js | 16 + src/lib/routes/totp.js | 33 + src/lib/routes/u2f.js | 144 ++++ src/lib/routes/verify.js | 37 + src/lib/server.js | 37 +- src/lib/totp.js | 26 +- src/public_html/js.cookie.min.js | 2 - src/public_html/login.css | 32 +- src/public_html/login.js | 274 +++++-- src/public_html/notify.min.js | 1 + src/public_html/u2f-api.js | 748 ++++++++++++++++++ src/views/login.ejs | 33 +- test/integration/test_server.js | 93 ++- test/unitary/routes/test_deny_not_logged.js | 83 ++ test/unitary/routes/test_first_factor.js | 96 ++- test/unitary/routes/test_totp.js | 78 ++ test/unitary/routes/test_u2f.js | 230 ++++++ test/unitary/routes/test_verify.js | 63 ++ test/unitary/test_authentication.js | 105 --- test/unitary/test_jwt.js | 39 - test/unitary/test_ldap.js | 52 ++ test/unitary/test_ldap_checker.js | 35 - test/unitary/test_replies.js | 52 -- test/unitary/test_server.js | 269 ++++--- .../{test_totp_checker.js => test_totp.js} | 12 +- 41 files changed, 2187 insertions(+), 663 deletions(-) create mode 100644 docker-compose.dev.yml create mode 100644 nginx_conf/ssl/server.crt create mode 100644 nginx_conf/ssl/server.csr create mode 100644 nginx_conf/ssl/server.key delete mode 100644 src/lib/authentication.js delete mode 100644 src/lib/jwt.js delete mode 100644 src/lib/replies.js create mode 100644 src/lib/routes/deny_not_logged.js create mode 100644 src/lib/routes/second_factor.js create mode 100644 src/lib/routes/totp.js create mode 100644 src/lib/routes/u2f.js create mode 100644 src/lib/routes/verify.js delete mode 100644 src/public_html/js.cookie.min.js create mode 100644 src/public_html/notify.min.js create mode 100644 src/public_html/u2f-api.js create mode 100644 test/unitary/routes/test_deny_not_logged.js create mode 100644 test/unitary/routes/test_totp.js create mode 100644 test/unitary/routes/test_u2f.js create mode 100644 test/unitary/routes/test_verify.js delete mode 100644 test/unitary/test_authentication.js delete mode 100644 test/unitary/test_jwt.js create mode 100644 test/unitary/test_ldap.js delete mode 100644 test/unitary/test_ldap_checker.js delete mode 100644 test/unitary/test_replies.js rename test/unitary/{test_totp_checker.js => test_totp.js} (72%) diff --git a/.travis.yml b/.travis.yml index 84b9c1b98..b5f3617cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ script: - docker-compose build - docker-compose up -d - sleep 5 -- npm run-script integration-test +- npm run-script int-test deploy: provider: npm email: clement.michaud34@gmail.com diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 000000000..bd439c025 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,8 @@ + +version: '2' +services: + auth: + volumes: + - ./src/views:/usr/src/views + - ./src/public_html:/usr/src/public_html + diff --git a/docker-compose.yml b/docker-compose.yml index 23ceb2355..d9ea01a75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,8 @@ services: - LDAP_URL=ldap://ldap - LDAP_USERS_DN=dc=example,dc=com - TOTP_SECRET=GRWGIJS6IRHVEODVNRCXCOBMJ5AGC6ZE - - JWT_SECRET=unsecure_secret - - JWT_EXPIRATION_TIME=1h + - SESSION_SECRET=unsecure_secret + - SESSION_EXPIRATION_TIME=3600000 depends_on: - ldap restart: always @@ -28,7 +28,8 @@ services: - ./nginx_conf/nginx.conf:/etc/nginx/nginx.conf - ./nginx_conf/index.html:/usr/share/nginx/html/index.html - ./nginx_conf/secret.html:/usr/share/nginx/html/secret.html + - ./nginx_conf/ssl:/etc/ssl depends_on: - auth ports: - - "8080:80" + - "8080:443" diff --git a/nginx_conf/nginx.conf b/nginx_conf/nginx.conf index b50139f14..be07e2078 100644 --- a/nginx_conf/nginx.conf +++ b/nginx_conf/nginx.conf @@ -16,7 +16,6 @@ worker_processes 1; #pid logs/nginx.pid; - events { worker_connections 1024; } @@ -24,22 +23,28 @@ events { http { server { - listen 80; + listen 443 ssl; root /usr/share/nginx/html; + + server_name 127.0.0.1 localhost; + + ssl on; + ssl_certificate /etc/ssl/server.crt; + ssl_certificate_key /etc/ssl/server.key; error_page 401 = @error401; location @error401 { - return 302 http://localhost:8080/auth/login?redirect=$request_uri; + return 302 https://localhost:8080/auth/login?redirect=$request_uri; } - location = /check-auth { + location = /verify { internal; # proxy_pass_request_body off; proxy_set_header X-Original-URI $request_uri; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; - proxy_pass http://auth/_auth; + proxy_pass http://auth/_verify; } location /auth/ { @@ -51,7 +56,7 @@ http { } location = /secret.html { - auth_request /check-auth; + auth_request /verify; auth_request_set $user $upstream_http_x_remote_user; proxy_set_header X-Forwarded-User $user; @@ -60,14 +65,5 @@ http { auth_request_set $expiry $upstream_http_remote_expiry; proxy_set_header Remote-Expiry $expiry; } - - - # Block everything but POST on _auth - location = /_auth { - if ($request_method != POST) { - return 403; - } - proxy_pass http://auth/_auth; - } } } diff --git a/nginx_conf/ssl/server.crt b/nginx_conf/ssl/server.crt new file mode 100644 index 000000000..abe89b561 --- /dev/null +++ b/nginx_conf/ssl/server.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICATCCAWoCCQCvH2RvyOshNzANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMB4XDTE3MDExNzIzMTc0M1oXDTE4MDExNzIzMTc0M1owRTELMAkG +A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 +IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzZaE +4XE1QyFNbrHBHRhSA53anAsJ5mBeG7Om6SdQcZAYahlDWEbtdoY4hy0gPNGcITcW +eE+WA+PvNRr7PczKEhneIyUUgV+nrz010fM5JnECPxLTe1oFzl4U8dyYiBpTziNz +hiUfq733PRYjcd9BQtcKcN4LdmQvjUHnnQ73TysCAwEAATANBgkqhkiG9w0BAQsF +AAOBgQAUFICtbuqXgL4HBRAg7yGbwokoH8Ar1QKZGe+F2WTR8vaDLOYUL7VsltLE +EJIGrcfs31nItHOBcLJuflrS8y0CQqes5puRw33LL2usSvO8z2q7JhCx+DSBi6yN +RbhcrGOllIdjsrbmd/zAMBVTUyxSisq3Nmk1cZayDvKg+GSAEA== +-----END CERTIFICATE----- diff --git a/nginx_conf/ssl/server.csr b/nginx_conf/ssl/server.csr new file mode 100644 index 000000000..80b307a91 --- /dev/null +++ b/nginx_conf/ssl/server.csr @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh +MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDNloThcTVDIU1uscEdGFIDndqcCwnmYF4bs6bpJ1BxkBhq +GUNYRu12hjiHLSA80ZwhNxZ4T5YD4+81Gvs9zMoSGd4jJRSBX6evPTXR8zkmcQI/ +EtN7WgXOXhTx3JiIGlPOI3OGJR+rvfc9FiNx30FC1wpw3gt2ZC+NQeedDvdPKwID +AQABoAAwDQYJKoZIhvcNAQELBQADgYEAmCX60kspIw1Zfb79AQOarFW5Q2K2h5Vx +/cRbDyHlKtbmG77EtICccULyqf76B1gNRw5Zq3lSotSUcLzsWcdesXCFDC7k87Qf +mpQKPj6GdTYJvdWf8aDwt32tAqWuBIRoAbdx5WbFPPWVfDcm7zDJefBrhNUDH0Qd +vcnxjvPMmOM= +-----END CERTIFICATE REQUEST----- diff --git a/nginx_conf/ssl/server.key b/nginx_conf/ssl/server.key new file mode 100644 index 000000000..23a513ce1 --- /dev/null +++ b/nginx_conf/ssl/server.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDNloThcTVDIU1uscEdGFIDndqcCwnmYF4bs6bpJ1BxkBhqGUNY +Ru12hjiHLSA80ZwhNxZ4T5YD4+81Gvs9zMoSGd4jJRSBX6evPTXR8zkmcQI/EtN7 +WgXOXhTx3JiIGlPOI3OGJR+rvfc9FiNx30FC1wpw3gt2ZC+NQeedDvdPKwIDAQAB +AoGBAIwGcfkO30UawJ+daDeF4g5ejI/toM+NYWuiwBNbWJoQl+Bj1o+gt4obvxKq +tKNX7OxelepZ4oZB0CIuf2LHQfU6cVGdu//or7nfS2FLBYStopZyL6KorZbkqsj1 +ikQN4GosJQqaYkexnwjItMFaHaRRX6YnIXp42Jl1glitO3+5AkEA+thn/vwFo24I +fC+7ORpmLi+BVAkTuhMm+C6TIV6s64B+A5oQ82OBCYK9YCOWmS6JHHFDrxJla+3M +2U9KXky63wJBANHQCFCirfuT6esSjbqpCeqtmZG5LWHtL12V9DF7yjHPjmHL9uRu +e9W+Uz33IJbqd82gtZ/ARfpYEjD0JEieQTUCQFo872xzDTQ1qSfDo/5u2MNUo5mv +ikEuEp7FYnhmrp4poyt4iRCFgy4Ask+bfdmtO/XXaRnZ7FJfQYoLVB2ITNECQQCN +gOiauZztl4yj5heAVJFDnWF9To61BOp1C7VtyjdL8NfuTUluNrV+KqapnAp2vhue +q0zTOTH47X0XVxFBiLohAkBuQzPey5I3Ui8inE4sDt/fqX8r/GMhBTxIb9KlV/H6 +jKZNs/83n5/ohaX36er8svW9PB4pcqENZ+kBpvDtKVwS +-----END RSA PRIVATE KEY----- diff --git a/package.json b/package.json index c0d93b5e0..e62f951e2 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "main": "src/index.js", "scripts": { "test": "./node_modules/.bin/mocha --recursive test/unitary", - "integration-test": "./node_modules/.bin/mocha --recursive test/integration", + "unit-test": "./node_modules/.bin/mocha --recursive test/unitary", + "int-test": "./node_modules/.bin/mocha --recursive test/integration", + "all-test": "./node_modules/.bin/mocha --recursive test", "coverage": "./node_modules/.bin/istanbul cover _mocha -- -R spec" }, "repository": { @@ -18,16 +20,16 @@ "url": "https://github.com/clems4ever/two-factor-auth-server/issues" }, "dependencies": { + "authdog": "^0.1.1", "bluebird": "^3.4.7", "body-parser": "^1.15.2", - "cookie-parser": "^1.4.3", "ejs": "^2.5.5", "express": "^4.14.0", - "jsonwebtoken": "^7.2.1", + "express-session": "^1.14.2", "ldapjs": "^1.0.1", "object-path": "^0.11.3", - "q": "^1.4.1", - "speakeasy": "^2.0.0" + "speakeasy": "^2.0.0", + "winston": "^2.3.1" }, "devDependencies": { "mocha": "^3.2.0", diff --git a/src/index.js b/src/index.js index 1616cde7e..6e164ccf3 100644 --- a/src/index.js +++ b/src/index.js @@ -2,14 +2,15 @@ var server = require('./lib/server'); var ldap = require('ldapjs'); +var u2f = require('authdog'); var config = { port: process.env.PORT || 8080, totp_secret: process.env.TOTP_SECRET, ldap_url: process.env.LDAP_URL || 'ldap://127.0.0.1:389', ldap_users_dn: process.env.LDAP_USERS_DN, - jwt_secret: process.env.JWT_SECRET, - jwt_expiration_time: process.env.JWT_EXPIRATION_TIME || '1h' + session_secret: process.env.SESSION_SECRET, + session_max_age: process.env.SESSION_MAX_AGE || 3600000 // in ms } var ldap_client = ldap.createClient({ @@ -17,4 +18,4 @@ var ldap_client = ldap.createClient({ reconnect: true }); -server.run(config, ldap_client); +server.run(config, ldap_client, u2f); diff --git a/src/lib/authentication.js b/src/lib/authentication.js deleted file mode 100644 index 74993038d..000000000 --- a/src/lib/authentication.js +++ /dev/null @@ -1,19 +0,0 @@ - -module.exports = { - verify: verify_authentication -} - -var objectPath = require('object-path'); -var utils = require('./utils'); - -function verify_authentication(req, res) { - console.log('Verify authentication'); - - if(!objectPath.has(req, 'cookies.access_token')) { - return utils.reject('No access token provided'); - } - - var jsonWebToken = req.cookies['access_token']; - return req.app.get('jwt engine').verify(jsonWebToken); -} - diff --git a/src/lib/jwt.js b/src/lib/jwt.js deleted file mode 100644 index 7cc00d001..000000000 --- a/src/lib/jwt.js +++ /dev/null @@ -1,32 +0,0 @@ - -module.exports = Jwt; - -var jwt = require('jsonwebtoken'); -var utils = require('./utils'); -var Promise = require('bluebird'); - -function Jwt(secret) { - this._secret = secret; -} - -Jwt.prototype.sign = function(data, expiration_time) { - var that = this; - return new Promise(function(resolve, reject) { - var token = jwt.sign(data, that._secret, { expiresIn: expiration_time }) - resolve(token); - }); -} - -Jwt.prototype.verify = function(token) { - var that = this; - return new Promise(function(resolve, reject) { - try { - var decoded = jwt.verify(token, that._secret); - resolve(decoded); - } - catch(err) { - reject(err.message); - } - }); -} - diff --git a/src/lib/ldap.js b/src/lib/ldap.js index 77a157872..c2e36156b 100644 --- a/src/lib/ldap.js +++ b/src/lib/ldap.js @@ -7,7 +7,7 @@ var util = require('util'); var Promise = require('bluebird'); function validateCredentials(ldap_client, username, password, users_dn) { - var userDN = util.format("binding entry cn=%s,%s", username, users_dn); - var bind_promised = Promise.promisify(ldap_client.bind, ldap_client); + var userDN = util.format("cn=%s,%s", username, users_dn); + var bind_promised = Promise.promisify(ldap_client.bind, { context: ldap_client }); return bind_promised(userDN, password); } diff --git a/src/lib/replies.js b/src/lib/replies.js deleted file mode 100644 index a68d74faf..000000000 --- a/src/lib/replies.js +++ /dev/null @@ -1,27 +0,0 @@ - -module.exports = { - 'authentication_failed': authentication_failed, - 'authentication_succeeded': authentication_succeeded, - 'already_authenticated': already_authenticated -} - -function authentication_failed(res) { - console.log('Reply: authentication failed'); - res.status(401) - res.send('Authentication failed'); -} - -function authentication_succeeded(res, username, token) { - console.log('Reply: authentication succeeded'); - res.status(200); - res.set({ 'X-Remote-User': username }); - res.send(token); -} - -function already_authenticated(res, username) { - console.log('Reply: already authenticated'); - res.status(204); - res.set({ 'X-Remote-User': username }); - res.send(); -} - diff --git a/src/lib/routes.js b/src/lib/routes.js index 507726e3b..b9e46e202 100644 --- a/src/lib/routes.js +++ b/src/lib/routes.js @@ -1,39 +1,31 @@ var first_factor = require('./routes/first_factor'); +var second_factor = require('./routes/second_factor'); +var verify = require('./routes/verify'); module.exports = { - auth: serveAuth, login: serveLogin, logout: serveLogout, - first_factor: first_factor -} - -var authentication = require('./authentication'); -var replies = require('./replies'); - -function serveAuth(req, res) { - serveAuthGet(req, res); -} - -function serveAuthGet(req, res) { - authentication.verify(req, res) - .then(function(user) { - replies.already_authenticated(res, user); - }) - .catch(function(err) { - replies.authentication_failed(res); - console.error(err); - }); + verify: verify, + first_factor: first_factor, + second_factor: second_factor } function serveLogin(req, res) { + req.session.auth_session = {}; + req.session.auth_session.first_factor = false; + req.session.auth_session.second_factor = false; + res.render('login'); } function serveLogout(req, res) { var redirect_param = req.query.redirect; var redirect_url = redirect_param || '/'; - res.clearCookie('access_token'); + req.session.auth_session = { + first_factor: false, + second_factor: false + } res.redirect(redirect_url); } diff --git a/src/lib/routes/deny_not_logged.js b/src/lib/routes/deny_not_logged.js new file mode 100644 index 000000000..7aaf448bf --- /dev/null +++ b/src/lib/routes/deny_not_logged.js @@ -0,0 +1,24 @@ + +module.exports = denyNotLogged; + +var objectPath = require('object-path'); + +function replyWithUnauthorized(res) { + res.status(401); + res.send('Unauthorized access'); +} + +function denyNotLogged(next) { + return function(req, res) { + var auth_session = req.session.auth_session; + var first_factor = objectPath.has(req, 'session.auth_session.first_factor') + && req.session.auth_session.first_factor; + if(!first_factor) { + replyWithUnauthorized(res); + console.log('Access to this route is denied'); + return; + } + + next(req, res); + } +} diff --git a/src/lib/routes/first_factor.js b/src/lib/routes/first_factor.js index 4c4ca779a..4588a9749 100644 --- a/src/lib/routes/first_factor.js +++ b/src/lib/routes/first_factor.js @@ -2,14 +2,24 @@ module.exports = first_factor; var ldap = require('../ldap'); +var objectPath = require('object-path'); + +function replyWithUnauthorized(res) { + res.status(401); + res.send(); +} function first_factor(req, res) { + if(!objectPath.has(req, 'session.auth_session.second_factor')) { + replyWithUnauthorized(res); + } + var username = req.body.username; var password = req.body.password; console.log('Start authentication of user %s', username); if(!username || !password) { - replies.authentication_failed(res); + replyWithUnauthorized(res); return; } @@ -18,13 +28,14 @@ function first_factor(req, res) { ldap.validate(ldap_client, username, password, config.ldap_users_dn) .then(function() { + req.session.auth_session.userid = username; + req.session.auth_session.first_factor = true; res.status(204); res.send(); console.log('LDAP binding successful'); }) - .error(function(err) { - res.status(401); - res.send(); + .catch(function(err) { + replyWithUnauthorized(res); console.log('LDAP binding failed:', err); }); } diff --git a/src/lib/routes/second_factor.js b/src/lib/routes/second_factor.js new file mode 100644 index 000000000..7b0933c67 --- /dev/null +++ b/src/lib/routes/second_factor.js @@ -0,0 +1,16 @@ + +var user_key_container = {}; +var denyNotLogged = require('./deny_not_logged'); +var u2f = require('./u2f')(user_key_container); // create a u2f handler bound to +// user key container + +module.exports = { + totp: denyNotLogged(require('./totp')), + u2f: { + register_request: denyNotLogged(u2f.register_request), + register: denyNotLogged(u2f.register), + sign_request: denyNotLogged(u2f.sign_request), + sign: denyNotLogged(u2f.sign), + } +} + diff --git a/src/lib/routes/totp.js b/src/lib/routes/totp.js new file mode 100644 index 000000000..2e8c8820f --- /dev/null +++ b/src/lib/routes/totp.js @@ -0,0 +1,33 @@ + +module.exports = totp; + +var totp = require('../totp'); +var objectPath = require('object-path'); + +var UNAUTHORIZED_MESSAGE = 'Unauthorized access'; + +function replyWithUnauthorized(res) { + res.status(401); + res.send(); +} + +function totp(req, res) { + if(!objectPath.has(req, 'session.auth_session.second_factor')) { + replyWithUnauthorized(res); + } + var token = req.body.token; + + var totp_engine = req.app.get('totp engine'); + var config = req.app.get('config'); + + totp.validate(totp_engine, token, config.totp_secret) + .then(function() { + req.session.auth_session.second_factor = true; + res.status(204); + res.send(); + }) + .catch(function(err) { + console.error(err); + replyWithUnauthorized(res); + }); +} diff --git a/src/lib/routes/u2f.js b/src/lib/routes/u2f.js new file mode 100644 index 000000000..941f73065 --- /dev/null +++ b/src/lib/routes/u2f.js @@ -0,0 +1,144 @@ + +module.exports = function(user_key_container) { + return { + register_request: register_request, + register: register(user_key_container), + sign_request: sign_request(user_key_container), + sign: sign(user_key_container), + } +} + +var objectPath = require('object-path'); +var util = require('util'); + +function replyWithInternalError(res, msg) { + res.status(500); + res.send(msg) +} + +function replyWithMissingRegistration(res) { + res.status(401); + res.send('Please register before authenticate'); +} + +function replyWithUnauthorized(res) { + res.status(401); + res.send(); +} + + +function register_request(req, res) { + var u2f = req.app.get('u2f'); + var logger = req.app.get('logger'); + var app_id = util.format('https://%s', req.headers.host); + + logger.debug('U2F register_request: headers=%s', JSON.stringify(req.headers)); + logger.info('U2F register_request: Starting registration'); + u2f.startRegistration(app_id, []) + .then(function(registrationRequest) { + logger.info('U2F register_request: Sending back registration request'); + req.session.auth_session.register_request = registrationRequest; + res.status(200); + res.json(registrationRequest); + }, function(err) { + logger.error('U2F register_request: %s', err); + replyWithInternalError(res, 'Unable to complete the registration'); + }); +} + +function register(user_key_container) { + return function(req, res) { + if(!objectPath.has(req, 'session.auth_session.register_request')) { + replyWithUnauthorized(res); + return; + } + + var u2f = req.app.get('u2f'); + var registrationRequest = req.session.auth_session.register_request; + var logger = req.app.get('logger'); + + logger.info('U2F register: Finishing registration'); + logger.debug('U2F register: register_request=%s', JSON.stringify(registrationRequest)); + logger.debug('U2F register: body=%s', JSON.stringify(req.body)); + + u2f.finishRegistration(registrationRequest, req.body) + .then(function(registrationStatus) { + logger.info('U2F register: Store registration and reply'); + var meta = { + keyHandle: registrationStatus.keyHandle, + publicKey: registrationStatus.publicKey, + certificate: registrationStatus.certificate + } + user_key_container[req.session.auth_session.userid] = meta; + res.status(204); + res.send(); + }, function(err) { + logger.error('U2F register: %s', err); + replyWithInternalError(res, 'Unable to complete the registration'); + }); + } +} + +function userKeyExists(req, user_key_container) { + return req.session.auth_session.userid in user_key_container; +} + + + +function sign_request(user_key_container) { + return function(req, res) { + if(!userKeyExists(req, user_key_container)) { + replyWithMissingRegistration(res); + return; + } + + var logger = req.app.get('logger'); + var u2f = req.app.get('u2f'); + var key = user_key_container[req.session.auth_session.userid]; + var app_id = util.format('https://%s', req.headers.host); + + logger.info('U2F sign_request: Start authentication'); + u2f.startAuthentication(app_id, [key]) + .then(function(authRequest) { + logger.info('U2F sign_request: Store authentication request and reply'); + req.session.auth_session.sign_request = authRequest; + res.status(200); + res.json(authRequest); + }, function(err) { + logger.info('U2F sign_request: %s', err); + replyWithUnauthorized(res); + }); + } +} + +function sign(user_key_container) { + return function(req, res) { + if(!userKeyExists(req, user_key_container)) { + replyWithMissingRegistration(res); + return; + } + + if(!objectPath.has(req, 'session.auth_session.sign_request')) { + replyWithUnauthorized(res); + return; + } + + var logger = req.app.get('logger'); + var u2f = req.app.get('u2f'); + var authRequest = req.session.auth_session.sign_request; + var key = user_key_container[req.session.auth_session.userid]; + + logger.info('U2F sign: Finish authentication'); + u2f.finishAuthentication(authRequest, req.body, [key]) + .then(function(authenticationStatus) { + logger.info('U2F sign: Authentication successful'); + req.session.auth_session.second_factor = true; + res.status(204); + res.send(); + }, function(err) { + logger.error('U2F sign: %s', err); + res.status(401); + res.send(); + }); + } +} diff --git a/src/lib/routes/verify.js b/src/lib/routes/verify.js new file mode 100644 index 000000000..68b8e5aab --- /dev/null +++ b/src/lib/routes/verify.js @@ -0,0 +1,37 @@ + +module.exports = verify; + +var objectPath = require('object-path'); +var Promise = require('bluebird'); + +function verify_filter(req, res) { + if(!objectPath.has(req, 'session.auth_session')) + return Promise.reject('No auth_session variable'); + + if(!objectPath.has(req, 'session.auth_session.first_factor')) + return Promise.reject('No first factor variable'); + + if(!objectPath.has(req, 'session.auth_session.second_factor')) + return Promise.reject('No second factor variable'); + + if(!req.session.auth_session.first_factor || + !req.session.auth_session.second_factor) + return Promise.reject('First or second factor not validated'); + + return Promise.resolve(); +} + +function verify(req, res) { + console.log('Verify authentication'); + + verify_filter(req, res) + .then(function() { + res.status(204); + res.send(); + }) + .catch(function(err) { + res.status(401); + res.send(); + }); +} + diff --git a/src/lib/server.js b/src/lib/server.js index 526297367..750c625fa 100644 --- a/src/lib/server.js +++ b/src/lib/server.js @@ -4,39 +4,60 @@ module.exports = { } var routes = require('./routes'); -var Jwt = require('./jwt'); var express = require('express'); var bodyParser = require('body-parser'); -var cookieParser = require('cookie-parser'); var speakeasy = require('speakeasy'); var path = require('path'); +var session = require('express-session'); +var winston = require('winston'); -function run(config, ldap_client) { +function run(config, ldap_client, u2f, fn) { var view_directory = path.resolve(__dirname, '../views'); var public_html_directory = path.resolve(__dirname, '../public_html'); var app = express(); - app.use(cookieParser()); app.use(express.static(public_html_directory)); app.use(bodyParser.urlencoded({ extended: false })); + app.use(bodyParser.json()); + app.set('trust proxy', 1); // trust first proxy + + app.use(session({ + secret: config.session_secret, + resave: false, + saveUninitialized: true, + cookie: { + secure: false, + maxAge: config.session_max_age + }, + })); app.set('views', view_directory); app.set('view engine', 'ejs'); - app.set('jwt engine', new Jwt(config.jwt_secret)); + winston.level = 'debug'; + + app.set('logger', winston); app.set('ldap client', ldap_client); app.set('totp engine', speakeasy); + app.set('u2f', u2f); app.set('config', config); app.get ('/login', routes.login); app.get ('/logout', routes.logout); - app.get ('/_auth', routes.auth); + app.get ('/_verify', routes.verify); - app.post ('/_auth/1stfactor', routes.first_factor); + app.post ('/_auth/1stfactor', routes.first_factor); + app.post ('/_auth/2ndfactor/totp', routes.second_factor.totp); + + app.get ('/_auth/2ndfactor/u2f/register_request', routes.second_factor.u2f.register_request); + app.post ('/_auth/2ndfactor/u2f/register', routes.second_factor.u2f.register); + app.get ('/_auth/2ndfactor/u2f/sign_request', routes.second_factor.u2f.sign_request); + app.post ('/_auth/2ndfactor/u2f/sign', routes.second_factor.u2f.sign); - app.listen(config.port, function(err) { + return app.listen(config.port, function(err) { console.log('Listening on %d...', config.port); + if(fn) fn(); }); } diff --git a/src/lib/totp.js b/src/lib/totp.js index a68c12350..7d3c194e0 100644 --- a/src/lib/totp.js +++ b/src/lib/totp.js @@ -3,20 +3,20 @@ module.exports = { 'validate': validate } -var Q = require('q'); +var Promise = require('bluebird'); function validate(totp_engine, token, totp_secret) { - var defer = Q.defer(); - var real_token = totp_engine.totp({ - secret: totp_secret, - encoding: 'base32' - }); + return new Promise(function(resolve, reject) { + var real_token = totp_engine.totp({ + secret: totp_secret, + encoding: 'base32' + }); - if(token == real_token) { - defer.resolve(); - } - else { - defer.reject('Wrong challenge'); - } - return defer.promise; + if(token == real_token) { + resolve(); + } + else { + reject('Wrong challenge'); + } + }); } diff --git a/src/public_html/js.cookie.min.js b/src/public_html/js.cookie.min.js deleted file mode 100644 index ff0f37b1a..000000000 --- a/src/public_html/js.cookie.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! js-cookie v2.1.3 | MIT */ -!function(a){var b=!1;if("function"==typeof define&&define.amd&&(define(a),b=!0),"object"==typeof exports&&(module.exports=a(),b=!0),!b){var c=window.Cookies,d=window.Cookies=a();d.noConflict=function(){return window.Cookies=c,d}}}(function(){function a(){for(var a=0,b={};a1){if(f=a({path:"/"},d.defaults,f),"number"==typeof f.expires){var h=new Date;h.setMilliseconds(h.getMilliseconds()+864e5*f.expires),f.expires=h}try{g=JSON.stringify(e),/^[\{\[]/.test(g)&&(e=g)}catch(i){}return e=c.write?c.write(e,b):encodeURIComponent(e+"").replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),b=encodeURIComponent(b+""),b=b.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent),b=b.replace(/[\(\)]/g,escape),document.cookie=b+"="+e+(f.expires?"; expires="+f.expires.toUTCString():"")+(f.path?"; path="+f.path:"")+(f.domain?"; domain="+f.domain:"")+(f.secure?"; secure":"")}b||(g={});for(var j=document.cookie?document.cookie.split("; "):[],k=/(%[0-9A-Z]{2})+/g,l=0;l\n
\n
\n',css:"."+r+"-corner {\n position: fixed;\n margin: 5px;\n z-index: 1050;\n}\n\n."+r+"-corner ."+r+"-wrapper,\n."+r+"-corner ."+r+"-container {\n position: relative;\n display: block;\n height: inherit;\n width: inherit;\n margin: 3px;\n}\n\n."+r+"-wrapper {\n z-index: 1;\n position: absolute;\n display: inline-block;\n height: 0;\n width: 0;\n}\n\n."+r+"-container {\n display: none;\n z-index: 1;\n position: absolute;\n}\n\n."+r+"-hidable {\n cursor: pointer;\n}\n\n[data-notify-text],[data-notify-html] {\n position: relative;\n}\n\n."+r+"-arrow {\n position: absolute;\n z-index: 2;\n width: 0;\n height: 0;\n}"},p={"border-radius":["-webkit-","-moz-"]},d=function(e){return c[e]},v=function(e){if(!e)throw"Missing Style name";c[e]&&delete c[e]},m=function(t,i){if(!t)throw"Missing Style name";if(!i)throw"Missing Style definition";if(!i.html)throw"Missing Style HTML";var s=c[t];s&&s.cssElem&&(window.console&&console.warn(n+": overwriting style '"+t+"'"),c[t].cssElem.remove()),i.name=t,c[t]=i;var o="";i.classes&&e.each(i.classes,function(t,n){return o+="."+r+"-"+i.name+"-"+t+" {\n",e.each(n,function(t,n){return p[t]&&e.each(p[t],function(e,r){return o+=" "+r+t+": "+n+";\n"}),o+=" "+t+": "+n+";\n"}),o+="}\n"}),i.css&&(o+="/* styles for "+i.name+" */\n"+i.css),o&&(i.cssElem=g(o),i.cssElem.attr("id","notify-"+i.name));var u={},a=e(i.html);y("html",a,u),y("text",a,u),i.fields=u},g=function(t){var n,r,i;r=x("style"),r.attr("type","text/css"),e("head").append(r);try{r.html(t)}catch(s){r[0].styleSheet.cssText=t}return r},y=function(t,n,r){var s;return t!=="html"&&(t="text"),s="data-notify-"+t,b(n,"["+s+"]").each(function(){var n;n=e(this).attr(s),n||(n=i),r[n]=t})},b=function(e,t){return e.is(t)?e:e.find(t)},w={clickToHide:!0,autoHide:!0,autoHideDelay:5e3,arrowShow:!0,arrowSize:5,breakNewLines:!0,elementPosition:"bottom",globalPosition:"top right",style:"bootstrap",className:"error",showAnimation:"slideDown",showDuration:400,hideAnimation:"slideUp",hideDuration:200,gap:5},E=function(t,n){var r;return r=function(){},r.prototype=t,e.extend(!0,new r,n)},S=function(t){return e.extend(w,t)},x=function(t){return e("<"+t+">")},T={},N=function(t){var n;return t.is("[type=radio]")&&(n=t.parents("form:first").find("[type=radio]").filter(function(n,r){return e(r).attr("name")===t.attr("name")}),t=n.first()),t},C=function(e,t,n){var r,i;if(typeof n=="string")n=parseInt(n,10);else if(typeof n!="number")return;if(isNaN(n))return;return r=s[f[t.charAt(0)]],i=t,e[r]!==undefined&&(t=s[r.charAt(0)],n=-n),e[t]===undefined?e[t]=n:e[t]+=n,null},k=function(e,t,n){if(e==="l"||e==="t")return 0;if(e==="c"||e==="m")return n/2-t/2;if(e==="r"||e==="b")return n-t;throw"Invalid alignment"},L=function(e){return L.e=L.e||x("div"),L.e.text(e).html()};A.prototype.loadHTML=function(){var t;t=this.getStyle(),this.userContainer=e(t.html),this.userFields=t.fields},A.prototype.show=function(e,t){var n,r,i,s,o;r=function(n){return function(){!e&&!n.elem&&n.destroy();if(t)return t()}}(this),o=this.container.parent().parents(":hidden").length>0,i=this.container.add(this.arrow),n=[];if(o&&e)s="show";else if(o&&!e)s="hide";else if(!o&&e)s=this.options.showAnimation,n.push(this.options.showDuration);else{if(!!o||!!e)return r();s=this.options.hideAnimation,n.push(this.options.hideDuration)}return n.push(r),i[s].apply(i,n)},A.prototype.setGlobalPosition=function(){var t=this.getPosition(),n=t[0],i=t[1],o=s[n],u=s[i],a=n+"|"+i,f=T[a];if(!f||!document.body.contains(f[0])){f=T[a]=x("div");var l={};l[o]=0,u==="middle"?l.top="45%":u==="center"?l.left="45%":l[u]=0,f.css(l).addClass(r+"-corner"),e("body").append(f)}return f.prepend(this.wrapper)},A.prototype.setElementPosition=function(){var n,r,i,l,c,h,p,d,v,m,g,y,b,w,E,S,x,T,N,L,A,O,M,_,D,P,H,B,j;H=this.getPosition(),_=H[0],O=H[1],M=H[2],g=this.elem.position(),d=this.elem.outerHeight(),y=this.elem.outerWidth(),v=this.elem.innerHeight(),m=this.elem.innerWidth(),j=this.wrapper.position(),c=this.container.height(),h=this.container.width(),T=s[_],L=f[_],A=s[L],p={},p[A]=_==="b"?d:_==="r"?y:0,C(p,"top",g.top-j.top),C(p,"left",g.left-j.left),B=["top","left"];for(w=0,S=B.length;w=0&&C(r,s[O],i*2)}t.call(u,_)>=0?(C(p,"left",k(O,h,y)),r&&C(r,"left",k(O,i,m))):t.call(o,_)>=0&&(C(p,"top",k(O,c,d)),r&&C(r,"top",k(O,i,v))),this.container.is(":visible")&&(p.display="block"),this.container.removeAttr("style").css(p);if(r)return this.arrow.removeAttr("style").css(r)},A.prototype.getPosition=function(){var e,n,r,i,s,f,c,h;h=this.options.position||(this.elem?this.options.elementPosition:this.options.globalPosition),e=l(h),e.length===0&&(e[0]="b");if(n=e[0],t.call(a,n)<0)throw"Must be one of ["+a+"]";if(e.length===1||(r=e[0],t.call(u,r)>=0)&&(i=e[1],t.call(o,i)<0)||(s=e[0],t.call(o,s)>=0)&&(f=e[1],t.call(u,f)<0))e[1]=(c=e[0],t.call(o,c)>=0)?"m":"l";return e.length===2&&(e[2]=e[1]),e},A.prototype.getStyle=function(e){var t;e||(e=this.options.style),e||(e="default"),t=c[e];if(!t)throw"Missing style: "+e;return t},A.prototype.updateClasses=function(){var t,n;return t=["base"],e.isArray(this.options.className)?t=t.concat(this.options.className):this.options.className&&t.push(this.options.className),n=this.getStyle(),t=e.map(t,function(e){return r+"-"+n.name+"-"+e}).join(" "),this.userContainer.attr("class",t)},A.prototype.run=function(t,n){var r,s,o,u,a;e.isPlainObject(n)?e.extend(this.options,n):e.type(n)==="string"&&(this.options.className=n);if(this.container&&!t){this.show(!1);return}if(!this.container&&!t)return;s={},e.isPlainObject(t)?s=t:s[i]=t;for(o in s){r=s[o],u=this.userFields[o];if(!u)continue;u==="text"&&(r=L(r),this.options.breakNewLines&&(r=r.replace(/\n/g,"
"))),a=o===i?"":"="+o,b(this.userContainer,"[data-notify-"+u+a+"]").html(r)}this.updateClasses(),this.elem?this.setElementPosition():this.setGlobalPosition(),this.show(!0),this.options.autoHide&&(clearTimeout(this.autohideTimer),this.autohideTimer=setTimeout(this.show.bind(this,!1),this.options.autoHideDelay))},A.prototype.destroy=function(){this.wrapper.data(r,null),this.wrapper.remove()},e[n]=function(t,r,i){return t&&t.nodeName||t.jquery?e(t)[n](r,i):(i=r,r=t,new A(null,r,i)),t},e.fn[n]=function(t,n){return e(this).each(function(){var i=N(e(this)).data(r);i&&i.destroy();var s=new A(e(this),t,n)}),this},e.extend(e[n],{defaults:S,addStyle:m,removeStyle:v,pluginOptions:w,getStyle:d,insertCSS:g}),m("bootstrap",{html:"
\n\n
",classes:{base:{"font-weight":"bold",padding:"8px 15px 8px 14px","text-shadow":"0 1px 0 rgba(255, 255, 255, 0.5)","background-color":"#fcf8e3",border:"1px solid #fbeed5","border-radius":"4px","white-space":"nowrap","padding-left":"25px","background-repeat":"no-repeat","background-position":"3px 7px"},error:{color:"#B94A48","background-color":"#F2DEDE","border-color":"#EED3D7","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAtRJREFUeNqkVc1u00AQHq+dOD+0poIQfkIjalW0SEGqRMuRnHos3DjwAH0ArlyQeANOOSMeAA5VjyBxKBQhgSpVUKKQNGloFdw4cWw2jtfMOna6JOUArDTazXi/b3dm55socPqQhFka++aHBsI8GsopRJERNFlY88FCEk9Yiwf8RhgRyaHFQpPHCDmZG5oX2ui2yilkcTT1AcDsbYC1NMAyOi7zTX2Agx7A9luAl88BauiiQ/cJaZQfIpAlngDcvZZMrl8vFPK5+XktrWlx3/ehZ5r9+t6e+WVnp1pxnNIjgBe4/6dAysQc8dsmHwPcW9C0h3fW1hans1ltwJhy0GxK7XZbUlMp5Ww2eyan6+ft/f2FAqXGK4CvQk5HueFz7D6GOZtIrK+srupdx1GRBBqNBtzc2AiMr7nPplRdKhb1q6q6zjFhrklEFOUutoQ50xcX86ZlqaZpQrfbBdu2R6/G19zX6XSgh6RX5ubyHCM8nqSID6ICrGiZjGYYxojEsiw4PDwMSL5VKsC8Yf4VRYFzMzMaxwjlJSlCyAQ9l0CW44PBADzXhe7xMdi9HtTrdYjFYkDQL0cn4Xdq2/EAE+InCnvADTf2eah4Sx9vExQjkqXT6aAERICMewd/UAp/IeYANM2joxt+q5VI+ieq2i0Wg3l6DNzHwTERPgo1ko7XBXj3vdlsT2F+UuhIhYkp7u7CarkcrFOCtR3H5JiwbAIeImjT/YQKKBtGjRFCU5IUgFRe7fF4cCNVIPMYo3VKqxwjyNAXNepuopyqnld602qVsfRpEkkz+GFL1wPj6ySXBpJtWVa5xlhpcyhBNwpZHmtX8AGgfIExo0ZpzkWVTBGiXCSEaHh62/PoR0p/vHaczxXGnj4bSo+G78lELU80h1uogBwWLf5YlsPmgDEd4M236xjm+8nm4IuE/9u+/PH2JXZfbwz4zw1WbO+SQPpXfwG/BBgAhCNZiSb/pOQAAAAASUVORK5CYII=)"},success:{color:"#468847","background-color":"#DFF0D8","border-color":"#D6E9C6","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAutJREFUeNq0lctPE0Ecx38zu/RFS1EryqtgJFA08YCiMZIAQQ4eRG8eDGdPJiYeTIwHTfwPiAcvXIwXLwoXPaDxkWgQ6islKlJLSQWLUraPLTv7Gme32zoF9KSTfLO7v53vZ3d/M7/fIth+IO6INt2jjoA7bjHCJoAlzCRw59YwHYjBnfMPqAKWQYKjGkfCJqAF0xwZjipQtA3MxeSG87VhOOYegVrUCy7UZM9S6TLIdAamySTclZdYhFhRHloGYg7mgZv1Zzztvgud7V1tbQ2twYA34LJmF4p5dXF1KTufnE+SxeJtuCZNsLDCQU0+RyKTF27Unw101l8e6hns3u0PBalORVVVkcaEKBJDgV3+cGM4tKKmI+ohlIGnygKX00rSBfszz/n2uXv81wd6+rt1orsZCHRdr1Imk2F2Kob3hutSxW8thsd8AXNaln9D7CTfA6O+0UgkMuwVvEFFUbbAcrkcTA8+AtOk8E6KiQiDmMFSDqZItAzEVQviRkdDdaFgPp8HSZKAEAL5Qh7Sq2lIJBJwv2scUqkUnKoZgNhcDKhKg5aH+1IkcouCAdFGAQsuWZYhOjwFHQ96oagWgRoUov1T9kRBEODAwxM2QtEUl+Wp+Ln9VRo6BcMw4ErHRYjH4/B26AlQoQQTRdHWwcd9AH57+UAXddvDD37DmrBBV34WfqiXPl61g+vr6xA9zsGeM9gOdsNXkgpEtTwVvwOklXLKm6+/p5ezwk4B+j6droBs2CsGa/gNs6RIxazl4Tc25mpTgw/apPR1LYlNRFAzgsOxkyXYLIM1V8NMwyAkJSctD1eGVKiq5wWjSPdjmeTkiKvVW4f2YPHWl3GAVq6ymcyCTgovM3FzyRiDe2TaKcEKsLpJvNHjZgPNqEtyi6mZIm4SRFyLMUsONSSdkPeFtY1n0mczoY3BHTLhwPRy9/lzcziCw9ACI+yql0VLzcGAZbYSM5CCSZg1/9oc/nn7+i8N9p/8An4JMADxhH+xHfuiKwAAAABJRU5ErkJggg==)"},info:{color:"#3A87AD","background-color":"#D9EDF7","border-color":"#BCE8F1","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYFAhkSsdes/QAAA8dJREFUOMvVlGtMW2UYx//POaWHXg6lLaW0ypAtw1UCgbniNOLcVOLmAjHZolOYlxmTGXVZdAnRfXQm+7SoU4mXaOaiZsEpC9FkiQs6Z6bdCnNYruM6KNBw6YWewzl9z+sHImEWv+vz7XmT95f/+3/+7wP814v+efDOV3/SoX3lHAA+6ODeUFfMfjOWMADgdk+eEKz0pF7aQdMAcOKLLjrcVMVX3xdWN29/GhYP7SvnP0cWfS8caSkfHZsPE9Fgnt02JNutQ0QYHB2dDz9/pKX8QjjuO9xUxd/66HdxTeCHZ3rojQObGQBcuNjfplkD3b19Y/6MrimSaKgSMmpGU5WevmE/swa6Oy73tQHA0Rdr2Mmv/6A1n9w9suQ7097Z9lM4FlTgTDrzZTu4StXVfpiI48rVcUDM5cmEksrFnHxfpTtU/3BFQzCQF/2bYVoNbH7zmItbSoMj40JSzmMyX5qDvriA7QdrIIpA+3cdsMpu0nXI8cV0MtKXCPZev+gCEM1S2NHPvWfP/hL+7FSr3+0p5RBEyhEN5JCKYr8XnASMT0xBNyzQGQeI8fjsGD39RMPk7se2bd5ZtTyoFYXftF6y37gx7NeUtJJOTFlAHDZLDuILU3j3+H5oOrD3yWbIztugaAzgnBKJuBLpGfQrS8wO4FZgV+c1IxaLgWVU0tMLEETCos4xMzEIv9cJXQcyagIwigDGwJgOAtHAwAhisQUjy0ORGERiELgG4iakkzo4MYAxcM5hAMi1WWG1yYCJIcMUaBkVRLdGeSU2995TLWzcUAzONJ7J6FBVBYIggMzmFbvdBV44Corg8vjhzC+EJEl8U1kJtgYrhCzgc/vvTwXKSib1paRFVRVORDAJAsw5FuTaJEhWM2SHB3mOAlhkNxwuLzeJsGwqWzf5TFNdKgtY5qHp6ZFf67Y/sAVadCaVY5YACDDb3Oi4NIjLnWMw2QthCBIsVhsUTU9tvXsjeq9+X1d75/KEs4LNOfcdf/+HthMnvwxOD0wmHaXr7ZItn2wuH2SnBzbZAbPJwpPx+VQuzcm7dgRCB57a1uBzUDRL4bfnI0RE0eaXd9W89mpjqHZnUI5Hh2l2dkZZUhOqpi2qSmpOmZ64Tuu9qlz/SEXo6MEHa3wOip46F1n7633eekV8ds8Wxjn37Wl63VVa+ej5oeEZ/82ZBETJjpJ1Rbij2D3Z/1trXUvLsblCK0XfOx0SX2kMsn9dX+d+7Kf6h8o4AIykuffjT8L20LU+w4AZd5VvEPY+XpWqLV327HR7DzXuDnD8r+ovkBehJ8i+y8YAAAAASUVORK5CYII=)"},warn:{color:"#C09853","background-color":"#FCF8E3","border-color":"#FBEED5","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABJlBMVEXr6eb/2oD/wi7/xjr/0mP/ykf/tQD/vBj/3o7/uQ//vyL/twebhgD/4pzX1K3z8e349vK6tHCilCWbiQymn0jGworr6dXQza3HxcKkn1vWvV/5uRfk4dXZ1bD18+/52YebiAmyr5S9mhCzrWq5t6ufjRH54aLs0oS+qD751XqPhAybhwXsujG3sm+Zk0PTwG6Shg+PhhObhwOPgQL4zV2nlyrf27uLfgCPhRHu7OmLgAafkyiWkD3l49ibiAfTs0C+lgCniwD4sgDJxqOilzDWowWFfAH08uebig6qpFHBvH/aw26FfQTQzsvy8OyEfz20r3jAvaKbhgG9q0nc2LbZxXanoUu/u5WSggCtp1anpJKdmFz/zlX/1nGJiYmuq5Dx7+sAAADoPUZSAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfdBgUBGhh4aah5AAAAlklEQVQY02NgoBIIE8EUcwn1FkIXM1Tj5dDUQhPU502Mi7XXQxGz5uVIjGOJUUUW81HnYEyMi2HVcUOICQZzMMYmxrEyMylJwgUt5BljWRLjmJm4pI1hYp5SQLGYxDgmLnZOVxuooClIDKgXKMbN5ggV1ACLJcaBxNgcoiGCBiZwdWxOETBDrTyEFey0jYJ4eHjMGWgEAIpRFRCUt08qAAAAAElFTkSuQmCC)"}}}),e(function(){g(h.css).attr("id","core-notify"),e(document).on("click","."+r+"-hidable",function(t){e(this).trigger("notify-hide")}),e(document).on("notify-hide","."+r+"-wrapper",function(t){var n=e(this).data(r);n&&n.show(!1)})})}) \ No newline at end of file diff --git a/src/public_html/u2f-api.js b/src/public_html/u2f-api.js new file mode 100644 index 000000000..1d229dbc0 --- /dev/null +++ b/src/public_html/u2f-api.js @@ -0,0 +1,748 @@ +//Copyright 2014-2015 Google Inc. All rights reserved. + +//Use of this source code is governed by a BSD-style +//license that can be found in the LICENSE file or at +//https://developers.google.com/open-source/licenses/bsd + +/** + * @fileoverview The U2F api. + */ +'use strict'; + + +/** + * Namespace for the U2F api. + * @type {Object} + */ +var u2f = u2f || {}; + +/** + * FIDO U2F Javascript API Version + * @number + */ +var js_api_version; + +/** + * The U2F extension id + * @const {string} + */ +// The Chrome packaged app extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the package Chrome app and does not require installing the U2F Chrome extension. + u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; +// The U2F Chrome extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the U2F Chrome extension to authenticate. +// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; + + +/** + * Message types for messsages to/from the extension + * @const + * @enum {string} + */ +u2f.MessageTypes = { + 'U2F_REGISTER_REQUEST': 'u2f_register_request', + 'U2F_REGISTER_RESPONSE': 'u2f_register_response', + 'U2F_SIGN_REQUEST': 'u2f_sign_request', + 'U2F_SIGN_RESPONSE': 'u2f_sign_response', + 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', + 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' +}; + + +/** + * Response status codes + * @const + * @enum {number} + */ +u2f.ErrorCodes = { + 'OK': 0, + 'OTHER_ERROR': 1, + 'BAD_REQUEST': 2, + 'CONFIGURATION_UNSUPPORTED': 3, + 'DEVICE_INELIGIBLE': 4, + 'TIMEOUT': 5 +}; + + +/** + * A message for registration requests + * @typedef {{ + * type: u2f.MessageTypes, + * appId: ?string, + * timeoutSeconds: ?number, + * requestId: ?number + * }} + */ +u2f.U2fRequest; + + +/** + * A message for registration responses + * @typedef {{ + * type: u2f.MessageTypes, + * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), + * requestId: ?number + * }} + */ +u2f.U2fResponse; + + +/** + * An error object for responses + * @typedef {{ + * errorCode: u2f.ErrorCodes, + * errorMessage: ?string + * }} + */ +u2f.Error; + +/** + * Data object for a single sign request. + * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}} + */ +u2f.Transport; + + +/** + * Data object for a single sign request. + * @typedef {Array} + */ +u2f.Transports; + +/** + * Data object for a single sign request. + * @typedef {{ + * version: string, + * challenge: string, + * keyHandle: string, + * appId: string + * }} + */ +u2f.SignRequest; + + +/** + * Data object for a sign response. + * @typedef {{ + * keyHandle: string, + * signatureData: string, + * clientData: string + * }} + */ +u2f.SignResponse; + + +/** + * Data object for a registration request. + * @typedef {{ + * version: string, + * challenge: string + * }} + */ +u2f.RegisterRequest; + + +/** + * Data object for a registration response. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: Transports, + * appId: string + * }} + */ +u2f.RegisterResponse; + + +/** + * Data object for a registered key. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: ?Transports, + * appId: ?string + * }} + */ +u2f.RegisteredKey; + + +/** + * Data object for a get API register response. + * @typedef {{ + * js_api_version: number + * }} + */ +u2f.GetJsApiVersionResponse; + + +//Low level MessagePort API support + +/** + * Sets up a MessagePort to the U2F extension using the + * available mechanisms. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + */ +u2f.getMessagePort = function(callback) { + if (typeof chrome != 'undefined' && chrome.runtime) { + // The actual message here does not matter, but we need to get a reply + // for the callback to run. Thus, send an empty signature request + // in order to get a failure response. + var msg = { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: [] + }; + chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { + if (!chrome.runtime.lastError) { + // We are on a whitelisted origin and can talk directly + // with the extension. + u2f.getChromeRuntimePort_(callback); + } else { + // chrome.runtime was available, but we couldn't message + // the extension directly, use iframe + u2f.getIframePort_(callback); + } + }); + } else if (u2f.isAndroidChrome_()) { + u2f.getAuthenticatorPort_(callback); + } else if (u2f.isIosChrome_()) { + u2f.getIosPort_(callback); + } else { + // chrome.runtime was not available at all, which is normal + // when this origin doesn't have access to any extensions. + u2f.getIframePort_(callback); + } +}; + +/** + * Detect chrome running on android based on the browser's useragent. + * @private + */ +u2f.isAndroidChrome_ = function() { + var userAgent = navigator.userAgent; + return userAgent.indexOf('Chrome') != -1 && + userAgent.indexOf('Android') != -1; +}; + +/** + * Detect chrome running on iOS based on the browser's platform. + * @private + */ +u2f.isIosChrome_ = function() { + return $.inArray(navigator.platform, ["iPhone", "iPad", "iPod"]) > -1; +}; + +/** + * Connects directly to the extension via chrome.runtime.connect. + * @param {function(u2f.WrappedChromeRuntimePort_)} callback + * @private + */ +u2f.getChromeRuntimePort_ = function(callback) { + var port = chrome.runtime.connect(u2f.EXTENSION_ID, + {'includeTlsChannelId': true}); + setTimeout(function() { + callback(new u2f.WrappedChromeRuntimePort_(port)); + }, 0); +}; + +/** + * Return a 'port' abstraction to the Authenticator app. + * @param {function(u2f.WrappedAuthenticatorPort_)} callback + * @private + */ +u2f.getAuthenticatorPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedAuthenticatorPort_()); + }, 0); +}; + +/** + * Return a 'port' abstraction to the iOS client app. + * @param {function(u2f.WrappedIosPort_)} callback + * @private + */ +u2f.getIosPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedIosPort_()); + }, 0); +}; + +/** + * A wrapper for chrome.runtime.Port that is compatible with MessagePort. + * @param {Port} port + * @constructor + * @private + */ +u2f.WrappedChromeRuntimePort_ = function(port) { + this.port_ = port; +}; + +/** + * Format and return a sign request compliant with the JS API version supported by the extension. + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatSignRequest_ = + function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: challenge, + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: signRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + appId: appId, + challenge: challenge, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + +/** + * Format and return a register request compliant with the JS API version supported by the extension.. + * @param {Array} signRequests + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatRegisterRequest_ = + function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + for (var i = 0; i < registerRequests.length; i++) { + registerRequests[i].appId = appId; + } + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: registerRequests[0], + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + signRequests: signRequests, + registerRequests: registerRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + appId: appId, + registerRequests: registerRequests, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + + +/** + * Posts a message on the underlying channel. + * @param {Object} message + */ +u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { + this.port_.postMessage(message); +}; + + +/** + * Emulates the HTML 5 addEventListener interface. Works only for the + * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedChromeRuntimePort_.prototype.addEventListener = + function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message' || name == 'onmessage') { + this.port_.onMessage.addListener(function(message) { + // Emulate a minimal MessageEvent object + handler({'data': message}); + }); + } else { + console.error('WrappedChromeRuntimePort only supports onMessage'); + } +}; + +/** + * Wrap the Authenticator app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedAuthenticatorPort_ = function() { + this.requestId_ = -1; + this.requestObject_ = null; +} + +/** + * Launch the Authenticator intent. + * @param {Object} message + */ +u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { + var intentUrl = + u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + + ';S.request=' + encodeURIComponent(JSON.stringify(message)) + + ';end'; + document.location = intentUrl; +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { + return "WrappedAuthenticatorPort_"; +}; + + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message') { + var self = this; + /* Register a callback to that executes when + * chrome injects the response. */ + window.addEventListener( + 'message', self.onRequestUpdate_.bind(self, handler), false); + } else { + console.error('WrappedAuthenticatorPort only supports message'); + } +}; + +/** + * Callback invoked when a response is received from the Authenticator. + * @param function({data: Object}) callback + * @param {Object} message message Object + */ +u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = + function(callback, message) { + var messageObject = JSON.parse(message.data); + var intentUrl = messageObject['intentURL']; + + var errorCode = messageObject['errorCode']; + var responseObject = null; + if (messageObject.hasOwnProperty('data')) { + responseObject = /** @type {Object} */ ( + JSON.parse(messageObject['data'])); + } + + callback({'data': responseObject}); +}; + +/** + * Base URL for intents to Authenticator. + * @const + * @private + */ +u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = + 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; + +/** + * Wrap the iOS client app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedIosPort_ = function() {}; + +/** + * Launch the iOS client app request + * @param {Object} message + */ +u2f.WrappedIosPort_.prototype.postMessage = function(message) { + var str = JSON.stringify(message); + var url = "u2f://auth?" + encodeURI(str); + location.replace(url); +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedIosPort_.prototype.getPortType = function() { + return "WrappedIosPort_"; +}; + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name !== 'message') { + console.error('WrappedIosPort only supports message'); + } +}; + +/** + * Sets up an embedded trampoline iframe, sourced from the extension. + * @param {function(MessagePort)} callback + * @private + */ +u2f.getIframePort_ = function(callback) { + // Create the iframe + var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; + var iframe = document.createElement('iframe'); + iframe.src = iframeOrigin + '/u2f-comms.html'; + iframe.setAttribute('style', 'display:none'); + document.body.appendChild(iframe); + + var channel = new MessageChannel(); + var ready = function(message) { + if (message.data == 'ready') { + channel.port1.removeEventListener('message', ready); + callback(channel.port1); + } else { + console.error('First event on iframe port was not "ready"'); + } + }; + channel.port1.addEventListener('message', ready); + channel.port1.start(); + + iframe.addEventListener('load', function() { + // Deliver the port to the iframe and initialize + iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); + }); +}; + + +//High-level JS API + +/** + * Default extension response timeout in seconds. + * @const + */ +u2f.EXTENSION_TIMEOUT_SEC = 30; + +/** + * A singleton instance for a MessagePort to the extension. + * @type {MessagePort|u2f.WrappedChromeRuntimePort_} + * @private + */ +u2f.port_ = null; + +/** + * Callbacks waiting for a port + * @type {Array} + * @private + */ +u2f.waitingForPort_ = []; + +/** + * A counter for requestIds. + * @type {number} + * @private + */ +u2f.reqCounter_ = 0; + +/** + * A map from requestIds to client callbacks + * @type {Object.} + * @private + */ +u2f.callbackMap_ = {}; + +/** + * Creates or retrieves the MessagePort singleton to use. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + * @private + */ +u2f.getPortSingleton_ = function(callback) { + if (u2f.port_) { + callback(u2f.port_); + } else { + if (u2f.waitingForPort_.length == 0) { + u2f.getMessagePort(function(port) { + u2f.port_ = port; + u2f.port_.addEventListener('message', + /** @type {function(Event)} */ (u2f.responseHandler_)); + + // Careful, here be async callbacks. Maybe. + while (u2f.waitingForPort_.length) + u2f.waitingForPort_.shift()(u2f.port_); + }); + } + u2f.waitingForPort_.push(callback); + } +}; + +/** + * Handles response messages from the extension. + * @param {MessageEvent.} message + * @private + */ +u2f.responseHandler_ = function(message) { + var response = message.data; + var reqId = response['requestId']; + if (!reqId || !u2f.callbackMap_[reqId]) { + console.error('Unknown or missing requestId in response.'); + return; + } + var cb = u2f.callbackMap_[reqId]; + delete u2f.callbackMap_[reqId]; + cb(response['responseData']); +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the sign request. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual sign request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; + console.log("Extension JS API Version: ", js_api_version); + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual sign request in the supported API version. + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the register request. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual register request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; + console.log("Extension JS API Version: ", js_api_version); + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual register request in the supported API version. + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatRegisterRequest_( + appId, registeredKeys, registerRequests, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + + +/** + * Dispatches a message to the extension to find out the supported + * JS API version. + * If the user is on a mobile phone and is thus using Google Authenticator instead + * of the Chrome extension, don't send the request and simply return 0. + * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.getApiVersion = function(callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + // If we are using Android Google Authenticator or iOS client app, + // do not fire an intent to ask which JS API version to use. + if (port.getPortType) { + var apiVersion; + switch (port.getPortType()) { + case 'WrappedIosPort_': + case 'WrappedAuthenticatorPort_': + apiVersion = 1.1; + break; + + default: + apiVersion = 0; + break; + } + callback({ 'js_api_version': apiVersion }); + return; + } + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var req = { + type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, + timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), + requestId: reqId + }; + port.postMessage(req); + }); +}; diff --git a/src/views/login.ejs b/src/views/login.ejs index 928684047..5278bf210 100644 --- a/src/views/login.ejs +++ b/src/views/login.ejs @@ -4,18 +4,31 @@ - @@ -27,8 +28,7 @@ - - - - + <% include scripts %> + + diff --git a/src/views/reset-password-form.ejs b/src/views/reset-password-form.ejs new file mode 100644 index 000000000..7a4f44d01 --- /dev/null +++ b/src/views/reset-password-form.ejs @@ -0,0 +1,18 @@ + + + Reset Password + <% include head %> + + + + + <% include scripts %> + + diff --git a/src/views/reset-password.ejs b/src/views/reset-password.ejs new file mode 100644 index 000000000..603417549 --- /dev/null +++ b/src/views/reset-password.ejs @@ -0,0 +1,19 @@ + + + Reset Password + <% include head %> + + + + + <% include scripts %> + + diff --git a/src/views/scripts.ejs b/src/views/scripts.ejs new file mode 100644 index 000000000..49ad79ac8 --- /dev/null +++ b/src/views/scripts.ejs @@ -0,0 +1,2 @@ + + diff --git a/src/views/u2f_register.ejs b/src/views/u2f-register.ejs similarity index 50% rename from src/views/u2f_register.ejs rename to src/views/u2f-register.ejs index 0bf8b353d..d7b743eae 100644 --- a/src/views/u2f_register.ejs +++ b/src/views/u2f-register.ejs @@ -1,7 +1,7 @@ FIDO U2F Registration - + <% include head %> - - - - + <% include scripts %> + + diff --git a/test/unitary/requests.js b/test/unitary/requests.js new file mode 100644 index 000000000..deb7a06cb --- /dev/null +++ b/test/unitary/requests.js @@ -0,0 +1,130 @@ + +var Promise = require('bluebird'); +var request = Promise.promisifyAll(require('request')); +var assert = require('assert'); + +module.exports = function(port) { + var PORT = port; + var BASE_URL = 'http://localhost:' + PORT; + + function execute_reset_password(jar, transporter, user, new_password) { + return request.postAsync({ + url: BASE_URL + '/authentication/reset-password', + jar: jar, + form: { userid: user } + }) + .then(function(res) { + assert.equal(res.statusCode, 204); + var html_content = transporter.sendMail.getCall(0).args[0].html; + var regexp = /identity_token=([a-zA-Z0-9]+)/; + var token = regexp.exec(html_content)[1]; + // console.log(html_content, token); + return request.getAsync({ + url: BASE_URL + '/authentication/reset-password?identity_token=' + token, + jar: jar + }) + }) + .then(function(res) { + assert.equal(res.statusCode, 200); + return request.postAsync({ + url: BASE_URL + '/authentication/new-password', + jar: jar, + form: { + password: new_password + } + }); + }); + } + + function execute_totp(jar, token) { + return request.postAsync({ + url: BASE_URL + '/authentication/2ndfactor/totp', + jar: jar, + form: { + token: token + } + }); + } + + function execute_u2f_authentication(jar) { + return request.getAsync({ + url: BASE_URL + '/authentication/2ndfactor/u2f/sign_request', + jar: jar + }) + .then(function(res) { + assert.equal(res.statusCode, 200); + return request.postAsync({ + url: BASE_URL + '/authentication/2ndfactor/u2f/sign', + jar: jar, + form: { + } + }); + }); + } + + function execute_verification(jar) { + return request.getAsync({ url: BASE_URL + '/authentication/verify', jar: jar }) + } + + function execute_login(jar) { + return request.getAsync({ url: BASE_URL + '/authentication/login', jar: jar }) + } + + function execute_u2f_registration(jar, transporter) { + return request.postAsync({ + url: BASE_URL + '/authentication/u2f-register', + jar: jar + }) + .then(function(res) { + assert.equal(res.statusCode, 204); + var html_content = transporter.sendMail.getCall(0).args[0].html; + var regexp = /identity_token=([a-zA-Z0-9]+)/; + var token = regexp.exec(html_content)[1]; + // console.log(html_content, token); + return request.getAsync({ + url: BASE_URL + '/authentication/u2f-register?identity_token=' + token, + jar: jar + }) + }) + .then(function(res) { + assert.equal(res.statusCode, 200); + return request.getAsync({ + url: BASE_URL + '/authentication/2ndfactor/u2f/register_request', + jar: jar, + }); + }) + .then(function(res) { + assert.equal(res.statusCode, 200); + return request.postAsync({ + url: BASE_URL + '/authentication/2ndfactor/u2f/register', + jar: jar, + form: { + s: 'test' + } + }); + }); + } + + function execute_first_factor(jar) { + return request.postAsync({ + url: BASE_URL + '/authentication/1stfactor', + jar: jar, + form: { + username: 'test_ok', + password: 'password' + } + }); + } + + return { + login: execute_login, + verify: execute_verification, + reset_password: execute_reset_password, + u2f_authentication: execute_u2f_authentication, + u2f_registration: execute_u2f_registration, + first_factor: execute_first_factor, + totp: execute_totp, + } + +} + diff --git a/test/unitary/routes/test_reset_password.js b/test/unitary/routes/test_reset_password.js new file mode 100644 index 000000000..a92d56c53 --- /dev/null +++ b/test/unitary/routes/test_reset_password.js @@ -0,0 +1,140 @@ +var sinon = require('sinon'); +var winston = require('winston'); +var reset_password = require('../../../src/lib/routes/reset_password'); +var assert = require('assert'); + +describe('test reset password', function() { + var req, res; + var user_data_store; + var ldap_client; + var ldap; + + beforeEach(function() { + req = {} + req.body = {}; + req.body.userid = 'user'; + req.app = {}; + req.app.get = sinon.stub(); + req.app.get.withArgs('logger').returns(winston); + req.session = {}; + req.session.auth_session = {}; + req.session.auth_session.userid = 'user'; + req.session.auth_session.email = 'user@example.com'; + req.session.auth_session.first_factor = true; + req.session.auth_session.second_factor = false; + req.headers = {}; + req.headers.host = 'localhost'; + + var options = {}; + options.inMemoryOnly = true; + + user_data_store = {}; + user_data_store.set_u2f_meta = sinon.stub().returns(Promise.resolve({})); + user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve({})); + user_data_store.issue_identity_check_token = sinon.stub().returns(Promise.resolve({})); + user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({})); + req.app.get.withArgs('user data store').returns(user_data_store); + + ldap = {}; + ldap.Change = sinon.spy(); + req.app.get.withArgs('ldap').returns(ldap); + + ldap_client = {}; + ldap_client.bind = sinon.stub(); + ldap_client.search = sinon.stub(); + ldap_client.modify = sinon.stub(); + req.app.get.withArgs('ldap client').returns(ldap_client); + + config = {}; + config.ldap_users_dn = 'dc=example,dc=com'; + req.app.get.withArgs('config').returns(config); + + res = {}; + res.send = sinon.spy(); + res.json = sinon.spy(); + res.status = sinon.spy(); + }); + + describe('test reset password identity pre check', test_reset_password_check); + describe('test reset password post', test_reset_password_post); + + function test_reset_password_check() { + it('should fail when no userid is provided', function(done) { + req.body.userid = undefined; + reset_password.icheck_interface.pre_check_callback(req) + .catch(function(err) { + done(); + }); + }); + + it('should fail if ldap fail', function(done) { + ldap_client.search.yields('Internal error'); + reset_password.icheck_interface.pre_check_callback(req) + .catch(function(err) { + done(); + }); + }); + + it('should returns identity when ldap replies', function(done) { + var doc = {}; + doc.object = {}; + doc.object.email = 'test@example.com'; + doc.object.userid = 'user'; + + var res = {}; + res.on = sinon.stub(); + res.on.withArgs('searchEntry').yields(doc); + res.on.withArgs('end').yields(); + + ldap_client.search.yields(undefined, res); + reset_password.icheck_interface.pre_check_callback(req) + .then(function() { + done(); + }); + }); + } + + function test_reset_password_post() { + it('should update the password', function(done) { + req.session.auth_session.identity_check = {}; + req.session.auth_session.identity_check.userid = 'user'; + req.session.auth_session.identity_check.challenge = 'reset-password'; + req.body = {}; + req.body.password = 'new-password'; + + ldap_client.modify.yields(undefined); + ldap_client.bind.yields(undefined); + res.send = sinon.spy(function() { + assert.equal(ldap_client.modify.getCall(0).args[0], 'cn=user,dc=example,dc=com'); + assert.equal(res.status.getCall(0).args[0], 204); + done(); + }); + reset_password.post(req, res); + }); + + it('should fail if identity_challenge does not exist', function(done) { + req.session.auth_session.identity_check = {}; + req.session.auth_session.identity_check.challenge = undefined; + res.send = sinon.spy(function() { + assert.equal(res.status.getCall(0).args[0], 403); + done(); + }); + reset_password.post(req, res); + }); + + it('should fail when ldap fails', function(done) { + req.session.auth_session.identity_check = {}; + req.session.auth_session.identity_check.challenge = 'reset-password'; + req.body = {}; + req.body.password = 'new-password'; + + ldap_client.bind.yields(undefined); + ldap_client.modify.yields('Internal error with LDAP'); + res.send = sinon.spy(function() { + assert.equal(res.status.getCall(0).args[0], 500); + done(); + }); + reset_password.post(req, res); + }); + } +}); diff --git a/test/unitary/routes/test_u2f.js b/test/unitary/routes/test_u2f.js index 2d939d4ed..401152d2b 100644 --- a/test/unitary/routes/test_u2f.js +++ b/test/unitary/routes/test_u2f.js @@ -19,6 +19,9 @@ describe('test u2f routes', function() { req.session.auth_session.userid = 'user'; req.session.auth_session.first_factor = true; req.session.auth_session.second_factor = false; + req.session.auth_session.identity_check = {}; + req.session.auth_session.identity_check.challenge = 'u2f-register'; + req.session.auth_session.register_request = {}; req.headers = {}; req.headers.host = 'localhost'; @@ -73,6 +76,15 @@ describe('test u2f routes', function() { req.app.get.withArgs('u2f').returns(u2f_mock); u2f.register_request(req, res); }); + + it('should return forbidden if identity has not been verified', function(done) { + res.send = sinon.spy(function(data) { + assert.equal(403, res.status.getCall(0).args[0]); + done(); + }); + req.session.auth_session.identity_check = undefined; + u2f.register_request(req, res); + }); } function test_registration() { @@ -97,7 +109,7 @@ describe('test u2f routes', function() { it('should return unauthorized on finishRegistration error', function(done) { res.send = sinon.spy(function(data) { - assert.equal(401, res.status.getCall(0).args[0]); + assert.equal(500, res.status.getCall(0).args[0]); done(); }); var user_key_container = {}; @@ -110,9 +122,9 @@ describe('test u2f routes', function() { u2f.register(req, res); }); - it('should return unauthorized error when no auth request has been initiated', function(done) { + it('should return forbidden error when no auth request has been initiated', function(done) { res.send = sinon.spy(function(data) { - assert.equal(401, res.status.getCall(0).args[0]); + assert.equal(403, res.status.getCall(0).args[0]); done(); }); var user_key_container = {}; @@ -120,9 +132,19 @@ describe('test u2f routes', function() { u2f_mock.finishRegistration = sinon.stub(); u2f_mock.finishRegistration.returns(Promise.resolve()); + req.session.auth_session.register_request = undefined; req.app.get.withArgs('u2f').returns(u2f_mock); u2f.register(req, res); }); + + it('should return forbidden error when identity has not been verified', function(done) { + res.send = sinon.spy(function(data) { + assert.equal(403, res.status.getCall(0).args[0]); + done(); + }); + req.session.auth_session.identity_check = undefined; + u2f.register(req, res); + }); } function test_signing_request() { @@ -148,7 +170,7 @@ describe('test u2f routes', function() { it('should return unauthorized error on registration request error', function(done) { res.send = sinon.spy(function(data) { - assert.equal(401, res.status.getCall(0).args[0]); + assert.equal(500, res.status.getCall(0).args[0]); done(); }); var user_key_container = {}; @@ -209,7 +231,7 @@ describe('test u2f routes', function() { it('should return unauthorized error on registration request internal error', function(done) { res.send = sinon.spy(function(data) { - assert.equal(401, res.status.getCall(0).args[0]); + assert.equal(500, res.status.getCall(0).args[0]); done(); }); var user_key_container = {}; diff --git a/test/unitary/routes/test_u2f_register.js b/test/unitary/routes/test_u2f_register.js index 86a904dcd..c3860ea5a 100644 --- a/test/unitary/routes/test_u2f_register.js +++ b/test/unitary/routes/test_u2f_register.js @@ -1,10 +1,9 @@ - var sinon = require('sinon'); var winston = require('winston'); -var u2f_register = require('../../../src/lib/routes/u2f_register'); +var u2f_register = require('../../../src/lib/routes/u2f_register_handler'); var assert = require('assert'); -describe('test register handle', function() { +describe('test register handler', function() { var req, res; var user_data_store; @@ -28,88 +27,52 @@ describe('test register handle', function() { user_data_store = {}; user_data_store.set_u2f_meta = sinon.stub().returns(Promise.resolve({})); user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve({})); - user_data_store.save_u2f_registration_token = sinon.stub().returns(Promise.resolve({})); - user_data_store.consume_u2f_registration_token = sinon.stub().returns(Promise.resolve({})); + user_data_store.issue_identity_check_token = sinon.stub().returns(Promise.resolve({})); + user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({})); req.app.get.withArgs('user data store').returns(user_data_store); res = {}; res.send = sinon.spy(); res.json = sinon.spy(); res.status = sinon.spy(); - }) + }); + describe('test u2f registration check', test_registration_check); - describe('test registration handler (POST)', test_registration_handler_post); - describe('test registration handler (GET)', test_registration_handler_get); - - function test_registration_handler_post() { - it('should issue a registration token', function(done) { - res.send = sinon.spy(function() { - assert.equal(204, res.status.getCall(0).args[0]); - assert.equal('user', user_data_store.save_u2f_registration_token.getCall(0).args[0]); - assert.equal(4 * 60 * 1000, user_data_store.save_u2f_registration_token.getCall(0).args[2]); + function test_registration_check() { + it('should fail if first_factor has not been passed', function(done) { + req.session.auth_session.first_factor = false; + u2f_register.icheck_interface.pre_check_callback(req) + .catch(function(err) { done(); }); - var email_sender = {}; - email_sender.send = sinon.stub().returns(Promise.resolve()); - req.app.get.withArgs('email sender').returns(email_sender); - u2f_register.register_handler_post(req, res); }); - it('should fail during issuance of a registration token', function(done) { - res.send = sinon.spy(function() { - assert.equal(500, res.status.getCall(0).args[0]); + it('should fail if userid is missing', function(done) { + req.session.auth_session.first_factor = false; + req.session.auth_session.userid = undefined; + + u2f_register.icheck_interface.pre_check_callback(req) + .catch(function(err) { done(); }); - user_data_store.save_u2f_registration_token = sinon.stub().returns(Promise.reject('Error')); - u2f_register.register_handler_post(req, res); }); - it('should send bad request if no email has been found for the given user', function(done) { - res.send = sinon.spy(function() { - assert.equal(400, res.status.getCall(0).args[0]); - done(); - }); + it('should fail if email is missing', function(done) { + req.session.auth_session.first_factor = false; req.session.auth_session.email = undefined; - var email_sender = {}; - email_sender.send = sinon.stub().returns(Promise.resolve()); - req.app.get.withArgs('email sender').returns(email_sender); - u2f_register.register_handler_post(req, res); - }); - } - - function test_registration_handler_get() { - it('should send forbidden if no registration_token has been provided', function(done) { - res.send = sinon.spy(function() { - assert.equal(403, res.status.getCall(0).args[0]); + u2f_register.icheck_interface.pre_check_callback(req) + .catch(function(err) { done(); }); - u2f_register.register_handler_get(req, res); }); - - it('should render the u2f-register view when registration token is still valid', function(done) { - res.render = sinon.spy(function(data) { - assert.equal('u2f_register', data); + it('should succeed if first factor passed, userid and email are provided', function(done) { + u2f_register.icheck_interface.pre_check_callback(req) + .then(function(err) { done(); }); - req.query = {}; - req.query.registration_token = 'token'; - u2f_register.register_handler_get(req, res); - }); - - it('should send forbidden status when registration token is not valid', function(done) { - res.send = sinon.spy(function(data) { - assert.equal(403, res.status.getCall(0).args[0]); - done(); - }); - - req.params = {}; - req.params.registration_token = 'token'; - user_data_store.consume_u2f_registration_token = sinon.stub().returns(Promise.reject('Not valid anymore')); - - u2f_register.register_handler_get(req, res); }); } }); diff --git a/test/unitary/test_data_persistence.js b/test/unitary/test_data_persistence.js index 80dc876b1..072f9c361 100644 --- a/test/unitary/test_data_persistence.js +++ b/test/unitary/test_data_persistence.js @@ -1,18 +1,20 @@ var server = require('../../src/lib/server'); -var request = require('request'); +var Promise = require('bluebird'); +var request = Promise.promisifyAll(require('request')); var assert = require('assert'); var speakeasy = require('speakeasy'); var sinon = require('sinon'); -var Promise = require('bluebird'); var tmp = require('tmp'); - -var request = Promise.promisifyAll(request); +var nedb = require('nedb'); var PORT = 8050; var BASE_URL = 'http://localhost:' + PORT; +var requests = require('./requests')(PORT); + + describe('test data persistence', function() { var u2f; var tmpDir; @@ -73,38 +75,52 @@ describe('test data persistence', function() { u2f.finishRegistration.returns(Promise.resolve(sign_status)); u2f.startAuthentication.returns(Promise.resolve(registration_request)); u2f.finishAuthentication.returns(Promise.resolve(registration_status)); - + + var nodemailer = {}; + var transporter = { + sendMail: sinon.stub().yields() + }; + nodemailer.createTransport = sinon.spy(function() { + return transporter; + }); + + var deps = {}; + deps.u2f = u2f; + deps.nedb = nedb; + deps.nodemailer = nodemailer; + var j1 = request.jar(); var j2 = request.jar(); - return start_server(config, ldap_client, u2f) + + return start_server(config, ldap_client, deps) .then(function(s) { server = s; - return execute_login(j1); + return requests.login(j1); }) .then(function(res) { - return execute_first_factor(j1); + return requests.first_factor(j1); }) .then(function() { - return execute_u2f_registration(j1); + return requests.u2f_registration(j1, transporter); }) .then(function() { - return execute_u2f_authentication(j1); + return requests.u2f_authentication(j1); }) .then(function() { return stop_server(server); }) .then(function() { - return start_server(config, ldap_client, u2f) + return start_server(config, ldap_client, deps) }) .then(function(s) { server = s; - return execute_login(j2); + return requests.login(j2); }) .then(function() { - return execute_first_factor(j2); + return requests.first_factor(j2); }) .then(function() { - return execute_u2f_authentication(j2); + return requests.u2f_authentication(j2); }) .then(function(res) { assert.equal(204, res.statusCode); @@ -117,9 +133,9 @@ describe('test data persistence', function() { }); }); - function start_server(config, ldap_client, u2f) { + function start_server(config, ldap_client, deps) { return new Promise(function(resolve, reject) { - var s = server.run(config, ldap_client, u2f); + var s = server.run(config, ldap_client, deps); resolve(s); }); } @@ -130,55 +146,4 @@ describe('test data persistence', function() { resolve(); }); } - - function execute_first_factor(jar) { - return request.postAsync({ - url: BASE_URL + '/1stfactor', - jar: jar, - form: { - username: 'test_ok', - password: 'password' - } - }); - } - - function execute_u2f_registration(jar) { - return request.getAsync({ - url: BASE_URL + '/2ndfactor/u2f/register_request', - jar: jar - }) - .then(function(res) { - return request.postAsync({ - url: BASE_URL + '/2ndfactor/u2f/register', - jar: jar, - form: { - s: 'test' - } - }); - }); - } - - function execute_u2f_authentication(jar) { - return request.getAsync({ - url: BASE_URL + '/2ndfactor/u2f/sign_request', - jar: jar - }) - .then(function() { - return request.postAsync({ - url: BASE_URL + '/2ndfactor/u2f/sign', - jar: jar, - form: { - s: 'test' - } - }); - }); - } - - function execute_verification(jar) { - return request.getAsync({ url: BASE_URL + '/verify', jar: jar }) - } - - function execute_login(jar) { - return request.getAsync({ url: BASE_URL + '/login', jar: jar }) - } }); diff --git a/test/unitary/test_identity_check.js b/test/unitary/test_identity_check.js new file mode 100644 index 000000000..ae2b571b5 --- /dev/null +++ b/test/unitary/test_identity_check.js @@ -0,0 +1,208 @@ + +var sinon = require('sinon'); +var identity_check = require('../../src/lib/identity_check'); +var exceptions = require('../../src/lib/exceptions'); +var assert = require('assert'); +var winston = require('winston'); +var Promise = require('bluebird'); + +describe('test identity check process', function() { + var req, res, app, icheck_interface; + var user_data_store; + var email_sender; + + beforeEach(function() { + req = {}; + res = {}; + + app = {}; + icheck_interface = {}; + icheck_interface.pre_check_callback = sinon.stub(); + + user_data_store = {}; + user_data_store.issue_identity_check_token = sinon.stub(); + user_data_store.issue_identity_check_token.returns(Promise.resolve()); + user_data_store.consume_identity_check_token = sinon.stub(); + user_data_store.consume_identity_check_token.returns(Promise.resolve({ userid: 'user' })); + + email_sender = {}; + email_sender.send = sinon.stub(); + email_sender.send = sinon.stub().returns(Promise.resolve()); + + req.headers = {}; + req.session = {}; + req.session.auth_session = {}; + + req.query = {}; + req.app = {}; + req.app.get = sinon.stub(); + req.app.get.withArgs('logger').returns(winston); + req.app.get.withArgs('user data store').returns(user_data_store); + req.app.get.withArgs('email sender').returns(email_sender); + + res.status = sinon.spy(); + res.send = sinon.spy(); + res.redirect = sinon.spy(); + res.render = sinon.spy(); + + app.get = sinon.spy(); + app.post = sinon.spy(); + }); + + it('should register a POST and GET endpoint', function() { + var app = {}; + app.get = sinon.spy(); + app.post = sinon.spy(); + var endpoint = '/test'; + var icheck_interface = {}; + + identity_check(app, endpoint, icheck_interface); + + assert(app.get.calledOnce); + assert(app.get.calledWith(endpoint)); + + assert(app.post.calledOnce); + assert(app.post.calledWith(endpoint)); + }); + + describe('test POST', test_post_handler); + describe('test GET', test_get_handler); + + + function test_post_handler() { + it('should send 403 if pre check rejects', function(done) { + var endpoint = '/protected'; + + icheck_interface.pre_check_callback.returns(Promise.reject('No access')); + identity_check(app, endpoint, icheck_interface); + + res.send = sinon.spy(function() { + assert.equal(res.status.getCall(0).args[0], 403); + done(); + }); + + var handler = app.post.getCall(0).args[1]; + handler(req, res); + }); + + it('should send 400 if email is missing in provided identity', function(done) { + var endpoint = '/protected'; + var identity = { userid: 'abc' }; + + icheck_interface.pre_check_callback.returns(Promise.resolve(identity)); + identity_check(app, endpoint, icheck_interface); + + res.send = sinon.spy(function() { + assert.equal(res.status.getCall(0).args[0], 400); + done(); + }); + + var handler = app.post.getCall(0).args[1]; + handler(req, res); + }); + + it('should send 400 if userid is missing in provided identity', function(done) { + var endpoint = '/protected'; + var identity = { email: 'abc@example.com' }; + + icheck_interface.pre_check_callback.returns(Promise.resolve(identity)); + identity_check(app, endpoint, icheck_interface); + + res.send = sinon.spy(function() { + assert.equal(res.status.getCall(0).args[0], 400); + done(); + }); + var handler = app.post.getCall(0).args[1]; + handler(req, res); + }); + + it('should issue a token, send an email and return 204', function(done) { + var endpoint = '/protected'; + var identity = { userid: 'user', email: 'abc@example.com' }; + req.headers.host = 'localhost'; + req.headers['x-original-uri'] = '/auth/test'; + + icheck_interface.pre_check_callback.returns(Promise.resolve(identity)); + identity_check(app, endpoint, icheck_interface); + + res.send = sinon.spy(function() { + assert.equal(res.status.getCall(0).args[0], 204); + assert(email_sender.send.calledOnce); + assert(user_data_store.issue_identity_check_token.calledOnce); + assert.equal(user_data_store.issue_identity_check_token.getCall(0).args[0], 'user'); + assert.equal(user_data_store.issue_identity_check_token.getCall(0).args[3], 240000); + assert(email_sender.send.getCall(0).args[2].startsWith(' -1); assert(is_secret_page_content); - return getPromised(BASE_URL + '/auth/logout') + return getPromised(BASE_URL + '/authentication/logout') }) .then(function(data) { assert.equal(data.statusCode, 200); @@ -164,5 +164,5 @@ function getHomePage() { } function getLoginPage() { - return getPromised(BASE_URL + '/auth/login'); + return getPromised(BASE_URL + '/authentication/login'); } diff --git a/test/unitary/requests.js b/test/unitary/requests.js index deb7a06cb..dfa4e4d95 100644 --- a/test/unitary/requests.js +++ b/test/unitary/requests.js @@ -115,6 +115,17 @@ module.exports = function(port) { } }); } + + function execute_failing_first_factor(jar) { + return request.postAsync({ + url: BASE_URL + '/authentication/1stfactor', + jar: jar, + form: { + username: 'test_nok', + password: 'password' + } + }); + } return { login: execute_login, @@ -123,6 +134,7 @@ module.exports = function(port) { u2f_authentication: execute_u2f_authentication, u2f_registration: execute_u2f_registration, first_factor: execute_first_factor, + failing_first_factor: execute_failing_first_factor, totp: execute_totp, } diff --git a/test/unitary/routes/test_deny_not_logged.js b/test/unitary/routes/test_deny_not_logged.js index 04b003f7e..48a7007c4 100644 --- a/test/unitary/routes/test_deny_not_logged.js +++ b/test/unitary/routes/test_deny_not_logged.js @@ -6,11 +6,11 @@ var assert = require('assert'); var denyNotLogged = require('../../../src/lib/routes/deny_not_logged'); describe('test not logged', function() { - it('should return status code 401 when auth_session has not been previously created', function() { + it('should return status code 403 when auth_session has not been previously created', function() { return test_auth_session_not_created(); }); - it('should return status code 401 when auth_session has failed first factor', function() { + it('should return status code 403 when auth_session has failed first factor', function() { return test_auth_first_factor_not_validated(); }); @@ -23,7 +23,7 @@ function test_auth_session_not_created() { return new Promise(function(resolve, reject) { var send = sinon.spy(resolve); var status = sinon.spy(function(code) { - assert.equal(401, code); + assert.equal(403, code); }); var req = { session: {} @@ -42,7 +42,7 @@ function test_auth_first_factor_not_validated() { return new Promise(function(resolve, reject) { var send = sinon.spy(resolve); var status = sinon.spy(function(code) { - assert.equal(401, code); + assert.equal(403, code); }); var req = { session: { diff --git a/test/unitary/routes/test_first_factor.js b/test/unitary/routes/test_first_factor.js index 840a9b99c..aabd085fc 100644 --- a/test/unitary/routes/test_first_factor.js +++ b/test/unitary/routes/test_first_factor.js @@ -4,11 +4,13 @@ var Promise = require('bluebird'); var assert = require('assert'); var winston = require('winston'); var first_factor = require('../../../src/lib/routes/first_factor'); +var exceptions = require('../../../src/lib/exceptions'); describe('test the first factor validation route', function() { var req, res; var ldap_interface_mock; var search_res_ok; + var regulator; beforeEach(function() { ldap_interface_mock = { @@ -31,10 +33,18 @@ describe('test the first factor validation route', function() { }); ldap_interface_mock.search.yields(undefined, search_res_ok); + regulator = {}; + regulator.mark = sinon.stub(); + regulator.regulate = sinon.stub(); + + regulator.mark.returns(Promise.resolve()); + regulator.regulate.returns(Promise.resolve()); + var app_get = sinon.stub(); app_get.withArgs('ldap client').returns(ldap_interface_mock); app_get.withArgs('config').returns(ldap_interface_mock); app_get.withArgs('logger').returns(winston); + app_get.withArgs('authentication regulator').returns(regulator); req = { app: { @@ -69,27 +79,36 @@ describe('test the first factor validation route', function() { }); }); - it('should return status code 401 when LDAP binding fails', function() { - return new Promise(function(resolve, reject) { - res.send = sinon.spy(function(data) { - assert.equal(401, res.status.getCall(0).args[0]); - resolve(); - }); - ldap_interface_mock.bind.yields('Bad credentials'); - first_factor(req, res); + it('should return status code 401 when LDAP binding fails', function(done) { + res.send = sinon.spy(function(data) { + assert.equal(401, res.status.getCall(0).args[0]); + assert.equal(regulator.mark.getCall(0).args[0], 'username'); + done(); }); + ldap_interface_mock.bind.yields('Bad credentials'); + first_factor(req, res); }); - it('should return status code 500 when LDAP binding fails', function() { - return new Promise(function(resolve, reject) { - res.send = sinon.spy(function(data) { - assert.equal(500, res.status.getCall(0).args[0]); - resolve(); - }); - ldap_interface_mock.bind.yields(undefined); - ldap_interface_mock.search.yields('error'); - first_factor(req, res); + it('should return status code 500 when LDAP binding throws', function(done) { + res.send = sinon.spy(function(data) { + assert.equal(500, res.status.getCall(0).args[0]); + done(); }); + ldap_interface_mock.bind.yields(undefined); + ldap_interface_mock.search.yields('error'); + first_factor(req, res); + }); + + it('should return status code 403 when regulator rejects authentication', function(done) { + var err = new exceptions.AuthenticationRegulationError(); + regulator.regulate.returns(Promise.reject(err)); + res.send = sinon.spy(function(data) { + assert.equal(403, res.status.getCall(0).args[0]); + done(); + }); + ldap_interface_mock.bind.yields(undefined); + ldap_interface_mock.search.yields(undefined); + first_factor(req, res); }); }); diff --git a/test/unitary/routes/test_reset_password.js b/test/unitary/routes/test_reset_password.js index a92d56c53..8ceea448f 100644 --- a/test/unitary/routes/test_reset_password.js +++ b/test/unitary/routes/test_reset_password.js @@ -95,7 +95,7 @@ describe('test reset password', function() { } function test_reset_password_post() { - it('should update the password', function(done) { + it('should update the password and reset auth_session for reauthentication', function(done) { req.session.auth_session.identity_check = {}; req.session.auth_session.identity_check.userid = 'user'; req.session.auth_session.identity_check.challenge = 'reset-password'; @@ -107,6 +107,7 @@ describe('test reset password', function() { res.send = sinon.spy(function() { assert.equal(ldap_client.modify.getCall(0).args[0], 'cn=user,dc=example,dc=com'); assert.equal(res.status.getCall(0).args[0], 204); + assert.equal(req.session.auth_session, undefined); done(); }); reset_password.post(req, res); diff --git a/test/unitary/routes/test_u2f.js b/test/unitary/routes/test_u2f.js index 401152d2b..d8c65ae8b 100644 --- a/test/unitary/routes/test_u2f.js +++ b/test/unitary/routes/test_u2f.js @@ -95,7 +95,8 @@ describe('test u2f routes', function() { certificate: 'cert' }; res.send = sinon.spy(function(data) { - assert('user', user_data_store.set_u2f_meta.getCall(0).args[0]) + assert.equal('user', user_data_store.set_u2f_meta.getCall(0).args[0]) + assert.equal(req.session.auth_session.identity_check, undefined); done(); }); var u2f_mock = {}; diff --git a/test/unitary/test_authentication_regulator.js b/test/unitary/test_authentication_regulator.js new file mode 100644 index 000000000..18b46c9ec --- /dev/null +++ b/test/unitary/test_authentication_regulator.js @@ -0,0 +1,70 @@ + +var AuthenticationRegulator = require('../../src/lib/authentication_regulator'); +var UserDataStore = require('../../src/lib/user_data_store'); +var DataStore = require('nedb'); +var exceptions = require('../../src/lib/exceptions'); +var MockDate = require('mockdate'); + +describe('test authentication regulator', function() { + it('should mark 2 authentication and regulate (resolve)', function() { + var options = {}; + options.inMemoryOnly = true; + var data_store = new UserDataStore(DataStore, options); + var regulator = new AuthenticationRegulator(data_store, 10); + var user = 'user'; + + return regulator.mark(user, false) + .then(function() { + return regulator.mark(user, true); + }) + .then(function() { + return regulator.regulate(user); + }); + }); + + it('should mark 3 authentications and regulate (reject)', function(done) { + var options = {}; + options.inMemoryOnly = true; + var data_store = new UserDataStore(DataStore, options); + var regulator = new AuthenticationRegulator(data_store, 10); + var user = 'user'; + + regulator.mark(user, false) + .then(function() { + return regulator.mark(user, false); + }) + .then(function() { + return regulator.mark(user, false); + }) + .then(function() { + return regulator.regulate(user); + }) + .catch(exceptions.AuthenticationRegulationError, function() { + done(); + }) + }); + + it('should mark 3 authentications and regulate (resolve)', function(done) { + var options = {}; + options.inMemoryOnly = true; + var data_store = new UserDataStore(DataStore, options); + var regulator = new AuthenticationRegulator(data_store, 10); + var user = 'user'; + + MockDate.set('1/2/2000 00:00:00'); + regulator.mark(user, false) + .then(function() { + MockDate.set('1/2/2000 00:00:15'); + return regulator.mark(user, false); + }) + .then(function() { + return regulator.mark(user, false); + }) + .then(function() { + return regulator.regulate(user); + }) + .then(function() { + done(); + }) + }); +}); diff --git a/test/unitary/test_identity_check.js b/test/unitary/test_identity_check.js index ae2b571b5..555cb197a 100644 --- a/test/unitary/test_identity_check.js +++ b/test/unitary/test_identity_check.js @@ -68,7 +68,6 @@ describe('test identity check process', function() { describe('test POST', test_post_handler); describe('test GET', test_get_handler); - function test_post_handler() { it('should send 403 if pre check rejects', function(done) { var endpoint = '/protected'; diff --git a/test/unitary/test_server.js b/test/unitary/test_server.js index f6a9bde47..f6acfd73a 100644 --- a/test/unitary/test_server.js +++ b/test/unitary/test_server.js @@ -6,6 +6,7 @@ var request = Promise.promisifyAll(require('request')); var assert = require('assert'); var speakeasy = require('speakeasy'); var sinon = require('sinon'); +var MockDate = require('mockdate'); var PORT = 8090; var BASE_URL = 'http://localhost:' + PORT; @@ -36,6 +37,7 @@ describe('test the server', function() { ldap_password: 'password', session_secret: 'session_secret', session_max_age: 50000, + store_in_memory: true, gmail: { user: 'user@example.com', pass: 'password' @@ -48,15 +50,8 @@ describe('test the server', function() { u2f.startAuthentication = sinon.stub(); u2f.finishAuthentication = sinon.stub(); - collection = {}; - collection.insert = sinon.stub().yields(undefined, 1); - collection.findOne = sinon.stub().yields(undefined, {}); - collection.update = sinon.stub().yields(undefined, {}); - collection.remove = sinon.stub().yields(undefined, 1); - nedb = sinon.spy(function() { - return collection; - }); - + nedb = require('nedb'); + transporter = {}; transporter.sendMail = sinon.stub().yields(); @@ -80,10 +75,12 @@ describe('test the server', function() { 'password').yields(undefined); ldap_client.bind.withArgs('cn=admin,dc=example,dc=com', 'password').yields(undefined); - ldap_client.search.yields(undefined, search_res); + ldap_client.bind.withArgs('cn=test_nok,ou=users,dc=example,dc=com', 'password').yields('error'); + ldap_client.modify.yields(undefined); + ldap_client.search.yields(undefined, search_res); var deps = {}; deps.u2f = u2f; @@ -101,18 +98,97 @@ describe('test the server', function() {   }); describe('test GET /login', function() { - test_login() + test_login(); }); describe('test GET /logout', function() { - test_logout() + test_logout(); + }); + + describe('test GET /reset-password-form', function() { + test_reset_password_form(); + }); + + describe('test endpoints locks', function() { + function should_post_and_reply_with(url, status_code) { + return request.postAsync(url).then(function(response) { + assert.equal(response.statusCode, status_code); + return Promise.resolve(); + }) + } + + function should_get_and_reply_with(url, status_code) { + return request.getAsync(url).then(function(response) { + assert.equal(response.statusCode, status_code); + return Promise.resolve(); + }) + } + + function should_post_and_reply_with_403(url) { + return should_post_and_reply_with(url, 403); +  } + function should_get_and_reply_with_403(url) { + return should_get_and_reply_with(url, 403); +  } + + function should_post_and_reply_with_401(url) { + return should_post_and_reply_with(url, 401); +  } + function should_get_and_reply_with_401(url) { + return should_get_and_reply_with(url, 401); +  } + + function should_get_and_post_reply_with_403(url) { + var p1 = should_post_and_reply_with_403(url); + var p2 = should_get_and_reply_with_403(url); + return Promise.all([p1, p2]); +  } + + it('should block /authentication/new-password', function() { + return should_post_and_reply_with_403(BASE_URL + '/authentication/new-password') + }); + + it('should block /authentication/u2f-register', function() { + return should_get_and_post_reply_with_403(BASE_URL + '/authentication/u2f-register'); + }); + + it('should block /authentication/reset-password', function() { + return should_get_and_post_reply_with_403(BASE_URL + '/authentication/reset-password'); + }); + + it('should block /authentication/2ndfactor/u2f/register_request', function() { + return should_get_and_reply_with_403(BASE_URL + '/authentication/2ndfactor/u2f/register_request'); + }); + + it('should block /authentication/2ndfactor/u2f/register', function() { + return should_post_and_reply_with_403(BASE_URL + '/authentication/2ndfactor/u2f/register'); + }); + + it('should block /authentication/2ndfactor/u2f/sign_request', function() { + return should_get_and_reply_with_403(BASE_URL + '/authentication/2ndfactor/u2f/sign_request'); + }); + + it('should block /authentication/2ndfactor/u2f/sign', function() { + return should_post_and_reply_with_403(BASE_URL + '/authentication/2ndfactor/u2f/sign'); + }); }); describe('test authentication and verification', function() { test_authentication(); test_reset_password(); + test_regulation(); }); + function test_reset_password_form() { + it('should serve the reset password form page', function(done) { + request.getAsync(BASE_URL + '/authentication/reset-password-form') + .then(function(response) { + assert.equal(response.statusCode, 200); + done(); + }); + }); + } + function test_login() { it('should serve the login page', function(done) { request.getAsync(BASE_URL + '/authentication/login') @@ -209,11 +285,6 @@ describe('test the server', function() { u2f.startAuthentication.returns(Promise.resolve(registration_request)); u2f.finishAuthentication.returns(Promise.resolve(registration_status)); - collection.insert = sinon.spy(function(data, fn) { - collection.findOne.yields(undefined, data); - fn(); - }); - var j = request.jar(); return requests.login(j) .then(function(res) { @@ -241,11 +312,6 @@ describe('test the server', function() { function test_reset_password() { it('should reset the password', function() { - collection.insert = sinon.spy(function(data, fn) { - collection.findOne.yields(undefined, data); - fn(); - }); - var j = request.jar(); return requests.login(j) .then(function(res) { @@ -262,5 +328,39 @@ describe('test the server', function() { }); }); } + + function test_regulation() { + it('should regulate authentication', function() { + var j = request.jar(); + MockDate.set('1/2/2017 00:00:00'); + return requests.login(j) + .then(function(res) { + assert.equal(res.statusCode, 200, 'get login page failed'); + return requests.failing_first_factor(j); + }) + .then(function(res) { + console.log('coucou'); + assert.equal(res.statusCode, 401, 'first factor failed'); + return requests.failing_first_factor(j); + }) + .then(function(res) { + assert.equal(res.statusCode, 401, 'first factor failed'); + return requests.failing_first_factor(j); + }) + .then(function(res) { + assert.equal(res.statusCode, 401, 'first factor failed'); + return requests.failing_first_factor(j); + }) + .then(function(res) { + assert.equal(res.statusCode, 403, 'first factor failed'); + MockDate.set('1/2/2017 00:30:00'); + return requests.failing_first_factor(j); + }) + .then(function(res) { + assert.equal(res.statusCode, 401, 'first factor failed'); + return Promise.resolve(); + }) + }); + } }); diff --git a/test/unitary/user_data_store/test_authentication_audit.js b/test/unitary/user_data_store/test_authentication_audit.js new file mode 100644 index 000000000..d317b4803 --- /dev/null +++ b/test/unitary/user_data_store/test_authentication_audit.js @@ -0,0 +1,69 @@ + +var assert = require('assert'); +var Promise = require('bluebird'); +var sinon = require('sinon'); +var MockDate = require('mockdate'); +var UserDataStore = require('../../../src/lib/user_data_store'); +var DataStore = require('nedb'); + +describe('test user data store', function() { + describe('test authentication traces', test_authentication_traces); +}); + +function test_authentication_traces() { + it('should save an authentication trace in db', function() { + var options = {}; + options.inMemoryOnly = true; + + var data_store = new UserDataStore(DataStore, options); + var userid = 'user'; + var type = '1stfactor'; + var is_success = false; + return data_store.save_authentication_trace(userid, type, is_success) + .then(function(doc) { + assert('_id' in doc); + assert.equal(doc.userid, 'user'); + assert.equal(doc.is_success, false); + assert.equal(doc.type, '1stfactor'); + return Promise.resolve(); + }); + }); + + it('should return 3 last authentication traces', function() { + var options = {}; + options.inMemoryOnly = true; + + var data_store = new UserDataStore(DataStore, options); + var userid = 'user'; + var type = '1stfactor'; + var is_success = false; + MockDate.set('2/1/2000'); + return data_store.save_authentication_trace(userid, type, false) + .then(function(doc) { + MockDate.set('1/2/2000'); + return data_store.save_authentication_trace(userid, type, true); + }) + .then(function(doc) { + MockDate.set('1/7/2000'); + return data_store.save_authentication_trace(userid, type, false); + }) + .then(function(doc) { + MockDate.set('1/2/2000'); + return data_store.save_authentication_trace(userid, type, false); + }) + .then(function(doc) { + MockDate.set('1/5/2000'); + return data_store.save_authentication_trace(userid, type, false); + }) + .then(function(doc) { + return data_store.get_last_authentication_traces(userid, type, false, 3); + }) + .then(function(docs) { + assert.equal(docs.length, 3); + assert.deepEqual(docs[0].date, new Date('2/1/2000')); + assert.deepEqual(docs[1].date, new Date('1/7/2000')); + assert.deepEqual(docs[2].date, new Date('1/5/2000')); + return Promise.resolve(); + }) + }); +} From b205ba6a0dcbf431363e7e08ee1bf118fca38e6a Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sat, 28 Jan 2017 02:33:45 +0100 Subject: [PATCH 12/19] Use a rendered html email template for identity check --- src/lib/identity_check.js | 38 ++-- src/lib/routes/reset_password.js | 1 + src/lib/routes/u2f_register_handler.js | 1 + src/public_html/js/login.js | 2 +- src/resources/email-template.ejs | 254 +++++++++++++++++++++++++ test/unitary/test_identity_check.js | 1 - 6 files changed, 281 insertions(+), 16 deletions(-) create mode 100644 src/resources/email-template.ejs diff --git a/src/lib/identity_check.js b/src/lib/identity_check.js index 96d1f9d8d..bd1550d28 100644 --- a/src/lib/identity_check.js +++ b/src/lib/identity_check.js @@ -4,9 +4,13 @@ var randomstring = require('randomstring'); var Promise = require('bluebird'); var util = require('util'); var exceptions = require('./exceptions'); +var fs = require('fs'); +var ejs = require('ejs'); module.exports = identity_check; +var filePath = __dirname + '/../resources/email-template.ejs'; +var email_template = fs.readFileSync(filePath, 'utf8'); // IdentityCheck class @@ -24,15 +28,8 @@ IdentityCheck.prototype.issue_token = function(userid, email, content, logger) { this._logger.debug('identity_check: issue identity token %s for 5 minutes', token); return this._user_data_store.issue_identity_check_token(userid, token, content, five_minutes) .then(function() { - that._logger.debug('identity_check: send email to %s', email); - return that._send_identity_check_email(email, token); - }) -} - -IdentityCheck.prototype._send_identity_check_email = function(email, token) { - var url = util.format('%s?identity_token=%s', email.hook_url, token); - var email_content = util.format('Register', url); - return this._email_sender.send(email.to, email.subject, email_content); + return Promise.resolve(token); + }); } IdentityCheck.prototype.consume_token = function(token, logger) { @@ -107,14 +104,27 @@ function identity_check_post(endpoint, icheck_interface) { throw new exceptions.IdentityError('Missing user id or email address'); } - var email = {}; - email.to = email_address; - email.subject = 'Identity Verification'; - email.hook_url = util.format('https://%s%s', req.headers.host, req.headers['x-original-uri']); - return identity_check.issue_token(userid, email, undefined, logger); + return identity_check.issue_token(userid, undefined, logger); }, function(err) { throw new exceptions.AccessDeniedError(); }) + .then(function(token) { + var original_url = util.format('https://%s%s', req.headers.host, req.headers['x-original-uri']); + var link_url = util.format('%s?identity_token=%s', original_url, token); + var email = {}; + + var d = {}; + d.url = link_url; + d.button_title = 'Continue'; + d.title = icheck_interface.email_subject; + + email.to = email_address; + email.subject = icheck_interface.email_subject; + email.content = ejs.render(email_template, d); + + logger.info('POST identity_check: send email to %s', email.to); + return email_sender.send(email.to, email.subject, email.content); + }) .then(function() { res.status(204); res.send(); diff --git a/src/lib/routes/reset_password.js b/src/lib/routes/reset_password.js index 49b32e473..006dfa1f7 100644 --- a/src/lib/routes/reset_password.js +++ b/src/lib/routes/reset_password.js @@ -9,6 +9,7 @@ var icheck_interface = { challenge: CHALLENGE, render_template: 'reset-password', pre_check_callback: pre_check, + email_subject: 'Reset your password', } module.exports = { diff --git a/src/lib/routes/u2f_register_handler.js b/src/lib/routes/u2f_register_handler.js index 0ecac0e3f..2c2600a3f 100644 --- a/src/lib/routes/u2f_register_handler.js +++ b/src/lib/routes/u2f_register_handler.js @@ -8,6 +8,7 @@ var icheck_interface = { challenge: CHALLENGE, render_template: 'u2f-register', pre_check_callback: pre_check, + email_subject: 'Register your U2F device', } module.exports = { diff --git a/src/public_html/js/login.js b/src/public_html/js/login.js index d73ba0f08..38114d62e 100644 --- a/src/public_html/js/login.js +++ b/src/public_html/js/login.js @@ -23,7 +23,7 @@ function onLoginButtonClicked() { validateFirstFactor(username, password, function(err) { if(err) { - onFirstFactorFailure(err); + onFirstFactorFailure(err.responseText); return; } onFirstFactorSuccess(); diff --git a/src/resources/email-template.ejs b/src/resources/email-template.ejs new file mode 100644 index 000000000..f29d5afc0 --- /dev/null +++ b/src/resources/email-template.ejs @@ -0,0 +1,254 @@ + + + + + + Simples-Minimalistic Responsive Template + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + +
 
+ + + + + + + +
+

<%= title %>

+
+ +
 
+
+
+ + + + + + + + +
+ + + + + + +
 
+
+ + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + +
 
+ + + + + + + + + + + + + + + + + + +
+ This email has been sent to you in order to validate your identity. Please ignore it if you do not know why you received it. +
 
+ <%= button_title %> +
+
 
+
+
+ + + + + + + + +
+ + + + + + + + + + + + +
 
 
 
+
+ + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + +
+ Please ignore this email if you did not initiate the process. +
+
+
+ + + + + diff --git a/test/unitary/test_identity_check.js b/test/unitary/test_identity_check.js index 555cb197a..7100db1ea 100644 --- a/test/unitary/test_identity_check.js +++ b/test/unitary/test_identity_check.js @@ -130,7 +130,6 @@ describe('test identity check process', function() { assert(user_data_store.issue_identity_check_token.calledOnce); assert.equal(user_data_store.issue_identity_check_token.getCall(0).args[0], 'user'); assert.equal(user_data_store.issue_identity_check_token.getCall(0).args[3], 240000); - assert(email_sender.send.getCall(0).args[2].startsWith(''); + $('#qrcode').append(img); + $("#secret").text(secret.base32); +} + +$(document).ready(function() { + generateSecret(onSecretGenerated); +}); +})(); diff --git a/src/views/login.ejs b/src/views/login.ejs index f83606825..cacd1517e 100644 --- a/src/views/login.ejs +++ b/src/views/login.ejs @@ -20,6 +20,7 @@

Time-Based One-Time Password

+

FIDO Universal 2nd Factor

diff --git a/src/views/totp-register.ejs b/src/views/totp-register.ejs new file mode 100644 index 000000000..2652459e8 --- /dev/null +++ b/src/views/totp-register.ejs @@ -0,0 +1,18 @@ + + + TOTP Registration + <% include head %> + + +
+ + + <% include scripts %> + + diff --git a/test/unitary/requests.js b/test/unitary/requests.js index dfa4e4d95..3b2b79f4d 100644 --- a/test/unitary/requests.js +++ b/test/unitary/requests.js @@ -36,6 +36,37 @@ module.exports = function(port) { }); } + function execute_register_totp(jar, transporter) { + return request.postAsync({ + url: BASE_URL + '/authentication/totp-register', + jar: jar + }) + .then(function(res) { + assert.equal(res.statusCode, 204); + var html_content = transporter.sendMail.getCall(0).args[0].html; + var regexp = /identity_token=([a-zA-Z0-9]+)/; + var token = regexp.exec(html_content)[1]; + // console.log(html_content, token); + return request.getAsync({ + url: BASE_URL + '/authentication/totp-register?identity_token=' + token, + jar: jar + }) + }) + .then(function(res) { + assert.equal(res.statusCode, 200); + return request.postAsync({ + url : BASE_URL + '/authentication/new-totp-secret', + jar: jar, + }) + }) + .then(function(res) { + console.log(res.statusCode); + console.log(res.body); + assert.equal(res.statusCode, 200); + return Promise.resolve(res.body); + }); + } + function execute_totp(jar, token) { return request.postAsync({ url: BASE_URL + '/authentication/2ndfactor/totp', @@ -136,6 +167,7 @@ module.exports = function(port) { first_factor: execute_first_factor, failing_first_factor: execute_failing_first_factor, totp: execute_totp, + register_totp: execute_register_totp, } } diff --git a/test/unitary/routes/test_totp.js b/test/unitary/routes/test_totp.js index d0c31fbab..1fd0fc4db 100644 --- a/test/unitary/routes/test_totp.js +++ b/test/unitary/routes/test_totp.js @@ -3,10 +3,12 @@ var totp = require('../../../src/lib/routes/totp'); var Promise = require('bluebird'); var sinon = require('sinon'); var assert = require('assert'); +var winston = require('winston'); describe('test totp route', function() { var req, res; var totp_engine; + var user_data_store; beforeEach(function() { var app_get = sinon.stub(); @@ -19,6 +21,7 @@ describe('test totp route', function() { }, session: { auth_session: { + userid: 'user', first_factor: false, second_factor: false } @@ -33,46 +36,52 @@ describe('test totp route', function() { totp_engine = { totp: sinon.stub() } + + user_data_store = {}; + user_data_store.get_totp_secret = sinon.stub(); + + var doc = {}; + doc.userid = 'user'; + doc.secret = {}; + doc.secret.base32 = 'ABCDEF'; + user_data_store.get_totp_secret.returns(Promise.resolve(doc)); + + app_get.withArgs('logger').returns(winston); app_get.withArgs('totp engine').returns(totp_engine); app_get.withArgs('config').returns(config); + app_get.withArgs('user data store').returns(user_data_store); }); - it('should send status code 204 when totp is valid', function() { - return new Promise(function(resolve, reject) { - totp_engine.totp.returns('abc'); - res.send = sinon.spy(function() { - // Second factor passed - assert.equal(true, req.session.auth_session.second_factor) - assert.equal(204, res.status.getCall(0).args[0]); - resolve(); - }); - totp(req, res); - }) + it('should send status code 204 when totp is valid', function(done) { + totp_engine.totp.returns('abc'); + res.send = sinon.spy(function() { + // Second factor passed + assert.equal(true, req.session.auth_session.second_factor) + assert.equal(204, res.status.getCall(0).args[0]); + done(); + }); + totp(req, res); }); - it('should send status code 401 when totp is not valid', function() { - return new Promise(function(resolve, reject) { - totp_engine.totp.returns('bad_token'); - res.send = sinon.spy(function() { - assert.equal(false, req.session.auth_session.second_factor) - assert.equal(401, res.status.getCall(0).args[0]); - resolve(); - }); - totp(req, res); - }) + it('should send status code 401 when totp is not valid', function(done) { + totp_engine.totp.returns('bad_token'); + res.send = sinon.spy(function() { + assert.equal(false, req.session.auth_session.second_factor) + assert.equal(401, res.status.getCall(0).args[0]); + done(); + }); + totp(req, res); }); - it('should send status code 401 when session has not been initiated', function() { - return new Promise(function(resolve, reject) { - totp_engine.totp.returns('abc'); - res.send = sinon.spy(function() { - assert.equal(401, res.status.getCall(0).args[0]); - resolve(); - }); - req.session = {}; - totp(req, res); - }) + it('should send status code 401 when session has not been initiated', function(done) { + totp_engine.totp.returns('abc'); + res.send = sinon.spy(function() { + assert.equal(403, res.status.getCall(0).args[0]); + done(); + }); + req.session = {}; + totp(req, res); }); }); diff --git a/test/unitary/routes/test_totp_register.js b/test/unitary/routes/test_totp_register.js new file mode 100644 index 000000000..e6eb2f7dc --- /dev/null +++ b/test/unitary/routes/test_totp_register.js @@ -0,0 +1,130 @@ +var sinon = require('sinon'); +var winston = require('winston'); +var totp_register = require('../../../src/lib/routes/totp_register'); +var assert = require('assert'); +var Promise = require('bluebird'); + +describe('test totp register', function() { + var req, res; + var user_data_store; + + beforeEach(function() { + req = {} + req.app = {}; + req.app.get = sinon.stub(); + req.app.get.withArgs('logger').returns(winston); + req.session = {}; + req.session.auth_session = {}; + req.session.auth_session.userid = 'user'; + req.session.auth_session.email = 'user@example.com'; + req.session.auth_session.first_factor = true; + req.session.auth_session.second_factor = false; + req.headers = {}; + req.headers.host = 'localhost'; + + var options = {}; + options.inMemoryOnly = true; + + user_data_store = {}; + user_data_store.set_u2f_meta = sinon.stub().returns(Promise.resolve({})); + user_data_store.get_u2f_meta = sinon.stub().returns(Promise.resolve({})); + user_data_store.issue_identity_check_token = sinon.stub().returns(Promise.resolve({})); + user_data_store.consume_identity_check_token = sinon.stub().returns(Promise.resolve({})); + user_data_store.set_totp_secret = sinon.stub().returns(Promise.resolve({})); + req.app.get.withArgs('user data store').returns(user_data_store); + + res = {}; + res.send = sinon.spy(); + res.json = sinon.spy(); + res.status = sinon.spy(); + }); + + describe('test totp registration check', test_registration_check); + describe('test totp post secret', test_post_secret); + + function test_registration_check() { + it('should fail if first_factor has not been passed', function(done) { + req.session.auth_session.first_factor = false; + totp_register.icheck_interface.pre_check_callback(req) + .catch(function(err) { + done(); + }); + }); + + it('should fail if userid is missing', function(done) { + req.session.auth_session.first_factor = false; + req.session.auth_session.userid = undefined; + + totp_register.icheck_interface.pre_check_callback(req) + .catch(function(err) { + done(); + }); + }); + + it('should fail if email is missing', function(done) { + req.session.auth_session.first_factor = false; + req.session.auth_session.email = undefined; + + totp_register.icheck_interface.pre_check_callback(req) + .catch(function(err) { + done(); + }); + }); + + it('should succeed if first factor passed, userid and email are provided', function(done) { + totp_register.icheck_interface.pre_check_callback(req) + .then(function(err) { + done(); + }); + }); + } + + function test_post_secret() { + it('should send the secret in json format', function(done) { + req.app.get.withArgs('totp engine').returns(require('speakeasy')); + req.session.auth_session.identity_check = {}; + req.session.auth_session.identity_check.userid = 'user'; + req.session.auth_session.identity_check.challenge = 'totp-register'; + res.json = sinon.spy(function() { + done(); + }); + totp_register.post(req, res); + }); + + it('should clear the session for reauthentication', function(done) { + req.app.get.withArgs('totp engine').returns(require('speakeasy')); + req.session.auth_session.identity_check = {}; + req.session.auth_session.identity_check.userid = 'user'; + req.session.auth_session.identity_check.challenge = 'totp-register'; + res.json = sinon.spy(function() { + assert.equal(req.session, undefined); + done(); + }); + totp_register.post(req, res); + }); + + it('should return 403 if the identity check challenge is not set', function(done) { + req.session.auth_session.identity_check = {}; + req.session.auth_session.identity_check.challenge = undefined; + res.send = sinon.spy(function() { + assert.equal(res.status.getCall(0).args[0], 403); + done(); + }); + totp_register.post(req, res); + }); + + it('should return 500 if db throws', function(done) { + req.app.get.withArgs('totp engine').returns(require('speakeasy')); + req.session.auth_session.identity_check = {}; + req.session.auth_session.identity_check.userid = 'user'; + req.session.auth_session.identity_check.challenge = 'totp-register'; + user_data_store.set_totp_secret.throws('internal error'); + + res.send = sinon.spy(function() { + assert.equal(res.status.getCall(0).args[0], 500); + done(); + }); + totp_register.post(req, res); + }); +  } +}); diff --git a/test/unitary/test_server.js b/test/unitary/test_server.js index f6acfd73a..502c2930b 100644 --- a/test/unitary/test_server.js +++ b/test/unitary/test_server.js @@ -219,10 +219,6 @@ describe('test the server', function() { }); it('should return status code 204 when user is authenticated using totp', function() { - var real_token = speakeasy.totp({ - secret: 'totp_secret', - encoding: 'base32' - }); var j = request.jar(); return requests.login(j) .then(function(res) { @@ -231,6 +227,14 @@ describe('test the server', function() { }) .then(function(res) { assert.equal(res.statusCode, 204, 'first factor failed'); + return requests.register_totp(j, transporter); + }) + .then(function(secret) { + var sec = JSON.parse(secret); + var real_token = speakeasy.totp({ + secret: sec.base32, + encoding: 'base32' + }); return requests.totp(j, real_token); }) .then(function(res) { diff --git a/test/unitary/user_data_store/test_totp_secret.js b/test/unitary/user_data_store/test_totp_secret.js new file mode 100644 index 000000000..f08e4fff6 --- /dev/null +++ b/test/unitary/user_data_store/test_totp_secret.js @@ -0,0 +1,65 @@ + +var assert = require('assert'); +var Promise = require('bluebird'); +var sinon = require('sinon'); +var MockDate = require('mockdate'); +var UserDataStore = require('../../../src/lib/user_data_store'); +var DataStore = require('nedb'); + +describe('test user data store', function() { + describe('test totp secrets store', test_totp_secrets); +}); + +function test_totp_secrets() { + it('should save and reload a totp secret', function() { + var options = {}; + options.inMemoryOnly = true; + + var data_store = new UserDataStore(DataStore, options); + var userid = 'user'; + var secret = {}; + secret.ascii = 'abc'; + secret.base32 = 'ABCDKZLEFZGREJK'; + + return data_store.set_totp_secret(userid, secret) + .then(function() { + return data_store.get_totp_secret(userid); + }) + .then(function(doc) { + assert('_id' in doc); + assert.equal(doc.userid, 'user'); + assert.equal(doc.secret.ascii, 'abc'); + assert.equal(doc.secret.base32, 'ABCDKZLEFZGREJK'); + return Promise.resolve(); + }); + }); + + it('should only remember last secret', function() { + var options = {}; + options.inMemoryOnly = true; + + var data_store = new UserDataStore(DataStore, options); + var userid = 'user'; + var secret1 = {}; + secret1.ascii = 'abc'; + secret1.base32 = 'ABCDKZLEFZGREJK'; + var secret2 = {}; + secret2.ascii = 'def'; + secret2.base32 = 'XYZABC'; + + return data_store.set_totp_secret(userid, secret1) + .then(function() { + return data_store.set_totp_secret(userid, secret2) + }) + .then(function() { + return data_store.get_totp_secret(userid); + }) + .then(function(doc) { + assert('_id' in doc); + assert.equal(doc.userid, 'user'); + assert.equal(doc.secret.ascii, 'def'); + assert.equal(doc.secret.base32, 'XYZABC'); + return Promise.resolve(); + }); + }); +} From 7e41c68aa7b03952fc7dd311b3b361f503899f81 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sat, 28 Jan 2017 18:30:07 +0100 Subject: [PATCH 14/19] Remove TOTP password from the configuration --- config.template.yml | 3 --- src/index.js | 1 - 2 files changed, 4 deletions(-) diff --git a/config.template.yml b/config.template.yml index fd6a5a347..8c58243a1 100644 --- a/config.template.yml +++ b/config.template.yml @@ -7,9 +7,6 @@ ldap: user: cn=admin,dc=example,dc=com password: password -# Will be per user soon -totp_secret: GRWGIJS6IRHVEODVNRCXCOBMJ5AGC6ZE - session: secret: unsecure_secret expiration: 3600000 diff --git a/src/index.js b/src/index.js index 14edc7281..8755e490e 100644 --- a/src/index.js +++ b/src/index.js @@ -16,7 +16,6 @@ var yaml_config = YAML.load(config_path); var config = { port: process.env.PORT || 8080, - totp_secret: yaml_config.totp_secret, ldap_url: yaml_config.ldap.url || 'ldap://127.0.0.1:389', ldap_users_dn: yaml_config.ldap.base_dn, ldap_user: yaml_config.ldap.user, From d29aac78d05b0828bf85dae9b293873cc6ef5126 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sat, 28 Jan 2017 19:59:15 +0100 Subject: [PATCH 15/19] Create a filesystem notifier for simple getting started --- config.template.yml | 23 +++++++++++---- docker-compose.dev.yml | 1 + notifications/notification.txt | 3 ++ src/index.js | 9 +++--- src/lib/email_sender.js | 25 ----------------- src/lib/identity_check.js | 34 ++++++++--------------- src/lib/notifier.js | 24 ++++++++++++++++ src/lib/notifiers/filesystem.js | 16 +++++++++++ src/lib/notifiers/gmail.js | 33 ++++++++++++++++++++++ src/lib/server.js | 14 +++++----- test/unitary/notifiers/test_fs.js | 37 +++++++++++++++++++++++++ test/unitary/notifiers/test_gmail.js | 36 ++++++++++++++++++++++++ test/unitary/notifiers/test_notifier.js | 35 +++++++++++++++++++++++ test/unitary/test_data_persistence.js | 2 +- test/unitary/test_email_sender.js | 31 --------------------- test/unitary/test_identity_check.js | 11 ++++---- test/unitary/test_server.js | 8 ++++-- 17 files changed, 237 insertions(+), 105 deletions(-) create mode 100644 notifications/notification.txt delete mode 100644 src/lib/email_sender.js create mode 100644 src/lib/notifier.js create mode 100644 src/lib/notifiers/filesystem.js create mode 100644 src/lib/notifiers/gmail.js create mode 100644 test/unitary/notifiers/test_fs.js create mode 100644 test/unitary/notifiers/test_gmail.js create mode 100644 test/unitary/notifiers/test_notifier.js delete mode 100644 test/unitary/test_email_sender.js diff --git a/config.template.yml b/config.template.yml index 8c58243a1..d514ba316 100644 --- a/config.template.yml +++ b/config.template.yml @@ -1,20 +1,33 @@ -debug_level: info +### Level of verbosity for logs +logs_level: info +### Configuration of your LDAP ldap: url: ldap://ldap base_dn: ou=users,dc=example,dc=com user: cn=admin,dc=example,dc=com password: password +### Configuration of session cookies session: secret: unsecure_secret expiration: 3600000 -store_directory: /var/lib/auth-server +### The directory where the DB files will be saved +store_directory: /var/lib/auth-server/store + +### Notifications are sent to users when they require a password reset, a u2f +### registration or a TOTP registration. +### Use only one available configuration: filesystem, gmail notifier: - gmail: - username: user@example.com - password: yourpassword + ### For testing purpose, notifications can be sent in a file + filesystem: + filename: /var/lib/auth-server/notifications/notification.txt + + ### Use your gmail account to send the notifications. You can use an app password. + # gmail: + # username: user@example.com + # password: yourpassword diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 2352c93fc..980ec7c77 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -6,6 +6,7 @@ services: - ./test:/usr/src/test - ./src/views:/usr/src/views - ./src/public_html:/usr/src/public_html + - ./notifications:/var/lib/auth-server/notifications ldap-admin: image: osixia/phpldapadmin:0.6.11 diff --git a/notifications/notification.txt b/notifications/notification.txt new file mode 100644 index 000000000..19ff68363 --- /dev/null +++ b/notifications/notification.txt @@ -0,0 +1,3 @@ +User: user +Subject: Reset your password +Link: https://localhost:8080/authentication/reset-password?identity_token=CmJ51IdJLEcVr7AbbJPANe0wmJoOcgYzPqgGOngVRIhKq1UbQUoS44FXDEXBcolz \ No newline at end of file diff --git a/src/index.js b/src/index.js index 8755e490e..4717ff2ec 100644 --- a/src/index.js +++ b/src/index.js @@ -23,13 +23,12 @@ var config = { session_secret: yaml_config.session.secret, session_max_age: yaml_config.session.expiration || 3600000, // in ms store_directory: yaml_config.store_directory, - debug_level: yaml_config.debug_level, - gmail: { - user: yaml_config.notifier.gmail.username, - pass: yaml_config.notifier.gmail.password - } + logs_level: yaml_config.logs_level, + notifier: yaml_config.notifier, } +console.log(config); + var ldap_client = ldap.createClient({ url: config.ldap_url, reconnect: true diff --git a/src/lib/email_sender.js b/src/lib/email_sender.js deleted file mode 100644 index 8f25e841c..000000000 --- a/src/lib/email_sender.js +++ /dev/null @@ -1,25 +0,0 @@ - -module.exports = EmailSender; - -var Promise = require('bluebird'); - -function EmailSender(nodemailer, options) { - var transporter = nodemailer.createTransport({ - service: 'gmail', - auth: { - user: options.gmail.user, - pass: options.gmail.pass - } - }); - this.transporter = Promise.promisifyAll(transporter); -} - -EmailSender.prototype.send = function(to, subject, html) { - var mailOptions = {}; - mailOptions.from = 'auth-server@open-intent.io'; - mailOptions.to = to; - mailOptions.subject = subject; - mailOptions.html = html; - return this.transporter.sendMailAsync(mailOptions); -} - diff --git a/src/lib/identity_check.js b/src/lib/identity_check.js index bd1550d28..1b37c28b1 100644 --- a/src/lib/identity_check.js +++ b/src/lib/identity_check.js @@ -14,13 +14,12 @@ var email_template = fs.readFileSync(filePath, 'utf8'); // IdentityCheck class -function IdentityCheck(user_data_store, email_sender, logger) { +function IdentityCheck(user_data_store, logger) { this._user_data_store = user_data_store; - this._email_sender = email_sender; this._logger = logger; } -IdentityCheck.prototype.issue_token = function(userid, email, content, logger) { +IdentityCheck.prototype.issue_token = function(userid, content, logger) { var five_minutes = 4 * 60 * 1000; var token = randomstring.generate({ length: 64 }); var that = this; @@ -61,7 +60,7 @@ function identity_check_get(endpoint, icheck_interface) { var email_sender = req.app.get('email sender'); var user_data_store = req.app.get('user data store'); - var identity_check = new IdentityCheck(user_data_store, email_sender, logger); + var identity_check = new IdentityCheck(user_data_store, logger); identity_check.consume_token(identity_token, logger) .then(function(content) { @@ -90,15 +89,16 @@ function identity_check_get(endpoint, icheck_interface) { function identity_check_post(endpoint, icheck_interface) { return function(req, res) { var logger = req.app.get('logger'); - var email_sender = req.app.get('email sender'); + var notifier = req.app.get('notifier'); var user_data_store = req.app.get('user data store'); - var identity_check = new IdentityCheck(user_data_store, email_sender, logger); - var userid, email_address; + var identity_check = new IdentityCheck(user_data_store, logger); + var identity; icheck_interface.pre_check_callback(req) - .then(function(identity) { - email_address = objectPath.get(identity, 'email'); - userid = objectPath.get(identity, 'userid'); + .then(function(id) { + identity = id; + var email_address = objectPath.get(identity, 'email'); + var userid = objectPath.get(identity, 'userid'); if(!(email_address && userid)) { throw new exceptions.IdentityError('Missing user id or email address'); @@ -111,19 +111,9 @@ function identity_check_post(endpoint, icheck_interface) { .then(function(token) { var original_url = util.format('https://%s%s', req.headers.host, req.headers['x-original-uri']); var link_url = util.format('%s?identity_token=%s', original_url, token); - var email = {}; - var d = {}; - d.url = link_url; - d.button_title = 'Continue'; - d.title = icheck_interface.email_subject; - - email.to = email_address; - email.subject = icheck_interface.email_subject; - email.content = ejs.render(email_template, d); - - logger.info('POST identity_check: send email to %s', email.to); - return email_sender.send(email.to, email.subject, email.content); + logger.info('POST identity_check: notify to %s', identity.userid); + return notifier.notify(identity, icheck_interface.email_subject, link_url); }) .then(function() { res.status(204); diff --git a/src/lib/notifier.js b/src/lib/notifier.js new file mode 100644 index 000000000..84ba60600 --- /dev/null +++ b/src/lib/notifier.js @@ -0,0 +1,24 @@ + +module.exports = Notifier; + +var GmailNotifier = require('./notifiers/gmail.js'); +var FSNotifier = require('./notifiers/filesystem.js'); + +function notifier_factory(options, deps) { + if('gmail' in options) { + return new GmailNotifier(options.gmail, deps); + } + else if('filesystem' in options) { + return new FSNotifier(options.filesystem); + } +} + +function Notifier(options, deps) { + this._notifier = notifier_factory(options, deps); +} + +Notifier.prototype.notify = function(identity, subject, link) { + return this._notifier.notify(identity, subject, link); +} + + diff --git a/src/lib/notifiers/filesystem.js b/src/lib/notifiers/filesystem.js new file mode 100644 index 000000000..a4a295ce2 --- /dev/null +++ b/src/lib/notifiers/filesystem.js @@ -0,0 +1,16 @@ +module.exports = FSNotifier; + +var Promise = require('bluebird'); +var fs = Promise.promisifyAll(require('fs')); +var util = require('util'); + +function FSNotifier(options) { + this._filename = options.filename; +} + +FSNotifier.prototype.notify = function(identity, subject, link) { + var content = util.format('User: %s\nSubject: %s\nLink: %s', identity.userid, + subject, link); + return fs.writeFileAsync(this._filename, content); +} + diff --git a/src/lib/notifiers/gmail.js b/src/lib/notifiers/gmail.js new file mode 100644 index 000000000..5007d858c --- /dev/null +++ b/src/lib/notifiers/gmail.js @@ -0,0 +1,33 @@ +module.exports = GmailNotifier; + +var Promise = require('bluebird'); +var fs = require('fs'); +var ejs = require('ejs'); + +var email_template = fs.readFileSync(__dirname + '/../../resources/email-template.ejs', 'UTF-8'); + +function GmailNotifier(options, deps) { + var transporter = deps.nodemailer.createTransport({ + service: 'gmail', + auth: { + user: options.username, + pass: options.password + } + }); + this.transporter = Promise.promisifyAll(transporter); +} + +GmailNotifier.prototype.notify = function(identity, subject, link) { + var d = {}; + d.url = link; + d.button_title = 'Continue'; + d.title = subject; + + var mailOptions = {}; + mailOptions.from = 'auth-server@open-intent.io'; + mailOptions.to = identity.email; + mailOptions.subject = subject; + mailOptions.html = ejs.render(email_template, d); + return this.transporter.sendMailAsync(mailOptions); +} + diff --git a/src/lib/server.js b/src/lib/server.js index 84e8187aa..399ab1449 100644 --- a/src/lib/server.js +++ b/src/lib/server.js @@ -12,7 +12,7 @@ var path = require('path'); var session = require('express-session'); var winston = require('winston'); var UserDataStore = require('./user_data_store'); -var EmailSender = require('./email_sender'); +var Notifier = require('./notifier'); var AuthenticationRegulator = require('./authentication_regulator'); var identity_check = require('./identity_check'); @@ -24,9 +24,6 @@ function run(config, ldap_client, deps, fn) { if(config.store_in_memory) datastore_options.inMemory = true; - var email_options = {}; - email_options.gmail = config.gmail; - var app = express(); app.use(express.static(public_html_directory)); app.use(bodyParser.urlencoded({ extended: false })); @@ -46,12 +43,13 @@ function run(config, ldap_client, deps, fn) { app.set('views', view_directory); app.set('view engine', 'ejs'); - winston.level = config.debug_level || 'info'; + // by default the level of logs is info + winston.level = config.logs_level || 'info'; var five_minutes = 5 * 60; var data_store = new UserDataStore(deps.nedb, datastore_options); var regulator = new AuthenticationRegulator(data_store, five_minutes); - var notifier = new EmailSender(deps.nodemailer, email_options); + var notifier = new Notifier(config.notifier, deps); app.set('logger', winston); app.set('ldap', deps.ldap); @@ -59,7 +57,7 @@ function run(config, ldap_client, deps, fn) { app.set('totp engine', speakeasy); app.set('u2f', deps.u2f); app.set('user data store', data_store); - app.set('email sender', notifier); + app.set('notifier', notifier); app.set('authentication regulator', regulator); app.set('config', config); @@ -88,9 +86,11 @@ function run(config, ldap_client, deps, fn) { app.post (base_endpoint + '/1stfactor', routes.first_factor); app.post (base_endpoint + '/2ndfactor/totp', routes.second_factor.totp); + // U2F registration app.get (base_endpoint + '/2ndfactor/u2f/register_request', routes.second_factor.u2f.register_request); app.post (base_endpoint + '/2ndfactor/u2f/register', routes.second_factor.u2f.register); + // U2F authentication app.get (base_endpoint + '/2ndfactor/u2f/sign_request', routes.second_factor.u2f.sign_request); app.post (base_endpoint + '/2ndfactor/u2f/sign', routes.second_factor.u2f.sign); diff --git a/test/unitary/notifiers/test_fs.js b/test/unitary/notifiers/test_fs.js new file mode 100644 index 000000000..60b16b8ec --- /dev/null +++ b/test/unitary/notifiers/test_fs.js @@ -0,0 +1,37 @@ +var sinon = require('sinon'); +var assert = require('assert'); +var FSNotifier = require('../../../src/lib/notifiers/filesystem'); +var tmp = require('tmp'); +var fs = require('fs'); + +describe('test FS notifier', function() { + var tmpDir; + before(function() { + tmpDir = tmp.dirSync({ unsafeCleanup: true }); + }); + + after(function() { + tmpDir.removeCallback(); + }); + + it('should write the notification in a file', function() { + var options = {}; + options.filename = tmpDir.name + '/notification'; + + var sender = new FSNotifier(options); + var subject = 'subject'; + + var identity = {}; + identity.userid = 'user'; + identity.email = 'user@example.com'; + + var url = 'http://test.com'; + + return sender.notify(identity, subject, url) + .then(function() { + var content = fs.readFileSync(options.filename, 'UTF-8'); + assert(content.length > 0); + return Promise.resolve(); + }); + }); +}); diff --git a/test/unitary/notifiers/test_gmail.js b/test/unitary/notifiers/test_gmail.js new file mode 100644 index 000000000..bc65967a0 --- /dev/null +++ b/test/unitary/notifiers/test_gmail.js @@ -0,0 +1,36 @@ +var sinon = require('sinon'); +var assert = require('assert'); +var GmailNotifier = require('../../../src/lib/notifiers/gmail'); + +describe('test gmail notifier', function() { + it('should send an email', function() { + var nodemailer = {}; + var transporter = {}; + nodemailer.createTransport = sinon.stub().returns(transporter); + transporter.sendMail = sinon.stub().yields(); + var options = {}; + options.username = 'user_gmail'; + options.password = 'pass_gmail'; + + var deps = {}; + deps.nodemailer = nodemailer; + + var sender = new GmailNotifier(options, deps); + var subject = 'subject'; + + var identity = {}; + identity.userid = 'user'; + identity.email = 'user@example.com'; + + var url = 'http://test.com'; + + return sender.notify(identity, subject, url) + .then(function() { + assert.equal(nodemailer.createTransport.getCall(0).args[0].auth.user, 'user_gmail'); + assert.equal(nodemailer.createTransport.getCall(0).args[0].auth.pass, 'pass_gmail'); + assert.equal(transporter.sendMail.getCall(0).args[0].to, 'user@example.com'); + assert.equal(transporter.sendMail.getCall(0).args[0].subject, 'subject'); + return Promise.resolve(); + }); + }); +}); diff --git a/test/unitary/notifiers/test_notifier.js b/test/unitary/notifiers/test_notifier.js new file mode 100644 index 000000000..efa4413db --- /dev/null +++ b/test/unitary/notifiers/test_notifier.js @@ -0,0 +1,35 @@ + +var sinon = require('sinon'); +var Promise = require('bluebird'); +var assert = require('assert'); + +var Notifier = require('../../../src/lib/notifier'); +var GmailNotifier = require('../../../src/lib/notifiers/gmail'); +var FSNotifier = require('../../../src/lib/notifiers/filesystem'); + +describe('test notifier', function() { + it('should build a Gmail Notifier', function() { + var deps = {}; + deps.nodemailer = {}; + deps.nodemailer.createTransport = sinon.stub().returns({}); + + var options = {}; + options.gmail = {}; + options.gmail.user = 'abc'; + options.gmail.pass = 'abcd'; + + var notifier = new Notifier(options, deps); + assert(notifier._notifier instanceof GmailNotifier); + }); + + it('should build a FS Notifier', function() { + var deps = {}; + + var options = {}; + options.filesystem = {}; + options.filesystem.filename = 'abc'; + + var notifier = new Notifier(options, deps); + assert(notifier._notifier instanceof FSNotifier); + }); +}); diff --git a/test/unitary/test_data_persistence.js b/test/unitary/test_data_persistence.js index 072f9c361..202641040 100644 --- a/test/unitary/test_data_persistence.js +++ b/test/unitary/test_data_persistence.js @@ -57,7 +57,7 @@ describe('test data persistence', function() { session_secret: 'session_secret', session_max_age: 50000, store_directory: tmpDir.name, - gmail: { user: 'user@example.com', pass: 'password' } + notifier: { gmail: { user: 'user@example.com', pass: 'password' } } }; }); diff --git a/test/unitary/test_email_sender.js b/test/unitary/test_email_sender.js deleted file mode 100644 index 9328d674d..000000000 --- a/test/unitary/test_email_sender.js +++ /dev/null @@ -1,31 +0,0 @@ - - -var sinon = require('sinon'); -var assert = require('assert'); -var EmailSender = require('../../src/lib/email_sender'); - -describe('test email sender', function() { - it('should send an email', function() { - var nodemailer = {}; - var transporter = {}; - nodemailer.createTransport = sinon.stub().returns(transporter); - transporter.sendMail = sinon.stub().yields(); - var options = {}; - options.gmail = {}; - options.gmail.user = 'test@gmail.com'; - options.gmail.pass = 'test@gmail.com'; - - var sender = new EmailSender(nodemailer, options); - var to = 'example@gmail.com'; - var subject = 'subject'; - var content = 'content'; - - return sender.send(to, subject, content) - .then(function() { - assert.equal(to, transporter.sendMail.getCall(0).args[0].to); - assert.equal(subject, transporter.sendMail.getCall(0).args[0].subject); - assert.equal(content, transporter.sendMail.getCall(0).args[0].html); - return Promise.resolve(); - }); - }); -}); diff --git a/test/unitary/test_identity_check.js b/test/unitary/test_identity_check.js index 7100db1ea..52e890dd1 100644 --- a/test/unitary/test_identity_check.js +++ b/test/unitary/test_identity_check.js @@ -9,7 +9,7 @@ var Promise = require('bluebird'); describe('test identity check process', function() { var req, res, app, icheck_interface; var user_data_store; - var email_sender; + var notifier; beforeEach(function() { req = {}; @@ -25,9 +25,8 @@ describe('test identity check process', function() { user_data_store.consume_identity_check_token = sinon.stub(); user_data_store.consume_identity_check_token.returns(Promise.resolve({ userid: 'user' })); - email_sender = {}; - email_sender.send = sinon.stub(); - email_sender.send = sinon.stub().returns(Promise.resolve()); + notifier = {}; + notifier.notify = sinon.stub().returns(Promise.resolve()); req.headers = {}; req.session = {}; @@ -38,7 +37,7 @@ describe('test identity check process', function() { req.app.get = sinon.stub(); req.app.get.withArgs('logger').returns(winston); req.app.get.withArgs('user data store').returns(user_data_store); - req.app.get.withArgs('email sender').returns(email_sender); + req.app.get.withArgs('notifier').returns(notifier); res.status = sinon.spy(); res.send = sinon.spy(); @@ -126,7 +125,7 @@ describe('test identity check process', function() { res.send = sinon.spy(function() { assert.equal(res.status.getCall(0).args[0], 204); - assert(email_sender.send.calledOnce); + assert(notifier.notify.calledOnce); assert(user_data_store.issue_identity_check_token.calledOnce); assert.equal(user_data_store.issue_identity_check_token.getCall(0).args[0], 'user'); assert.equal(user_data_store.issue_identity_check_token.getCall(0).args[3], 240000); diff --git a/test/unitary/test_server.js b/test/unitary/test_server.js index 502c2930b..5e2160d52 100644 --- a/test/unitary/test_server.js +++ b/test/unitary/test_server.js @@ -38,9 +38,11 @@ describe('test the server', function() { session_secret: 'session_secret', session_max_age: 50000, store_in_memory: true, - gmail: { - user: 'user@example.com', - pass: 'password' + notifier: { + gmail: { + user: 'user@example.com', + pass: 'password' + } } }; From 5be5b34522f3d3268bd425589dbad632d9da0101 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sat, 28 Jan 2017 20:13:56 +0100 Subject: [PATCH 16/19] Remove temporarily integration tests --- .travis.yml | 2 +- package.json | 1 - test/integration/test_server.js | 168 -------------------------------- 3 files changed, 1 insertion(+), 170 deletions(-) delete mode 100644 test/integration/test_server.js diff --git a/.travis.yml b/.travis.yml index b5f3617cf..4fcbf7cfd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ script: - docker-compose build - docker-compose up -d - sleep 5 -- npm run-script int-test +- scripts/check_services.sh deploy: provider: npm email: clement.michaud34@gmail.com diff --git a/package.json b/package.json index 805417acf..ef6461ae2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "scripts": { "test": "./node_modules/.bin/mocha --recursive test/unitary", "unit-test": "./node_modules/.bin/mocha --recursive test/unitary", - "int-test": "./node_modules/.bin/mocha --recursive test/integration", "all-test": "./node_modules/.bin/mocha --recursive test", "coverage": "./node_modules/.bin/istanbul cover _mocha -- -R spec --recursive test" }, diff --git a/test/integration/test_server.js b/test/integration/test_server.js deleted file mode 100644 index 69df38270..000000000 --- a/test/integration/test_server.js +++ /dev/null @@ -1,168 +0,0 @@ - -var request_ = require('request'); -var assert = require('assert'); -var speakeasy = require('speakeasy'); -var j = request_.jar(); -var Promise = require('bluebird'); -var request = Promise.promisifyAll(request_.defaults({jar: j})); - -var BASE_URL = 'https://localhost:8080'; - -process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; - -describe('test the server', function() { - var home_page; - var login_page; - - before(function() { - var home_page_promise = getHomePage() - .then(function(data) { - home_page = data.body; - }); - var login_page_promise = getLoginPage() - .then(function(data) { - login_page = data.body; - }); - return Promise.all([home_page_promise, - login_page_promise]); - }); - - it('should serve the login page', function(done) { - getPromised(BASE_URL + '/authentication/login?redirect=/') - .then(function(data) { - assert.equal(data.statusCode, 200); - done(); - }); - }); - - it('should serve the homepage', function(done) { - getPromised(BASE_URL + '/') - .then(function(data) { - assert.equal(data.statusCode, 200); - done(); - }); - }); - - it('should redirect when logout', function(done) { - getPromised(BASE_URL + '/authentication/logout?redirect=/') - .then(function(data) { - assert.equal(data.statusCode, 200); - assert.equal(data.body, home_page); - done(); - }); - }); - - it('should be redirected to the login page when accessing secret while not authenticated', function() { - return getPromised(BASE_URL + '/secret.html') - .then(function(data) { - assert.equal(data.statusCode, 200); - assert.equal(data.body, login_page); - return Promise.resolve(); - }); - }); - - it('should fail the first_factor login', function() { - return postPromised(BASE_URL + '/authentication/1stfactor', { - form: { - username: 'user', - password: 'bad_password' - } - }) - .then(function(data) { - assert.equal(data.statusCode, 401); - return Promise.resolve(); - }); - }); - - it('should login and access the secret using totp', function() { - var token = speakeasy.totp({ - secret: 'GRWGIJS6IRHVEODVNRCXCOBMJ5AGC6ZE', - encoding: 'base32' - }); - - return postPromised(BASE_URL + '/authentication/1stfactor', { - form: { - username: 'user', - password: 'password', - } - }) - .then(function(response) { - assert.equal(response.statusCode, 204); - return postPromised(BASE_URL + '/authentication/2ndfactor/totp', { - form: { token: token } - }); - }) - .then(function(response) { - assert.equal(response.statusCode, 204); - return getPromised(BASE_URL + '/secret.html'); - }) - .then(function(response) { - var content = response.body; - var is_secret_page_content = - (content.indexOf('This is a very important secret!') > -1); - assert(is_secret_page_content); - return Promise.resolve(); - }) - .catch(function(err) { - console.error(err); - return Promise.reject(err); - }); - }); - - it('should logoff and should not be able to access secret anymore', function() { - return getPromised(BASE_URL + '/secret.html') - .then(function(data) { - var content = data.body; - var is_secret_page_content = - (content.indexOf('This is a very important secret!') > -1); - assert(is_secret_page_content); - return getPromised(BASE_URL + '/authentication/logout') - }) - .then(function(data) { - assert.equal(data.statusCode, 200); - assert.equal(data.body, home_page); - return getPromised(BASE_URL + '/secret.html'); - }) - .then(function(data) { - var content = data.body; - assert.equal(data.body, login_page); - return Promise.resolve(); - }) - .catch(function(err) { - console.error(err); - return Promise.reject(); - }); - }); -}); - -function responsePromised(defer) { - return function(error, response, body) { - if(error) { - console.error(error); - defer.reject(error); - return; - } - defer.resolve({ - response: response, - body: body - }); - } -} - -function getPromised(url) { - console.log('GET: %s', url); - return request.getAsync(url); -} - -function postPromised(url, body) { - console.log('POST: %s, %s', url, JSON.stringify(body)); - return request.postAsync(url, body); -} - -function getHomePage() { - return getPromised(BASE_URL + '/'); -} - -function getLoginPage() { - return getPromised(BASE_URL + '/authentication/login'); -} From 8d662c15916e69baff4c0aec623b9bc135debb7b Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sat, 28 Jan 2017 20:21:30 +0100 Subject: [PATCH 17/19] Install libgif-dev in travisci environment --- .gitignore | 2 ++ .travis.yml | 6 +++++- docker-compose.dev.yml | 2 +- docker-compose.yml | 3 ++- notifications/notification.txt | 3 --- scripts/check_services.sh | 14 ++++++++++++++ 6 files changed, 24 insertions(+), 6 deletions(-) delete mode 100644 notifications/notification.txt create mode 100755 scripts/check_services.sh diff --git a/.gitignore b/.gitignore index 7a4b1e5aa..328b13d38 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ coverage/ config.yml npm-debug.log + +notifications/ diff --git a/.travis.yml b/.travis.yml index 4fcbf7cfd..4e150744b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,17 @@ node_js: services: - docker - ntp +addons: + apt: + packages: + - libgif-dev before_install: npm install -g npm@'>=2.13.5' script: - npm test - docker-compose build - docker-compose up -d - sleep 5 -- scripts/check_services.sh +- ./scripts/check_services.sh deploy: provider: npm email: clement.michaud34@gmail.com diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 980ec7c77..bbdd7bc4d 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -6,7 +6,7 @@ services: - ./test:/usr/src/test - ./src/views:/usr/src/views - ./src/public_html:/usr/src/public_html - - ./notifications:/var/lib/auth-server/notifications + - ./config.yml:/etc/auth-server/config.yml:ro ldap-admin: image: osixia/phpldapadmin:0.6.11 diff --git a/docker-compose.yml b/docker-compose.yml index 8f62215fd..bf30b905e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,8 @@ services: - ldap restart: always volumes: - - ./config.yml:/etc/auth-server/config.yml:ro + - ./config.template.yml:/etc/auth-server/config.yml:ro + - ./notifications:/var/lib/auth-server/notifications ldap: image: dinkel/openldap diff --git a/notifications/notification.txt b/notifications/notification.txt deleted file mode 100644 index 19ff68363..000000000 --- a/notifications/notification.txt +++ /dev/null @@ -1,3 +0,0 @@ -User: user -Subject: Reset your password -Link: https://localhost:8080/authentication/reset-password?identity_token=CmJ51IdJLEcVr7AbbJPANe0wmJoOcgYzPqgGOngVRIhKq1UbQUoS44FXDEXBcolz \ No newline at end of file diff --git a/scripts/check_services.sh b/scripts/check_services.sh new file mode 100755 index 000000000..773ffca5d --- /dev/null +++ b/scripts/check_services.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +service_count=`docker ps -a | grep "Up " | wc -l` + +if [ "${service_count}" -eq "3" ] +then + echo "Service are up and running." + exit 0 +else + echo "Some services exited..." + docker ps -a + exit 1 +fi + From 2cc854b968036663cfd5947502ef8c14d744a169 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sun, 29 Jan 2017 01:33:48 +0100 Subject: [PATCH 18/19] Adding ApiDoc documentation to the repository --- doc/api_data.js | 1031 ++++++++++++++++++ doc/api_data.json | 1031 ++++++++++++++++++ doc/api_project.js | 15 + doc/api_project.json | 15 + doc/css/style.css | 568 ++++++++++ doc/fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes doc/fonts/glyphicons-halflings-regular.svg | 288 +++++ doc/fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes doc/fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes doc/fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes doc/img/favicon.ico | Bin 0 -> 894 bytes doc/index.html | 669 ++++++++++++ doc/locales/ca.js | 25 + doc/locales/de.js | 25 + doc/locales/es.js | 25 + doc/locales/fr.js | 25 + doc/locales/it.js | 25 + doc/locales/locale.js | 48 + doc/locales/nl.js | 25 + doc/locales/pl.js | 25 + doc/locales/pt_br.js | 25 + doc/locales/ro.js | 25 + doc/locales/ru.js | 25 + doc/locales/zh.js | 25 + doc/locales/zh_cn.js | 25 + doc/main.js | 827 ++++++++++++++ doc/utils/handlebars_helper.js | 357 ++++++ doc/utils/send_sample_request.js | 182 ++++ doc/vendor/bootstrap.min.css | 6 + doc/vendor/bootstrap.min.js | 7 + doc/vendor/diff_match_patch.min.js | 49 + doc/vendor/handlebars.min.js | 29 + doc/vendor/jquery.min.js | 4 + doc/vendor/list.min.js | 2 + doc/vendor/lodash.custom.min.js | 41 + doc/vendor/path-to-regexp/LICENSE | 21 + doc/vendor/path-to-regexp/index.js | 204 ++++ doc/vendor/polyfill.js | 96 ++ doc/vendor/prettify.css | 51 + doc/vendor/prettify/lang-Splus.js | 18 + doc/vendor/prettify/lang-aea.js | 18 + doc/vendor/prettify/lang-agc.js | 18 + doc/vendor/prettify/lang-apollo.js | 18 + doc/vendor/prettify/lang-basic.js | 18 + doc/vendor/prettify/lang-cbm.js | 18 + doc/vendor/prettify/lang-cl.js | 18 + doc/vendor/prettify/lang-clj.js | 17 + doc/vendor/prettify/lang-css.js | 18 + doc/vendor/prettify/lang-dart.js | 19 + doc/vendor/prettify/lang-el.js | 18 + doc/vendor/prettify/lang-erl.js | 18 + doc/vendor/prettify/lang-erlang.js | 18 + doc/vendor/prettify/lang-fs.js | 18 + doc/vendor/prettify/lang-go.js | 17 + doc/vendor/prettify/lang-hs.js | 18 + doc/vendor/prettify/lang-lasso.js | 19 + doc/vendor/prettify/lang-lassoscript.js | 19 + doc/vendor/prettify/lang-latex.js | 17 + doc/vendor/prettify/lang-lgt.js | 18 + doc/vendor/prettify/lang-lisp.js | 18 + doc/vendor/prettify/lang-ll.js | 17 + doc/vendor/prettify/lang-llvm.js | 17 + doc/vendor/prettify/lang-logtalk.js | 18 + doc/vendor/prettify/lang-ls.js | 19 + doc/vendor/prettify/lang-lsp.js | 18 + doc/vendor/prettify/lang-lua.js | 18 + doc/vendor/prettify/lang-matlab.js | 29 + doc/vendor/prettify/lang-ml.js | 18 + doc/vendor/prettify/lang-mumps.js | 18 + doc/vendor/prettify/lang-n.js | 19 + doc/vendor/prettify/lang-nemerle.js | 19 + doc/vendor/prettify/lang-pascal.js | 18 + doc/vendor/prettify/lang-proto.js | 17 + doc/vendor/prettify/lang-r.js | 18 + doc/vendor/prettify/lang-rd.js | 17 + doc/vendor/prettify/lang-rkt.js | 18 + doc/vendor/prettify/lang-rust.js | 20 + doc/vendor/prettify/lang-s.js | 18 + doc/vendor/prettify/lang-scala.js | 18 + doc/vendor/prettify/lang-scm.js | 18 + doc/vendor/prettify/lang-sql.js | 18 + doc/vendor/prettify/lang-ss.js | 18 + doc/vendor/prettify/lang-swift.js | 16 + doc/vendor/prettify/lang-tcl.js | 18 + doc/vendor/prettify/lang-tex.js | 17 + doc/vendor/prettify/lang-vb.js | 19 + doc/vendor/prettify/lang-vbs.js | 19 + doc/vendor/prettify/lang-vhd.js | 19 + doc/vendor/prettify/lang-vhdl.js | 19 + doc/vendor/prettify/lang-wiki.js | 18 + doc/vendor/prettify/lang-xq.js | 19 + doc/vendor/prettify/lang-xquery.js | 19 + doc/vendor/prettify/lang-yaml.js | 18 + doc/vendor/prettify/lang-yml.js | 18 + doc/vendor/prettify/prettify.css | 1 + doc/vendor/prettify/prettify.js | 46 + doc/vendor/prettify/run_prettify.js | 63 ++ doc/vendor/require.min.js | 37 + doc/vendor/semver.min.js | 1 + doc/vendor/webfontloader.js | 17 + package.json | 8 +- src/lib/ldap.js | 1 - src/lib/routes/u2f_register.js | 7 + src/lib/server.js | 37 +- src/lib/setup_endpoints.js | 282 +++++ test/unitary/routes/test_u2f.js | 15 + 106 files changed, 7322 insertions(+), 39 deletions(-) create mode 100644 doc/api_data.js create mode 100644 doc/api_data.json create mode 100644 doc/api_project.js create mode 100644 doc/api_project.json create mode 100644 doc/css/style.css create mode 100644 doc/fonts/glyphicons-halflings-regular.eot create mode 100644 doc/fonts/glyphicons-halflings-regular.svg create mode 100644 doc/fonts/glyphicons-halflings-regular.ttf create mode 100644 doc/fonts/glyphicons-halflings-regular.woff create mode 100644 doc/fonts/glyphicons-halflings-regular.woff2 create mode 100644 doc/img/favicon.ico create mode 100644 doc/index.html create mode 100644 doc/locales/ca.js create mode 100644 doc/locales/de.js create mode 100644 doc/locales/es.js create mode 100644 doc/locales/fr.js create mode 100644 doc/locales/it.js create mode 100644 doc/locales/locale.js create mode 100644 doc/locales/nl.js create mode 100644 doc/locales/pl.js create mode 100644 doc/locales/pt_br.js create mode 100644 doc/locales/ro.js create mode 100644 doc/locales/ru.js create mode 100644 doc/locales/zh.js create mode 100644 doc/locales/zh_cn.js create mode 100644 doc/main.js create mode 100644 doc/utils/handlebars_helper.js create mode 100755 doc/utils/send_sample_request.js create mode 100644 doc/vendor/bootstrap.min.css create mode 100644 doc/vendor/bootstrap.min.js create mode 100644 doc/vendor/diff_match_patch.min.js create mode 100644 doc/vendor/handlebars.min.js create mode 100644 doc/vendor/jquery.min.js create mode 100644 doc/vendor/list.min.js create mode 100644 doc/vendor/lodash.custom.min.js create mode 100644 doc/vendor/path-to-regexp/LICENSE create mode 100644 doc/vendor/path-to-regexp/index.js create mode 100644 doc/vendor/polyfill.js create mode 100644 doc/vendor/prettify.css create mode 100644 doc/vendor/prettify/lang-Splus.js create mode 100644 doc/vendor/prettify/lang-aea.js create mode 100644 doc/vendor/prettify/lang-agc.js create mode 100644 doc/vendor/prettify/lang-apollo.js create mode 100644 doc/vendor/prettify/lang-basic.js create mode 100644 doc/vendor/prettify/lang-cbm.js create mode 100644 doc/vendor/prettify/lang-cl.js create mode 100644 doc/vendor/prettify/lang-clj.js create mode 100644 doc/vendor/prettify/lang-css.js create mode 100644 doc/vendor/prettify/lang-dart.js create mode 100644 doc/vendor/prettify/lang-el.js create mode 100644 doc/vendor/prettify/lang-erl.js create mode 100644 doc/vendor/prettify/lang-erlang.js create mode 100644 doc/vendor/prettify/lang-fs.js create mode 100644 doc/vendor/prettify/lang-go.js create mode 100644 doc/vendor/prettify/lang-hs.js create mode 100644 doc/vendor/prettify/lang-lasso.js create mode 100644 doc/vendor/prettify/lang-lassoscript.js create mode 100644 doc/vendor/prettify/lang-latex.js create mode 100644 doc/vendor/prettify/lang-lgt.js create mode 100644 doc/vendor/prettify/lang-lisp.js create mode 100644 doc/vendor/prettify/lang-ll.js create mode 100644 doc/vendor/prettify/lang-llvm.js create mode 100644 doc/vendor/prettify/lang-logtalk.js create mode 100644 doc/vendor/prettify/lang-ls.js create mode 100644 doc/vendor/prettify/lang-lsp.js create mode 100644 doc/vendor/prettify/lang-lua.js create mode 100644 doc/vendor/prettify/lang-matlab.js create mode 100644 doc/vendor/prettify/lang-ml.js create mode 100644 doc/vendor/prettify/lang-mumps.js create mode 100644 doc/vendor/prettify/lang-n.js create mode 100644 doc/vendor/prettify/lang-nemerle.js create mode 100644 doc/vendor/prettify/lang-pascal.js create mode 100644 doc/vendor/prettify/lang-proto.js create mode 100644 doc/vendor/prettify/lang-r.js create mode 100644 doc/vendor/prettify/lang-rd.js create mode 100644 doc/vendor/prettify/lang-rkt.js create mode 100644 doc/vendor/prettify/lang-rust.js create mode 100644 doc/vendor/prettify/lang-s.js create mode 100644 doc/vendor/prettify/lang-scala.js create mode 100644 doc/vendor/prettify/lang-scm.js create mode 100644 doc/vendor/prettify/lang-sql.js create mode 100644 doc/vendor/prettify/lang-ss.js create mode 100644 doc/vendor/prettify/lang-swift.js create mode 100644 doc/vendor/prettify/lang-tcl.js create mode 100644 doc/vendor/prettify/lang-tex.js create mode 100644 doc/vendor/prettify/lang-vb.js create mode 100644 doc/vendor/prettify/lang-vbs.js create mode 100644 doc/vendor/prettify/lang-vhd.js create mode 100644 doc/vendor/prettify/lang-vhdl.js create mode 100644 doc/vendor/prettify/lang-wiki.js create mode 100644 doc/vendor/prettify/lang-xq.js create mode 100644 doc/vendor/prettify/lang-xquery.js create mode 100644 doc/vendor/prettify/lang-yaml.js create mode 100644 doc/vendor/prettify/lang-yml.js create mode 100644 doc/vendor/prettify/prettify.css create mode 100644 doc/vendor/prettify/prettify.js create mode 100644 doc/vendor/prettify/run_prettify.js create mode 100644 doc/vendor/require.min.js create mode 100644 doc/vendor/semver.min.js create mode 100644 doc/vendor/webfontloader.js create mode 100644 src/lib/setup_endpoints.js diff --git a/doc/api_data.js b/doc/api_data.js new file mode 100644 index 000000000..b502c45ed --- /dev/null +++ b/doc/api_data.js @@ -0,0 +1,1031 @@ +define({ "api": [ + { + "type": "post", + "url": "/authentication/2ndfactor/u2f/sign", + "title": "U2F Complete authentication", + "name": "CompleteU2FAuthentication", + "group": "Authentication", + "version": "1.0.0", + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

The U2F authentication succeeded.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "type": "none", + "optional": false, + "field": "error", + "description": "

No authentication request has been provided.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Complete authentication request of the U2F device.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Authentication", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "get", + "url": "/authentication/2ndfactor/u2f/sign_request", + "title": "U2F Start authentication", + "name": "StartU2FAuthentication", + "group": "Authentication", + "version": "1.0.0", + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "optional": false, + "field": "authentication_request", + "description": "

The U2F authentication request.

" + } + ] + } + }, + "error": { + "fields": { + "Error 401": [ + { + "group": "Error 401", + "type": "none", + "optional": false, + "field": "error", + "description": "

There is no key registered for user in session.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Initiate an authentication request using a U2F device.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Authentication", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/1stfactor", + "title": "LDAP authentication", + "name": "ValidateFirstFactor", + "group": "Authentication", + "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "username", + "description": "

User username.

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "password", + "description": "

User password.

" + } + ] + } + }, + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

1st factor is validated.

" + } + ] + } + }, + "error": { + "fields": { + "Error 401": [ + { + "group": "Error 401", + "type": "none", + "optional": false, + "field": "error", + "description": "

1st factor is not validated.

" + } + ], + "Error 403": [ + { + "group": "Error 403", + "type": "none", + "optional": false, + "field": "error", + "description": "

Access has been restricted after too many authentication attempts

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Verify credentials against the LDAP.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Authentication", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/2ndfactor/totp", + "title": "TOTP authentication", + "name": "ValidateTOTPSecondFactor", + "group": "Authentication", + "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "token", + "description": "

TOTP token.

" + } + ] + } + }, + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

TOTP token is valid.

" + } + ] + } + }, + "error": { + "fields": { + "Error 401": [ + { + "group": "Error 401", + "type": "none", + "optional": false, + "field": "error", + "description": "

TOTP token is invalid.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Verify TOTP token. The user is authenticated upon success.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Authentication", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "get", + "url": "/authentication/login", + "title": "Serve login page", + "name": "Login", + "group": "Pages", + "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "redirect", + "description": "

Redirect to this URL when user is authenticated.

" + } + ] + } + }, + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "Content", + "description": "

The content of the login page.

" + } + ] + } + }, + "description": "

Create a user session and serve the login page along with a cookie.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Pages" + }, + { + "type": "get", + "url": "/authentication/logout", + "title": "Server logout page", + "name": "Logout", + "group": "Pages", + "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "redirect", + "description": "

Redirect to this URL when user is deauthenticated.

" + } + ] + } + }, + "success": { + "fields": { + "Success 301": [ + { + "group": "Success 301", + "optional": false, + "field": "redirect", + "description": "

Redirect to the URL.

" + } + ] + } + }, + "description": "

Deauthenticate the user and redirect him.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Pages" + }, + { + "type": "get", + "url": "/authentication/reset-password", + "title": "Serve password reset form.", + "name": "ServePasswordResetForm", + "group": "Pages", + "version": "1.0.0", + "description": "

Serves password reset form that allow the user to provide the new password.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Pages", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "identity_token", + "description": "

The one-time identity validation token provided in the email.

" + } + ] + } + }, + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

Identity validation has been initiated.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 400": [ + { + "group": "Error 400", + "optional": false, + "field": "InvalidIdentity", + "description": "

User identity is invalid.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + } + }, + { + "type": "get", + "url": "/authentication/u2f-register", + "title": "Serve U2F registration page", + "name": "ServeU2FRegistrationPage", + "group": "Pages", + "version": "1.0.0", + "description": "

Serves the U2F registration page that asks the user to touch the token of the U2F device.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Pages", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "identity_token", + "description": "

The one-time identity validation token provided in the email.

" + } + ] + } + }, + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

Identity validation has been initiated.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 400": [ + { + "group": "Error 400", + "optional": false, + "field": "InvalidIdentity", + "description": "

User identity is invalid.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/2ndfactor/u2f/register", + "title": "U2F Complete device registration", + "name": "CompleteU2FRegistration", + "group": "Registration", + "version": "1.0.0", + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

The U2F registration succeeded.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "type": "none", + "optional": false, + "field": "error", + "description": "

Unexpected identity validation challenge.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Complete U2F registration request.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/new-totp-secret", + "title": "Generate TOTP secret", + "name": "GenerateTOTPSecret", + "group": "Registration", + "version": "1.0.0", + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "base32", + "description": "

The base32 representation of the secret.

" + }, + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "ascii", + "description": "

The ASCII representation of the secret.

" + }, + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "qrcode", + "description": "

The QRCode of the secret in URI format.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "type": "String", + "optional": false, + "field": "error", + "description": "

No user provided in the session or unexpected identity validation challenge in the session.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message

" + } + ] + } + }, + "description": "

Generate a new TOTP secret and returns it.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/reset-password", + "title": "Request for password reset", + "name": "RequestPasswordReset", + "group": "Registration", + "version": "1.0.0", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "content", + "description": "

The content of the page.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

This request issue an identity validation token for the user bound to the session. It sends a challenge to the email address set in the user LDAP entry. The user must visit the sent URL to complete the validation and continue the registration process.

" + }, + { + "type": "post", + "url": "/authentication/totp-register", + "title": "Request TOTP registration", + "name": "RequestTOTPRegistration", + "group": "Registration", + "version": "1.0.0", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "content", + "description": "

The content of the page.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

This request issue an identity validation token for the user bound to the session. It sends a challenge to the email address set in the user LDAP entry. The user must visit the sent URL to complete the validation and continue the registration process.

" + }, + { + "type": "post", + "url": "/authentication/u2f-register", + "title": "Request U2F registration", + "name": "RequestU2FRegistration", + "group": "Registration", + "version": "1.0.0", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "content", + "description": "

The content of the page.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

This request issue an identity validation token for the user bound to the session. It sends a challenge to the email address set in the user LDAP entry. The user must visit the sent URL to complete the validation and continue the registration process.

" + }, + { + "type": "get", + "url": "/authentication/totp-register", + "title": "Serve TOTP registration page", + "name": "ServeTOTPRegistrationPage", + "group": "Registration", + "version": "1.0.0", + "description": "

Serves the TOTP registration page that displays the secret. The secret is a QRCode and a base32 secret.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "identity_token", + "description": "

The one-time identity validation token provided in the email.

" + } + ] + } + }, + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

Identity validation has been initiated.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 400": [ + { + "group": "Error 400", + "optional": false, + "field": "InvalidIdentity", + "description": "

User identity is invalid.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/new-password", + "title": "Set LDAP password", + "name": "SetLDAPPassword", + "group": "Registration", + "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "password", + "description": "

New password

" + } + ] + } + }, + "description": "

Set a new password for the user.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "get", + "url": "/authentication/2ndfactor/u2f/register_request", + "title": "U2F Start device registration", + "name": "StartU2FRegistration", + "group": "Registration", + "version": "1.0.0", + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "optional": false, + "field": "authentication_request", + "description": "

The U2F registration request.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "type": "none", + "optional": false, + "field": "error", + "description": "

Unexpected identity validation challenge.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Initiate a U2F device registration request.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "get", + "url": "/authentication/verify", + "title": "Verify user authentication", + "name": "VerifyAuthentication", + "group": "Verification", + "version": "1.0.0", + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

The user is authenticated.

" + } + ] + } + }, + "error": { + "fields": { + "Error 401": [ + { + "group": "Error 401", + "optional": false, + "field": "status", + "description": "

The user is not authenticated.

" + } + ] + } + }, + "description": "

Verify that the user is authenticated, i.e., the two factors have been validated

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Verification", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + } +] }); diff --git a/doc/api_data.json b/doc/api_data.json new file mode 100644 index 000000000..1e93f2cf7 --- /dev/null +++ b/doc/api_data.json @@ -0,0 +1,1031 @@ +[ + { + "type": "post", + "url": "/authentication/2ndfactor/u2f/sign", + "title": "U2F Complete authentication", + "name": "CompleteU2FAuthentication", + "group": "Authentication", + "version": "1.0.0", + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

The U2F authentication succeeded.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "type": "none", + "optional": false, + "field": "error", + "description": "

No authentication request has been provided.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Complete authentication request of the U2F device.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Authentication", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "get", + "url": "/authentication/2ndfactor/u2f/sign_request", + "title": "U2F Start authentication", + "name": "StartU2FAuthentication", + "group": "Authentication", + "version": "1.0.0", + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "optional": false, + "field": "authentication_request", + "description": "

The U2F authentication request.

" + } + ] + } + }, + "error": { + "fields": { + "Error 401": [ + { + "group": "Error 401", + "type": "none", + "optional": false, + "field": "error", + "description": "

There is no key registered for user in session.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Initiate an authentication request using a U2F device.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Authentication", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/1stfactor", + "title": "LDAP authentication", + "name": "ValidateFirstFactor", + "group": "Authentication", + "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "username", + "description": "

User username.

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "password", + "description": "

User password.

" + } + ] + } + }, + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

1st factor is validated.

" + } + ] + } + }, + "error": { + "fields": { + "Error 401": [ + { + "group": "Error 401", + "type": "none", + "optional": false, + "field": "error", + "description": "

1st factor is not validated.

" + } + ], + "Error 403": [ + { + "group": "Error 403", + "type": "none", + "optional": false, + "field": "error", + "description": "

Access has been restricted after too many authentication attempts

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Verify credentials against the LDAP.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Authentication", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/2ndfactor/totp", + "title": "TOTP authentication", + "name": "ValidateTOTPSecondFactor", + "group": "Authentication", + "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "token", + "description": "

TOTP token.

" + } + ] + } + }, + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

TOTP token is valid.

" + } + ] + } + }, + "error": { + "fields": { + "Error 401": [ + { + "group": "Error 401", + "type": "none", + "optional": false, + "field": "error", + "description": "

TOTP token is invalid.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Verify TOTP token. The user is authenticated upon success.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Authentication", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "get", + "url": "/authentication/login", + "title": "Serve login page", + "name": "Login", + "group": "Pages", + "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "redirect", + "description": "

Redirect to this URL when user is authenticated.

" + } + ] + } + }, + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "Content", + "description": "

The content of the login page.

" + } + ] + } + }, + "description": "

Create a user session and serve the login page along with a cookie.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Pages" + }, + { + "type": "get", + "url": "/authentication/logout", + "title": "Server logout page", + "name": "Logout", + "group": "Pages", + "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "redirect", + "description": "

Redirect to this URL when user is deauthenticated.

" + } + ] + } + }, + "success": { + "fields": { + "Success 301": [ + { + "group": "Success 301", + "optional": false, + "field": "redirect", + "description": "

Redirect to the URL.

" + } + ] + } + }, + "description": "

Deauthenticate the user and redirect him.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Pages" + }, + { + "type": "get", + "url": "/authentication/reset-password", + "title": "Serve password reset form.", + "name": "ServePasswordResetForm", + "group": "Pages", + "version": "1.0.0", + "description": "

Serves password reset form that allow the user to provide the new password.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Pages", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "identity_token", + "description": "

The one-time identity validation token provided in the email.

" + } + ] + } + }, + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

Identity validation has been initiated.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 400": [ + { + "group": "Error 400", + "optional": false, + "field": "InvalidIdentity", + "description": "

User identity is invalid.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + } + }, + { + "type": "get", + "url": "/authentication/u2f-register", + "title": "Serve U2F registration page", + "name": "ServeU2FRegistrationPage", + "group": "Pages", + "version": "1.0.0", + "description": "

Serves the U2F registration page that asks the user to touch the token of the U2F device.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Pages", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "identity_token", + "description": "

The one-time identity validation token provided in the email.

" + } + ] + } + }, + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

Identity validation has been initiated.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 400": [ + { + "group": "Error 400", + "optional": false, + "field": "InvalidIdentity", + "description": "

User identity is invalid.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/2ndfactor/u2f/register", + "title": "U2F Complete device registration", + "name": "CompleteU2FRegistration", + "group": "Registration", + "version": "1.0.0", + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

The U2F registration succeeded.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "type": "none", + "optional": false, + "field": "error", + "description": "

Unexpected identity validation challenge.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Complete U2F registration request.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/new-totp-secret", + "title": "Generate TOTP secret", + "name": "GenerateTOTPSecret", + "group": "Registration", + "version": "1.0.0", + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "base32", + "description": "

The base32 representation of the secret.

" + }, + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "ascii", + "description": "

The ASCII representation of the secret.

" + }, + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "qrcode", + "description": "

The QRCode of the secret in URI format.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "type": "String", + "optional": false, + "field": "error", + "description": "

No user provided in the session or unexpected identity validation challenge in the session.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message

" + } + ] + } + }, + "description": "

Generate a new TOTP secret and returns it.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/reset-password", + "title": "Request for password reset", + "name": "RequestPasswordReset", + "group": "Registration", + "version": "1.0.0", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "content", + "description": "

The content of the page.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

This request issue an identity validation token for the user bound to the session. It sends a challenge to the email address set in the user LDAP entry. The user must visit the sent URL to complete the validation and continue the registration process.

" + }, + { + "type": "post", + "url": "/authentication/totp-register", + "title": "Request TOTP registration", + "name": "RequestTOTPRegistration", + "group": "Registration", + "version": "1.0.0", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "content", + "description": "

The content of the page.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

This request issue an identity validation token for the user bound to the session. It sends a challenge to the email address set in the user LDAP entry. The user must visit the sent URL to complete the validation and continue the registration process.

" + }, + { + "type": "post", + "url": "/authentication/u2f-register", + "title": "Request U2F registration", + "name": "RequestU2FRegistration", + "group": "Registration", + "version": "1.0.0", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "content", + "description": "

The content of the page.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

This request issue an identity validation token for the user bound to the session. It sends a challenge to the email address set in the user LDAP entry. The user must visit the sent URL to complete the validation and continue the registration process.

" + }, + { + "type": "get", + "url": "/authentication/totp-register", + "title": "Serve TOTP registration page", + "name": "ServeTOTPRegistrationPage", + "group": "Registration", + "version": "1.0.0", + "description": "

Serves the TOTP registration page that displays the secret. The secret is a QRCode and a base32 secret.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + }, + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "identity_token", + "description": "

The one-time identity validation token provided in the email.

" + } + ] + } + }, + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

Identity validation has been initiated.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "optional": false, + "field": "AccessDenied", + "description": "

Access is denied.

" + } + ], + "Error 400": [ + { + "group": "Error 400", + "optional": false, + "field": "InvalidIdentity", + "description": "

User identity is invalid.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + } + }, + { + "type": "post", + "url": "/authentication/new-password", + "title": "Set LDAP password", + "name": "SetLDAPPassword", + "group": "Registration", + "version": "1.0.0", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "password", + "description": "

New password

" + } + ] + } + }, + "description": "

Set a new password for the user.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "get", + "url": "/authentication/2ndfactor/u2f/register_request", + "title": "U2F Start device registration", + "name": "StartU2FRegistration", + "group": "Registration", + "version": "1.0.0", + "success": { + "fields": { + "Success 200": [ + { + "group": "Success 200", + "optional": false, + "field": "authentication_request", + "description": "

The U2F registration request.

" + } + ] + } + }, + "error": { + "fields": { + "Error 403": [ + { + "group": "Error 403", + "type": "none", + "optional": false, + "field": "error", + "description": "

Unexpected identity validation challenge.

" + } + ], + "Error 500": [ + { + "group": "Error 500", + "type": "String", + "optional": false, + "field": "error", + "description": "

Internal error message.

" + } + ] + } + }, + "description": "

Initiate a U2F device registration request.

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Registration", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + }, + { + "type": "get", + "url": "/authentication/verify", + "title": "Verify user authentication", + "name": "VerifyAuthentication", + "group": "Verification", + "version": "1.0.0", + "success": { + "fields": { + "Success 204": [ + { + "group": "Success 204", + "optional": false, + "field": "status", + "description": "

The user is authenticated.

" + } + ] + } + }, + "error": { + "fields": { + "Error 401": [ + { + "group": "Error 401", + "optional": false, + "field": "status", + "description": "

The user is not authenticated.

" + } + ] + } + }, + "description": "

Verify that the user is authenticated, i.e., the two factors have been validated

", + "filename": "src/lib/setup_endpoints.js", + "groupTitle": "Verification", + "header": { + "fields": { + "Header": [ + { + "group": "Header", + "type": "String", + "optional": false, + "field": "Cookie", + "description": "

Cookie containing 'connect.sid', the user session token.

" + } + ] + } + } + } +] diff --git a/doc/api_project.js b/doc/api_project.js new file mode 100644 index 000000000..c9d88c053 --- /dev/null +++ b/doc/api_project.js @@ -0,0 +1,15 @@ +define({ + "title": "Authelia API documentation", + "name": "authelia", + "version": "1.0.11", + "description": "2-factor authentication server using LDAP as 1st factor and TOTP or U2F as 2nd factor", + "sampleUrl": false, + "defaultVersion": "0.0.0", + "apidoc": "0.3.0", + "generator": { + "name": "apidoc", + "time": "2017-01-29T00:33:20.669Z", + "url": "http://apidocjs.com", + "version": "0.17.5" + } +}); diff --git a/doc/api_project.json b/doc/api_project.json new file mode 100644 index 000000000..545dc264f --- /dev/null +++ b/doc/api_project.json @@ -0,0 +1,15 @@ +{ + "title": "Authelia API documentation", + "name": "authelia", + "version": "1.0.11", + "description": "2-factor authentication server using LDAP as 1st factor and TOTP or U2F as 2nd factor", + "sampleUrl": false, + "defaultVersion": "0.0.0", + "apidoc": "0.3.0", + "generator": { + "name": "apidoc", + "time": "2017-01-29T00:33:20.669Z", + "url": "http://apidocjs.com", + "version": "0.17.5" + } +} diff --git a/doc/css/style.css b/doc/css/style.css new file mode 100644 index 000000000..eb953166b --- /dev/null +++ b/doc/css/style.css @@ -0,0 +1,568 @@ +/* ------------------------------------------------------------------------------------------ + * Content + * ------------------------------------------------------------------------------------------ */ +body { + min-width: 980px; + max-width: 1280px; +} + +body, p, a, div, th, td { + font-family: "Source Sans Pro", sans-serif; + font-weight: 400; + font-size: 16px; +} + +td.code { + font-size: 14px; + font-family: "Source Code Pro", monospace; + font-style: normal; + font-weight: 400; +} + +#content { + padding-top: 16px; + z-Index: -1; + margin-left: 270px; +} + +p { + color: #808080; +} + +h1 { + font-family: "Source Sans Pro Semibold", sans-serif; + font-weight: normal; + font-size: 44px; + line-height: 50px; + margin: 0 0 10px 0; + padding: 0; +} + +h2 { + font-family: "Source Sans Pro", sans-serif; + font-weight: normal; + font-size: 24px; + line-height: 40px; + margin: 0 0 20px 0; + padding: 0; +} + +section { + border-top: 1px solid #ebebeb; + padding: 30px 0; +} + +section h1 { + font-family: "Source Sans Pro", sans-serif; + font-weight: 700; + font-size: 32px; + line-height: 40px; + padding-bottom: 14px; + margin: 0 0 20px 0; + padding: 0; +} + +article { + padding: 14px 0 30px 0; +} + +article h1 { + font-family: "Source Sans Pro Bold", sans-serif; + font-weight: 600; + font-size: 24px; + line-height: 26px; +} + +article h2 { + font-family: "Source Sans Pro", sans-serif; + font-weight: 600; + font-size: 18px; + line-height: 24px; + margin: 0 0 10px 0; +} + +article h3 { + font-family: "Source Sans Pro", sans-serif; + font-weight: 600; + font-size: 16px; + line-height: 18px; + margin: 0 0 10px 0; +} + +article h4 { + font-family: "Source Sans Pro", sans-serif; + font-weight: 600; + font-size: 14px; + line-height: 16px; + margin: 0 0 8px 0; +} + +table { + border-collapse: collapse; + width: 100%; + margin: 0 0 20px 0; +} + +th { + background-color: #f5f5f5; + text-align: left; + font-family: "Source Sans Pro", sans-serif; + font-weight: 700; + padding: 4px 8px; + border: #e0e0e0 1px solid; +} + +td { + vertical-align: top; + padding: 10px 8px 0 8px; + border: #e0e0e0 1px solid; +} + +#generator .content { + color: #b0b0b0; + border-top: 1px solid #ebebeb; + padding: 10px 0; +} + +.label-optional { + float: right; + background-color: grey; + margin-top: 4px; +} + +.open-left { + right: 0; + left: auto; +} + +/* ------------------------------------------------------------------------------------------ + * apidoc - intro + * ------------------------------------------------------------------------------------------ */ + +#apidoc .apidoc { + border-top: 1px solid #ebebeb; + padding: 30px 0; +} + +#apidoc h1 { + font-family: "Source Sans Pro", sans-serif; + font-weight: 700; + font-size: 32px; + line-height: 40px; + padding-bottom: 14px; + margin: 0 0 20px 0; + padding: 0; +} + +#apidoc h2 { + font-family: "Source Sans Pro Bold", sans-serif; + font-weight: 600; + font-size: 22px; + line-height: 26px; + padding-top: 14px; +} + +/* ------------------------------------------------------------------------------------------ + * pre / code + * ------------------------------------------------------------------------------------------ */ +pre { + background-color: #292b36; + color: #ffffff; + padding: 10px; + border-radius: 6px; + position: relative; + margin: 10px 0 20px 0; +} + +pre.prettyprint { + width: 100%; +} + +code.language-text { + word-wrap: break-word; +} + +pre.language-json { + overflow: auto; +} + +pre.language-html { + margin: 0 0 20px 0; +} + +.type { + font-family: "Source Sans Pro", sans-serif; + font-weight: 600; + font-size: 15px; + display: inline-block; + margin: 0 0 5px 0; + padding: 4px 5px; + border-radius: 6px; + text-transform: uppercase; + background-color: #3387CC; + color: #ffffff; +} + +.type__get { + background-color: green; +} + +.type__put { + background-color: #e5c500; +} + +.type__post { + background-color: #4070ec; +} + +.type__delete { + background-color: #ed0039; +} + +pre.language-api .str { + color: #ffffff; +} + +pre.language-api .pln, +pre.language-api .pun { + color: #65B042; +} + +pre code { + display: block; + font-size: 14px; + font-family: "Source Code Pro", monospace; + font-style: normal; + font-weight: 400; + word-wrap: normal; + white-space: pre; +} + +pre code.sample-request-response-json { + white-space: pre-wrap; + max-height: 500px; + overflow: auto; +} + +/* ------------------------------------------------------------------------------------------ + * Sidenav + * ------------------------------------------------------------------------------------------ */ +.sidenav { + width: 228px; + margin: 0; + padding: 0 20px 20px 20px; + position: fixed; + top: 50px; + left: 0; + bottom: 0; + overflow-x: hidden; + overflow-y: auto; + background-color: #f5f5f5; + z-index: 10; +} + +.sidenav > li > a { + display: block; + width: 192px; + margin: 0; + padding: 2px 11px; + border: 0; + border-left: transparent 4px solid; + border-right: transparent 4px solid; + font-family: "Source Sans Pro", sans-serif; + font-weight: 400; + font-size: 14px; +} + +.sidenav > li.nav-header { + margin-top: 8px; + margin-bottom: 8px; +} + +.sidenav > li.nav-header > a { + padding: 5px 15px; + border: 1px solid #e5e5e5; + width: 190px; + font-family: "Source Sans Pro", sans-serif; + font-weight: 700; + font-size: 16px; + background-color: #ffffff; +} + +.sidenav > li.active > a { + position: relative; + z-index: 2; + background-color: #0088cc; + color: #ffffff; +} + +.sidenav > li.has-modifications a { + border-right: #60d060 4px solid; +} + +.sidenav > li.is-new a { + border-left: #e5e5e5 4px solid; +} + +/* ------------------------------------------------------------------------------------------ + * Side nav search + * ------------------------------------------------------------------------------------------ */ +.sidenav-search { + width: 228px; + left: 0px; + position: fixed; + padding: 16px 20px 10px 20px; + background-color: #F5F5F5; + z-index: 11; +} + +.sidenav-search .search { + height: 26px; +} + +.search-reset { + position: absolute; + display: block; + cursor: pointer; + width: 20px; + height: 20px; + text-align: center; + right: 28px; + top: 17px; + background-color: #fff; +} + +/* ------------------------------------------------------------------------------------------ + * Compare + * ------------------------------------------------------------------------------------------ */ + +ins { + background: #60d060; + text-decoration: none; + color: #000000; +} + +del { + background: #f05050; + color: #000000; +} + +.label-ins { + background-color: #60d060; +} + +.label-del { + background-color: #f05050; + text-decoration: line-through; +} + +pre.ins { + background-color: #60d060; +} + +pre.del { + background-color: #f05050; + text-decoration: line-through; +} + +table.ins th, +table.ins td { + background-color: #60d060; +} + +table.del th, +table.del td { + background-color: #f05050; + text-decoration: line-through; +} + +tr.ins td { + background-color: #60d060; +} + +tr.del td { + background-color: #f05050; + text-decoration: line-through; +} + +/* ------------------------------------------------------------------------------------------ + * Spinner + * ------------------------------------------------------------------------------------------ */ + +#loader { + position: absolute; + width: 100%; +} + +#loader p { + padding-top: 80px; + margin-left: -4px; +} + +.spinner { + margin: 200px auto; + width: 60px; + height: 60px; + position: relative; +} + +.container1 > div, .container2 > div, .container3 > div { + width: 14px; + height: 14px; + background-color: #0088cc; + + border-radius: 100%; + position: absolute; + -webkit-animation: bouncedelay 1.2s infinite ease-in-out; + animation: bouncedelay 1.2s infinite ease-in-out; + /* Prevent first frame from flickering when animation starts */ + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.spinner .spinner-container { + position: absolute; + width: 100%; + height: 100%; +} + +.container2 { + -webkit-transform: rotateZ(45deg); + transform: rotateZ(45deg); +} + +.container3 { + -webkit-transform: rotateZ(90deg); + transform: rotateZ(90deg); +} + +.circle1 { top: 0; left: 0; } +.circle2 { top: 0; right: 0; } +.circle3 { right: 0; bottom: 0; } +.circle4 { left: 0; bottom: 0; } + +.container2 .circle1 { + -webkit-animation-delay: -1.1s; + animation-delay: -1.1s; +} + +.container3 .circle1 { + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; +} + +.container1 .circle2 { + -webkit-animation-delay: -0.9s; + animation-delay: -0.9s; +} + +.container2 .circle2 { + -webkit-animation-delay: -0.8s; + animation-delay: -0.8s; +} + +.container3 .circle2 { + -webkit-animation-delay: -0.7s; + animation-delay: -0.7s; +} + +.container1 .circle3 { + -webkit-animation-delay: -0.6s; + animation-delay: -0.6s; +} + +.container2 .circle3 { + -webkit-animation-delay: -0.5s; + animation-delay: -0.5s; +} + +.container3 .circle3 { + -webkit-animation-delay: -0.4s; + animation-delay: -0.4s; +} + +.container1 .circle4 { + -webkit-animation-delay: -0.3s; + animation-delay: -0.3s; +} + +.container2 .circle4 { + -webkit-animation-delay: -0.2s; + animation-delay: -0.2s; +} + +.container3 .circle4 { + -webkit-animation-delay: -0.1s; + animation-delay: -0.1s; +} + +@-webkit-keyframes bouncedelay { + 0%, 80%, 100% { -webkit-transform: scale(0.0) } + 40% { -webkit-transform: scale(1.0) } +} + +@keyframes bouncedelay { + 0%, 80%, 100% { + transform: scale(0.0); + -webkit-transform: scale(0.0); + } 40% { + transform: scale(1.0); + -webkit-transform: scale(1.0); + } +} + +/* ------------------------------------------------------------------------------------------ + * Tabs + * ------------------------------------------------------------------------------------------ */ +ul.nav-tabs { + margin: 0; +} + +p.deprecated span{ + color: #ff0000; + font-weight: bold; + text-decoration: underline; +} + +/* ------------------------------------------------------------------------------------------ + * Print + * ------------------------------------------------------------------------------------------ */ + +@media print { + + #sidenav, + #version, + #versions, + section .version, + section .versions { + display: none; + } + + #content { + margin-left: 0; + } + + a { + text-decoration: none; + color: inherit; + } + + a:after { + content: " [" attr(href) "] "; + } + + p { + color: #000000 + } + + pre { + background-color: #ffffff; + color: #000000; + padding: 10px; + border: #808080 1px solid; + border-radius: 6px; + position: relative; + margin: 10px 0 20px 0; + } + +} /* /@media print */ diff --git a/doc/fonts/glyphicons-halflings-regular.eot b/doc/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..b93a4953fff68df523aa7656497ee339d6026d64 GIT binary patch literal 20127 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/fonts/glyphicons-halflings-regular.ttf b/doc/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1413fc609ab6f21774de0cb7e01360095584f65b GIT binary patch literal 45404 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 G`u_lkUM?>H literal 0 HcmV?d00001 diff --git a/doc/fonts/glyphicons-halflings-regular.woff2 b/doc/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 zcmV(~K+nH-Pew8T0RR9107h&84*&oF0I^&E07eM_0Rl|`00000000000000000000 z0000#Mn+Uk92y`7U;vDA2m}!b3WBL5f#qcZHUcCAhI9*rFaQJ~1&1OBl~F%;WnyLq z8)b|&?3j;$^FW}&KmNW53flIFARDZ7_Wz%hpoWaWlgHTHEHf()GI0&dMi#DFPaEt6 zCO)z0v0~C~q&0zBj^;=tv8q{$8JxX)>_`b}WQGgXi46R*CHJ}6r+;}OrvwA{_SY+o zK)H-vy{l!P`+NG*`*x6^PGgHH4!dsolgU4RKj@I8Xz~F6o?quCX&=VQ$Q{w01;M0? zKe|5r<_7CD z=eO3*x!r$aX2iFh3;}xNfx0v;SwBfGG+@Z;->HhvqfF4r__4$mU>Dl_1w;-9`~5rF~@!3;r~xP-hZvOfOx)A z#>8O3N{L{naf215f>m=bzbp7_(ssu&cx)Qo-{)!)Yz3A@Z0uZaM2yJ8#OGlzm?JO5gbrj~@)NB4@?>KE(K-$w}{};@dKY#K3+Vi64S<@!Z{(I{7l=!p9 z&kjG^P~0f46i13(w!hEDJga;*Eb z`!n|++@H8VaKG<9>VDh(y89J#=;Z$ei=GnD5TesW#|Wf)^D+9NKN4J3H5PF_t=V+Z zdeo8*h9+8&Zfc?>>1|E4B7MAx)^uy$L>szyXre7W|81fjy+RZ1>Gd}@@${~PCOXo) z$#HZd3)V3@lNGG%(3PyIbvyJTOJAWcN@Uh!FqUkx^&BuAvc)G}0~SKI`8ZZXw$*xP zum-ZdtPciTAUn$XWb6vrS=JX~f5?M%9S(=QsdYP?K%Odn0S0-Ad<-tBtS3W06I^FK z8}d2eR_n!(uK~APZ-#tl@SycxkRJ@5wmypdWV{MFtYBUY#g-Vv?5AEBj1 z`$T^tRKca*sn7gt%s@XUD-t>bij-4q-ilku9^;QJ3Mpc`HJ_EX4TGGQ-Og)`c~qm51<|gp7D@ zp#>Grssv^#A)&M8>ulnDM_5t#Al`#jaFpZ<#YJ@>!a$w@kEZ1<@PGs#L~kxOSz7jj zEhb?;W)eS}0IQQuk4~JT30>4rFJ3!b+77}>$_>v#2FFEnN^%(ls*o80pv0Q>#t#%H z@`Yy-FXQ9ULKh{Up&oA_A4B!(x^9&>i`+T|eD!&QOLVd(_avv-bFX~4^>o{%mzzrg_i~SBnr%DeE|i+^}|8?kaV(Z32{`vA^l!sp15>Z72z52FgXf z^8ZITvJ9eXBT1~iQjW|Q`Fac^ak$^N-vI^*geh5|*CdMz;n16gV_zk|Z7q8tFfCvU zJK^Pptnn0Rc~egGIAK}uv99VZm2WLPezQQ5K<`f zg{8Ll|GioPYfNheMj-7-S87=w4N0WxHP`1V6Y)0M&SkYzVrwp>yfsEF7wj&T0!}dB z)R~gGfP9pOR;GY_e0~K^^oJ-3AT+m~?Al!{>>5gNe17?OWz)$)sMH*xuQiB>FT2{i zQ>6U_8}Ay~r4li;jzG+$&?S12{)+<*k9 z<^SX#xY|jvlvTxt(m~C7{y{3g>7TX#o2q$xQO|fc<%8rE@A3=UW(o?gVg?gDV!0q6O!{MlX$6-Bu_m&0ms66 znWS&zr{O_4O&{2uCLQvA?xC5vGZ}KV1v6)#oTewgIMSnBur0PtM0&{R5t#UEy3I9) z`LVP?3f;o}sz*7g5qdTxJl^gk3>;8%SOPH@B)rmFOJ)m6?PlYa$y=RX%;}KId{m9R#2=LNwosF@OTivgMqxpRGe}5=LtAn?VVl6VWCFLD z7l#^^H8jY~42hR)OoVF#YDW(md!g(&pJ;yMj|UBAQa}UH?ED@%ci=*(q~Opn>kE2Q z_4Kgf|0kEA6ary41A;)^Ku(*nirvP!Y>{FZYBLXLP6QL~vRL+uMlZ?jWukMV*(dsn zL~~KA@jU)(UeoOz^4Gkw{fJsYQ%|UA7i79qO5=DOPBcWlv%pK!A+)*F`3WJ}t9FU3 zXhC4xMV7Z%5RjDs0=&vC4WdvD?Zi5tg4@xg8-GLUI>N$N&3aS4bHrp%3_1u9wqL)i z)XQLsI&{Hd&bQE!3m&D0vd!4D`l1$rt_{3NS?~lj#|$GN5RmvP(j3hzJOk=+0B*2v z)Bw133RMUM%wu_+$vbzOy?yk#kvR?xGsg-ipX4wKyXqd zROKp5))>tNy$HByaEHK%$mqd>-{Yoj`oSBK;w>+eZ&TVcj^DyXjo{DDbZ>vS2cCWB z(6&~GZ}kUdN(*2-nI!hvbnVy@z2E#F394OZD&Jb04}`Tgaj?MoY?1`{ejE2iud51% zQ~J0sijw(hqr_Ckbj@pm$FAVASKY(D4BS0GYPkSMqSDONRaFH+O2+jL{hIltJSJT~e)TNDr(}=Xt7|UhcU9eoXl&QZRR<9WomW%&m)FT~j zTgGd3-j}Uk%CRD;$@X)NNV9+RJbifYu>yr{FkO;p>_&njI> zyBHh_72bW;8}oGeY0gpHOxiV597j7mY<#?WMmkf5x~Kfk*re(&tG_mX<3&2cON*2u%V29tsXUv{#-ijs2>EuNH-x3) zPBpi+V6gI=wn}u164_j8xi-y(B?Au2o;UO=r6&)i5S3Mx*)*{_;u}~i4dh$`VgUS- zMG6t*?DXDYX0D2Oj31MI!HF>|aG8rjrOPnxHu4wZl;!=NGjjDoBpXf?ntrwt^dqxm zs(lE@*QB3NH)!`rH)5kks-D89g@UX&@DU9jvrsY)aI=9b4nPy3bfdX_U;#?zsan{G>DKob2LnhCJv8o}duQK)qP{7iaaf2=K`a-VNcfC582d4a z>sBJA*%S|NEazDxXcGPW_uZ&d7xG`~JB!U>U(}acUSn=FqOA~(pn^!aMXRnqiL0;? zebEZYouRv}-0r;Dq&z9>s#Rt1HL`0p4bB)A&sMyn|rE_9nh z?NO*RrjET8D4s(-`nS{MrdYtv*kyCnJKbsftG2D#ia@;42!8xd?a3P(&Y?vCf9na< zQ&Ni*1Qel&Xq{Z?=%f0SRqQt5m|Myg+8T=GDc)@^};=tM>9IDr7hdvE9-M@@<0pqv45xZTeNecbL- zWFQt4t`9>j8~X%lz}%We>Kzh_=`XO}!;4!OWH?=p*DOs#Nt({k^IvtBEL~Qafn)I^ zm*k{y7_bIs9YE}0B6%r`EIUH8US+MGY!KQA1fi-jCx9*}oz2k1nBsXp;4K<_&SN}}w<)!EylI_)v7}3&c)V;Cfuj*eJ2yc8LK=vugqTL><#65r6%#2e| zdYzZ)9Uq7)A$ol&ynM!|RDHc_7?FlWqjW>8TIHc`jExt)f5W|;D%GC#$u!%B*S%Z0 zsj&;bIU2jrt_7%$=!h4Q29n*A^^AI8R|stsW%O@?i+pN0YOU`z;TVuPy!N#~F8Z29 zzZh1`FU(q31wa>kmw{$q=MY>XBprL<1)Py~5TW4mgY%rg$S=4C^0qr+*A^T)Q)Q-U zGgRb9%MdE-&i#X3xW=I`%xDzAG95!RG9)s?v_5+qx`7NdkQ)If5}BoEp~h}XoeK>kweAMxJ8tehagx~;Nr_WP?jXa zJ&j7%Ef3w*XWf?V*nR)|IOMrX;$*$e23m?QN` zk>sC^GE=h6?*Cr~596s_QE@>Nnr?{EU+_^G=LZr#V&0fEXQ3IWtrM{=t^qJ62Sp=e zrrc>bzX^6yFV!^v7;>J9>j;`qHDQ4uc92eVe6nO@c>H=ouLQot``E~KLNqMqJ7(G+?GWO9Ol+q$w z!^kMv!n{vF?RqLnxVk{a_Ar;^sw0@=+~6!4&;SCh^utT=I zo&$CwvhNOjQpenw2`5*a6Gos6cs~*TD`8H9P4=#jOU_`%L!W;$57NjN%4 z39(61ZC#s7^tv`_4j}wMRT9rgDo*XtZwN-L;Qc$6v8kKkhmRrxSDkUAzGPgJ?}~_t zkwoGS4=6lsD`=RL|8L3O9L()N)lmEn-M15fRC{dhZ}7eYV%O-R^gsAp{q4 z!C1}_T8gy^v@SZ5R&Li5JMJy+K8iZw3LOGA0pN1~y@w7RRl#F()ii6Y5mr~Mdy@Kz z@FT4cm^I&#Fu_9IX(HAFP{XLbRALqm&)>m_we>a`hfv?eE|t z?YdDp2yAhj-~vuw^wzVDuj%w?exOcOT(ls(F*ceCe(C5HlN{lcQ;}|mRPqFDqLEzw zR7ldY+M6xe$$qLwekmk{Z&5cME$gpC?-8)f0m$rqaS|mj9ATNJvvyCgs(f2{r;2E!oy$k5{jik#(;S>do<#m0wVcU<}>)VtYmF9O0%(C>GDzPgh6X z9OkQLMR~y7=|MtaU!LDPPY7O)L{X#SC+M|v^X2CZ?$GS>U_|aC(VA(mIvCNk+biD| zSpj>gd(v>_Cbq>~-x^Y3o|?eHmuC?E&z>;Ij`%{$Pm$hI}bl0Kd`9KD~AchY+goL1?igDxf$qxL9< z4sW@sD)nwWr`T>e2B8MQN|p*DVTT8)3(%AZ&D|@Zh6`cJFT4G^y6`(UdPLY-&bJYJ z*L06f2~BX9qX}u)nrpmHPG#La#tiZ23<>`R@u8k;ueM6 znuSTY7>XEc+I-(VvL?Y>)adHo(cZ;1I7QP^q%hu#M{BEd8&mG_!EWR7ZV_&EGO;d(hGGJzX|tqyYEg2-m0zLT}a{COi$9!?9yK zGN7&yP$a|0gL`dPUt=4d^}?zrLN?HfKP0_gdRvb}1D73Hx!tXq>7{DWPV;^X{-)cm zFa^H5oBDL3uLkaFDWgFF@HL6Bt+_^g~*o*t`Hgy3M?nHhWvTp^|AQDc9_H< zg>IaSMzd7c(Sey;1SespO=8YUUArZaCc~}}tZZX80w%)fNpMExki-qB+;8xVX@dr; z#L52S6*aM-_$P9xFuIui;dN#qZ_MYy^C^hrY;YAMg;K`!ZpKKFc z9feHsool)`tFSS}Su|cL0%F;h!lpR+ym|P>kE-O`3QnHbJ%gJ$dQ_HPTT~>6WNX41 zoDEUpX-g&Hh&GP3koF4##?q*MX1K`@=W6(Gxm1=2Tb{hn8{sJyhQBoq}S>bZT zisRz-xDBYoYxt6--g2M1yh{#QWFCISux}4==r|7+fYdS$%DZ zXVQu{yPO<)Hn=TK`E@;l!09aY{!TMbT)H-l!(l{0j=SEj@JwW0a_h-2F0MZNpyucb zPPb+4&j?a!6ZnPTB>$t`(XSf-}`&+#rI#`GB> zl=$3HORwccTnA2%>$Nmz)u7j%_ywoGri1UXVNRxSf(<@vDLKKxFo;5pTI$R~a|-sQ zd5Rfwj+$k1t0{J`qOL^q>vZUHc7a^`cKKVa{66z?wMuQAfdZBaVVv@-wamPmes$d! z>gv^xx<0jXOz;7HIQS z4RBIFD?7{o^IQ=sNQ-k!ao*+V*|-^I2=UF?{d>bE9avsWbAs{sRE-y`7r zxVAKA9amvo4T}ZAHSF-{y1GqUHlDp4DO9I3mz5h8n|}P-9nKD|$r9AS3gbF1AX=2B zyaK3TbKYqv%~JHKQH8v+%zQ8UVEGDZY|mb>Oe3JD_Z{+Pq%HB+J1s*y6JOlk`6~H) zKt)YMZ*RkbU!GPHzJltmW-=6zqO=5;S)jz{ zFSx?ryqSMxgx|Nhv3z#kFBTuTBHsViaOHs5e&vXZ@l@mVI37<+^KvTE51!pB4Tggq zz!NlRY2ZLno0&6bA|KHPYOMY;;LZG&_lzuLy{@i$&B(}_*~Zk2 z>bkQ7u&Ww%CFh{aqkT{HCbPbRX&EvPRp=}WKmyHc>S_-qbwAr0<20vEoJ(!?-ucjE zKQ+nSlRL^VnOX0h+WcjGb6WI(8;7bsMaHXDb6ynPoOXMlf9nLKre;w*#E_whR#5!! z!^%_+X3eJVKc$fMZP;+xP$~e(CIP1R&{2m+iTQhDoC8Yl@kLM=Wily_cu>7C1wjVU z-^~I0P06ZSNVaN~A`#cSBH2L&tk6R%dU1(u1XdAx;g+5S^Hn9-L$v@p7CCF&PqV{Z?R$}4EJi36+u2JP7l(@fYfP!=e#76LGy^f>~vs0%s*x@X8`|5 zGd6JOHsQ=feES4Vo8%1P_7F5qjiIm#oRT0kO1(?Z_Dk6oX&j=Xd8Klk(;gk3S(ZFnc^8Gc=d;8O-R9tlGyp=2I@1teAZpGWUi;}`n zbJOS_Z2L16nVtDnPpMn{+wR9&yU9~C<-ncppPee`>@1k7hTl5Fn_3_KzQ)u{iJPp3 z)df?Xo%9ta%(dp@DhKuQj4D8=_!*ra#Ib&OXKrsYvAG%H7Kq|43WbayvsbeeimSa= z8~{7ya9ZUAIgLLPeuNmSB&#-`Je0Lja)M$}I41KHb7dQq$wgwX+EElNxBgyyLbA2* z=c1VJR%EPJEw(7!UE?4w@94{pI3E%(acEYd8*Wmr^R7|IM2RZ-RVXSkXy-8$!(iB* zQA`qh2Ze!EY6}Zs7vRz&nr|L60NlIgnO3L*Yz2k2Ivfen?drnVzzu3)1V&-t5S~S? zw#=Sdh>K@2vA25su*@>npw&7A%|Uh9T1jR$mV*H@)pU0&2#Se`7iJlOr$mp79`DKM z5vr*XLrg7w6lc4&S{So1KGKBqcuJ!E|HVFB?vTOjQHi)g+FwJqX@Y3q(qa#6T@3{q zhc@2T-W}XD9x4u+LCdce$*}x!Sc#+rH-sCz6j}0EE`Tk*irUq)y^za`}^1gFnF)C!yf_l_}I<6qfbT$Gc&Eyr?!QwJR~RE4!gKVmqjbI+I^*^ z&hz^7r-dgm@Mbfc#{JTH&^6sJCZt-NTpChB^fzQ}?etydyf~+)!d%V$0faN(f`rJb zm_YaJZ@>Fg>Ay2&bzTx3w^u-lsulc{mX4-nH*A(32O&b^EWmSuk{#HJk}_ULC}SB(L7`YAs>opp9o5UcnB^kVB*rmW6{s0&~_>J!_#+cEWib@v-Ms`?!&=3fDot`oH9v&$f<52>{n2l* z1FRzJ#yQbTHO}}wt0!y8Eh-0*|Um3vjX-nWH>`JN5tWB_gnW%; zUJ0V?_a#+!=>ahhrbGvmvObe8=v1uI8#gNHJ#>RwxL>E^pT05Br8+$@a9aDC1~$@* zicSQCbQcr=DCHM*?G7Hsovk|{$3oIwvymi#YoXeVfWj{Gd#XmnDgzQPRUKNAAI44y z{1WG&rhIR4ipmvBmq$BZ*5tmPIZmhhWgq|TcuR{6lA)+vhj(cH`0;+B^72{&a7ff* zkrIo|pd-Yxm+VVptC@QNCDk0=Re%Sz%ta7y{5Dn9(EapBS0r zLbDKeZepar5%cAcb<^;m>1{QhMzRmRem=+0I3ERot-)gb`i|sII^A#^Gz+x>TW5A& z3PQcpM$lDy`zb%1yf!e8&_>D02RN950KzW>GN6n@2so&Wu09x@PB=&IkIf|zZ1W}P zAKf*&Mo5@@G=w&290aG1@3=IMCB^|G4L7*xn;r3v&HBrD4D)Zg+)f~Ls$7*P-^i#B z4X7ac=0&58j^@2EBZCs}YPe3rqgLAA1L3Y}o?}$%u~)7Rk=LLFbAdSy@-Uw6lv?0K z&P@@M`o2Rll3GoYjotf@WNNjHbe|R?IKVn*?Rzf9v9QoFMq)ODF~>L}26@z`KA82t z43e!^z&WGqAk$Ww8j6bc3$I|;5^BHwt`?e)zf|&+l#!8uJV_Cwy-n1yS0^Q{W*a8B zTzTYL>tt&I&9vzGQUrO?YIm6C1r>eyh|qw~-&;7s7u1achP$K3VnXd8sV8J7ZTxTh z5+^*J5%_#X)XL2@>h(Gmv$@)fZ@ikR$v(2Rax89xscFEi!3_;ORI0dBxw)S{r50qf zg&_a*>2Xe{s@)7OX9O!C?^6fD8tc3bQTq9}fxhbx2@QeaO9Ej+2m!u~+u%Q6?Tgz{ zjYS}bleKcVhW~1$?t*AO^p!=Xkkgwx6OTik*R3~yg^L`wUU9Dq#$Z*iW%?s6pO_f8 zJ8w#u#Eaw7=8n{zJ}C>w{enA6XYHfUf7h)!Qaev)?V=yW{b@-z`hAz;I7^|DoFChP z1aYQnkGauh*ps6x*_S77@z1wwGmF8ky9fMbM$dr*`vsot4uvqWn)0vTRwJqH#&D%g zL3(0dP>%Oj&vm5Re%>*4x|h1J2X*mK5BH1?Nx_#7( zepgF`+n)rHXj!RiipusEq!X81;QQBXlTvLDj=Qub(ha&D=BDx3@-V*d!D9PeXUY?l zwZ0<4=iY!sUj4G>zTS+eYX7knN-8Oynl=NdwHS*nSz_5}*5LQ@=?Yr?uj$`C1m2OR zK`f5SD2|;=BhU#AmaTKe9QaSHQ_DUj1*cUPa*JICFt1<&S3P3zsrs^yUE;tx=x^cmW!Jq!+hohv_B> zPDMT0D&08dC4x@cTD$o1$x%So1Ir(G3_AVQMvQ13un~sP(cEWi$2%5q93E7t{3VJf%K? zuwSyDke~7KuB2?*#DV8YzJw z&}SCDexnUPD!%4|y~7}VzvJ4ch)WT4%sw@ItwoNt(C*RP)h?&~^g##vnhR0!HvIYx z0td2yz9=>t3JNySl*TszmfH6`Ir;ft@RdWs3}!J88UE|gj_GMQ6$ZYphUL2~4OY7} zB*33_bjkRf_@l;Y!7MIdb~bVe;-m78Pz|pdy=O*3kjak63UnLt!{^!!Ljg0rJD3a~ z1Q;y5Z^MF<=Hr}rdoz>yRczx+p3RxxgJE2GX&Si)14B@2t21j4hnnP#U?T3g#+{W+Zb z5s^@>->~-}4|_*!5pIzMCEp|3+i1XKcfUxW`8|ezAh>y{WiRcjSG*asw6;Ef(k#>V ztguN?EGkV_mGFdq!n#W)<7E}1#EZN8O$O|}qdoE|7K?F4zo1jL-v}E8v?9qz(d$&2 zMwyK&xlC9rXo_2xw7Qe0caC?o?Pc*-QAOE!+UvRuKjG+;dk|jQhDDBe?`XT7Y5lte zqSu0t5`;>Wv%|nhj|ZiE^IqA_lZu7OWh!2Y(627zb=r7Ends}wVk7Q5o09a@ojhH7 zU0m&h*8+j4e|OqWyJ&B`V`y=>MVO;K9=hk^6EsmVAGkLT{oUtR{JqSRY{Qi{kKw1k z6s;0SMPJOLp!som|A`*q3t0wIj-=bG8a#MC)MHcMSQU98Juv$?$CvYX)(n`P^!`5| zv3q@@|G@6wMqh;d;m4qvdibx2Yjml}vG9mDv&!0ne02M#D`Bo}xIB0VWh8>>WtNZQ z$&ISlJX;*ORQIO;k62qA{^6P%3!Z=Y1EbmY02{w^yB$`;%!{kur&XTGDiO2cjA)lr zsY^XZWy^DSAaz;kZ_VG?uWnJR7qdN18$~)>(kOoybY0~QYu9||K#|$Mby{3GduV~N zk9H7$7=RSo+?CUYF502`b76ytBy}sFak&|HIwRvB=0D|S`c#QCJPq zP)uOWI)#(n&{6|C4A^G~%B~BY21aOMoz9RuuM`Ip%oBz+NoAlb7?#`E^}7xXo!4S? zFg8I~G%!@nXi8&aJSGFcZAxQf;0m}942=i#p-&teLvE{AKm7Sl2f}Io?!IqbC|J;h z`=5LFOnU5?^w~SV@YwNZx$k_(kLNxZDE z3cf08^-rIT_>A$}B%IJBPcN^)4;90BQtiEi!gT#+EqyAUZ|}*b_}R>SGloq&6?opL zuT_+lwQMgg6!Cso$BwUA;k-1NcrzyE>(_X$B0HocjY~=Pk~Q08+N}(|%HjO_i+*=o z%G6C6A30Ch<0UlG;Zdj@ed!rfUY_i9mYwK8(aYuzcUzlTJ1yPz|Bb-9b33A9zRhGl>Ny-Q#JAq-+qtI@B@&w z$;PJbyiW=!py@g2hAi0)U1v=;avka`gd@8LC4=BEbNqL&K^UAQ5%r95#x%^qRB%KLaqMnG|6xKAm}sx!Qwo}J=2C;NROi$mfADui4)y(3wVA3k~{j^_5%H)C6K zlYAm1eY**HZOj($)xfKIQFtIVw$4&yvz9>(Crs>Gh{ zya6-FG7Dgi92#K)64=9Csj5?Zqe~_9TwSI!2quAwa1w-*uC5!}xY`?tltb0Hq740< zsq2QelPveZ4chr$=~U3!+c&>xyfvA1`)owOqj=i4wjY=A1577Gwg&Ko7;?il9r|_* z8P&IDV_g2D{in5OLFxsO!kx3AhO$5aKeoM|!q|VokqMlYM@HtsRuMtBY%I35#5$+G zpp|JOeoj^U=95HLemB04Yqv{a8X<^K9G2`&ShM_6&Bi1n?o?@MXsDj9Z*A3>#XK%J zRc*&SlFl>l)9DyRQ{*%Z+^e1XpH?0@vhpXrnPPU*d%vOhKkimm-u3c%Q^v3RKp9kx@A2dS?QfS=iigGr7m><)YkV=%LA5h@Uj@9=~ABPMJ z1UE;F&;Ttg5Kc^Qy!1SuvbNEqdgu3*l`=>s5_}dUv$B%BJbMiWrrMm7OXOdi=GOmh zZBvXXK7VqO&zojI2Om9};zCB5i|<210I{iwiGznGCx=FT89=Ef)5!lB1cZ6lbzgDn07*he}G&w7m!;|E(L-?+cz@0<9ZI~LqYQE7>HnPA436}oeN2Y(VfG6 zxNZuMK3Crm^Z_AFeHc~CVRrSl0W^?+Gbteu1g8NGYa3(8f*P{(ZT>%!jtSl6WbYVv zmE(37t0C8vJ6O-5+o*lL9XRcFbd~GSBGbGh3~R!67g&l)7n!kJlWd)~TUyXus#!&G6sR%(l(h1$xyrR5j_jM1zj#giA&@(Xl26@n<9>folx!92bQ z24h570+<)4!$!IQ(5yOU|4_E6aN@4v0+{Kx~Z z;q7fp%0cHziuI%!kB~w}g9@V+1wDz0wFlzX2UOvOy|&;e;t!lAR8tV2KQHgtfk8Uf zw;rs!(4JPODERk4ckd5I2Vq|0rd@@Mwd8MID%0^fITjYIQom^q;qhP8@|eJx{?5xX zc1@Fj*kDknlk{c-rnCloQ3hGh7OU+@efO3>fkRMcM>J?AeVP& zlfzX%cdp=N+4S#E*%^=BQ+N`A7C}|k%$|QUn0yI6S3$MS-NjO!4hm55uyju)Q6e!} z*OVO@A#-mfC9Pha6ng((Xl^V7{d+&u+yx)_B1{~t7d5e8L^i4J>;x<7@5;+l7-Gge zf#9diXJ$&v^rbN5V(ee%q0xBMEgS6%qZm7hNUP%G;^J44I!BmI@M*+FWz0!+s;+iQ zU4CuI+27bvNK8v>?7PZnVxB=heJ&_ymE0nN^W#-rqB%+JXkYGDuRw>JM_LdtLkiq* z6%%3&^BX$jnM@2bjiGc-DymKly)wVkA-pq;jSWL#7_*moZZ4I|-N}o8SK?sIv)p|c zu~9-B%tMc=!)YMFp*SiC0>kfnH8+X5>;+FFVN{~a9YVdIg1uGkZ~kegFy{^PU(4{( z`CbY`XmVA3esai686Yw8djCEyF7`bfB^F1)nwv+AqYLZ&Zy=eFhYT2uMd@{sP_qS4 zbJ&>PxajjZt?&c<1^!T|pLHfX=E^FJ>-l_XCZzvRV%x}@u(FtF(mS+Umw$e+IA74e>gCdTqi;6&=euAIpxd=Y3I5xWR zBhGoT+T`V1@91OlQ}2YO*~P4ukd*TBBdt?Plt)_ou6Y@Db`ss+Q~A-48s>?eaJYA2 zRGOa8^~Em}EFTmKIVVbMb|ob)hJJ7ITg>yHAn2i|{2ZJU!cwt9YNDT0=*WO7Bq#Xj zg@FjEaKoolrF8%c;49|`IT&25?O$dq8kp3#la9&6aH z6G|{>^C(>yP7#Dr$aeFyS0Ai_$ILhL43#*mgEl(c*4?Ae;tRL&S7Vc}Szl>B`mBuI zB9Y%xp%CZwlH!3V(`6W4-ZuETssvI&B~_O;CbULfl)X1V%(H7VSPf`_Ka9ak@8A=z z1l|B1QKT}NLI`WVTRd;2En5u{0CRqy9PTi$ja^inu){LJ&E&6W%JJPw#&PaTxpt?k zpC~gjN*22Q8tpGHR|tg~ye#9a8N<%odhZJnk7Oh=(PKfhYfzLAxdE36r<6a?A;rO&ELp_Y?8Pdw(PT^Fxn!eG_|LEbSYoBrsBA|6Fgr zt5LntyusI{Q2fdy=>ditS;}^B;I2MD4=(>7fWt0Jp~y=?VvfvzHvQhj6dyIef46J$ zl4Xu7U9v_NJV?uBBC0!kcTS0UcrV7+@~is?Fi+jrr@l3XwD|uG zr26jUWiv>Ju48Y^#qn7r9mwIH-Pv6Y|V|V-GZ&+&gQ?S?-`&ts{@5GXPqbmyZjUACC&oVXfNwUX0}ba(v978 zp8z!v9~8Zx8qB@7>oFPDm^iR@+yw`79YF)w^OHB_N;&&x7c3l^3!)IY#)}x)@D(iNaOm9 zC=^*!{`7={3*S=%iU=KsPXh=DDZcc``Ss>057i{pdW8M@4q+Ba@Tt%OytH!4>rbIbQw^-pR zGGYNPzw@n=PV@)b7yVbFr;glF*Qq3>F9oBN5PUXt!?2mdGcpv^o1?Thp`jP10G2Yi z(c93td3F3SW!Le5DUwdub!aDKoVLU6g!O?Ret21l$qOC;kdd@L#M&baVu&JZGt&<6 z!VCkvgRaav6QDW2x}tUy4~Y5(B+#Ej-8vM?DM-1?J_*&PntI3E96M!`WL#<&Z5n2u zo`P!~vBT$YOT~gU9#PB)%JZ zcd_u=m^LYzC!pH#W`yA1!(fA;D~b zG#73@l)NNd;n#XrKXZEfab;@kQRnOFU2Th-1m<4mJzlj9b3pv-GF$elX7ib9!uILM_$ke zHIGB*&=5=;ynQA{y7H93%i^d)T}y@(p>8vVhJ4L)M{0Q*@D^+SPp`EW+G6E%+`Z;u zS3goV@Dic7vc5`?!pCN44Ts@*{)zwy)9?B||AM{zKlN4T}qQRL2 zgv+{K8bv7w)#xge16;kI1fU87!W4pX)N&|cq8&i^1r`W|Hg4366r(?-ecEJ9u&Eaw zrhyikXQB>C9d>cpPGiu=VU3Z-u4|0V_iap!_J3o+K_R5EXk@sfu~zHwwYkpncVh!R zqNe7Cmf_|Wmeq4#(mIO&(wCK@b4(x0?W1Qtk(`$?+$uCJCGZm_%k?l32vuShgDFMa ztc`{$8DhB9)&?~(m&EUc=LzI1=qo#zjy#2{hLT_*aj<618qQ7mD#k2ZFGou&69;=2 z1j7=Su8k}{L*h&mfs7jg^PN&9C1Z@U!p6gXk&-7xM~{X`nqH#aGO`;Xy_zbz^rYacIq0AH%4!Oh93TzJ820%ur)8OyeS@K?sF1V(iFO z37Nnqj1z#1{|v7=_CX`lQA|$<1gtuNMHGNJYp1D_k;WQk-b+T6VmUK(x=bWviOZ~T z|4e%SpuaWLWD?qN2%`S*`P;BQBw(B__wTD6epvGdJ+>DBq2oVlf&F*lz+#avb4)3P1c^Mf#olQheVvZ|Z5 z>xXfgmv!5Z^SYn+_x}K5B%G^sRwiez&z9|f!E!#oJlT2kCOV0000$L_|bHBqAarB4TD{W@grX1CUr72@caw0faEd7-K|4L_|cawbojjHdpd6 zI6~Iv5J?-Q4*&oF000000FV;^004t70Z6Qk1Xl{X9oJ{sRC2(cs?- literal 0 HcmV?d00001 diff --git a/doc/img/favicon.ico b/doc/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c307a043933f0e860284157007820fccbe0fc96f GIT binary patch literal 894 zcmdUuF-rqM6ojAn2d)y!l(7l<0Sa4)bsGCC1jQ;~7c8z2NtY8s$`~6pMGzDegoNu- zL@-!L!~}wc5V437IkY*y&4xu5e}L}I?#-JU-p($Z$Q+O73G1S4&5JCENScWxDW=x- zP<(U8O;P?D)-rOqJxf*D5X#fMtSq@XeWi86$CGh&FK141Qt~NVWtAj}bFN-DLPvaT z*RIZLGz1Vz2nbHj-L#d^{{!N!&pBAbk2j(^U(2=VbJTML#&?*0s93%{?MEL%tQGVj{yYS9qIdd y=|=!E>V$}|g9zOiSnsP@V9MK)_i(HXtO!B3{(nl`|3Ly2=CXc=hEFxke;MD%5Rt_I literal 0 HcmV?d00001 diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 000000000..d6347f26e --- /dev/null +++ b/doc/index.html @@ -0,0 +1,669 @@ + + + + + Loading... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+

Loading...

+
+
+ + + + diff --git a/doc/locales/ca.js b/doc/locales/ca.js new file mode 100644 index 000000000..65af5df2b --- /dev/null +++ b/doc/locales/ca.js @@ -0,0 +1,25 @@ +define({ + ca: { + 'Allowed values:' : 'Valors permesos:', + 'Compare all with predecessor': 'Comparar tot amb versió anterior', + 'compare changes to:' : 'comparar canvis amb:', + 'compared to' : 'comparat amb', + 'Default value:' : 'Valor per defecte:', + 'Description' : 'Descripció', + 'Field' : 'Camp', + 'General' : 'General', + 'Generated with' : 'Generat amb', + 'Name' : 'Nom', + 'No response values.' : 'Sense valors en la resposta.', + 'optional' : 'opcional', + 'Parameter' : 'Paràmetre', + 'Permission:' : 'Permisos:', + 'Response' : 'Resposta', + 'Send' : 'Enviar', + 'Send a Sample Request' : 'Enviar una petició d\'exemple', + 'show up to version:' : 'mostrar versió:', + 'Size range:' : 'Tamany de rang:', + 'Type' : 'Tipus', + 'url' : 'url' + } +}); diff --git a/doc/locales/de.js b/doc/locales/de.js new file mode 100644 index 000000000..f66420d00 --- /dev/null +++ b/doc/locales/de.js @@ -0,0 +1,25 @@ +define({ + de: { + 'Allowed values:' : 'Erlaubte Werte:', + 'Compare all with predecessor': 'Vergleiche alle mit ihren Vorgängern', + 'compare changes to:' : 'vergleiche Änderungen mit:', + 'compared to' : 'verglichen mit', + 'Default value:' : 'Standardwert:', + 'Description' : 'Beschreibung', + 'Field' : 'Feld', + 'General' : 'Allgemein', + 'Generated with' : 'Erstellt mit', + 'Name' : 'Name', + 'No response values.' : 'Keine Rückgabewerte.', + 'optional' : 'optional', + 'Parameter' : 'Parameter', + 'Permission:' : 'Berechtigung:', + 'Response' : 'Antwort', + 'Send' : 'Senden', + 'Send a Sample Request' : 'Eine Beispielanfrage senden', + 'show up to version:' : 'zeige bis zur Version:', + 'Size range:' : 'Größenbereich:', + 'Type' : 'Typ', + 'url' : 'url' + } +}); diff --git a/doc/locales/es.js b/doc/locales/es.js new file mode 100644 index 000000000..3d47e800e --- /dev/null +++ b/doc/locales/es.js @@ -0,0 +1,25 @@ +define({ + es: { + 'Allowed values:' : 'Valores permitidos:', + 'Compare all with predecessor': 'Comparar todo con versión anterior', + 'compare changes to:' : 'comparar cambios con:', + 'compared to' : 'comparado con', + 'Default value:' : 'Valor por defecto:', + 'Description' : 'Descripción', + 'Field' : 'Campo', + 'General' : 'General', + 'Generated with' : 'Generado con', + 'Name' : 'Nombre', + 'No response values.' : 'Sin valores en la respuesta.', + 'optional' : 'opcional', + 'Parameter' : 'Parámetro', + 'Permission:' : 'Permisos:', + 'Response' : 'Respuesta', + 'Send' : 'Enviar', + 'Send a Sample Request' : 'Enviar una petición de ejemplo', + 'show up to version:' : 'mostrar a versión:', + 'Size range:' : 'Tamaño de rango:', + 'Type' : 'Tipo', + 'url' : 'url' + } +}); diff --git a/doc/locales/fr.js b/doc/locales/fr.js new file mode 100644 index 000000000..100a64291 --- /dev/null +++ b/doc/locales/fr.js @@ -0,0 +1,25 @@ +define({ + fr: { + 'Allowed values:' : 'Valeurs autorisées :', + 'Compare all with predecessor': 'Tout comparer avec ...', + 'compare changes to:' : 'comparer les changements à :', + 'compared to' : 'comparer à', + 'Default value:' : 'Valeur par défaut :', + 'Description' : 'Description', + 'Field' : 'Champ', + 'General' : 'Général', + 'Generated with' : 'Généré avec', + 'Name' : 'Nom', + 'No response values.' : 'Aucune valeur de réponse.', + 'optional' : 'optionnel', + 'Parameter' : 'Paramètre', + 'Permission:' : 'Permission :', + 'Response' : 'Réponse', + 'Send' : 'Envoyer', + 'Send a Sample Request' : 'Envoyer une requête représentative', + 'show up to version:' : 'Montrer à partir de la version :', + 'Size range:' : 'Ordre de grandeur :', + 'Type' : 'Type', + 'url' : 'url' + } +}); diff --git a/doc/locales/it.js b/doc/locales/it.js new file mode 100644 index 000000000..8117108cf --- /dev/null +++ b/doc/locales/it.js @@ -0,0 +1,25 @@ +define({ + it: { + 'Allowed values:' : 'Valori permessi:', + 'Compare all with predecessor': 'Confronta tutto con versioni precedenti', + 'compare changes to:' : 'confronta modifiche con:', + 'compared to' : 'confrontato con', + 'Default value:' : 'Valore predefinito:', + 'Description' : 'Descrizione', + 'Field' : 'Campo', + 'General' : 'Generale', + 'Generated with' : 'Creato con', + 'Name' : 'Nome', + 'No response values.' : 'Nessun valore di risposta.', + 'optional' : 'opzionale', + 'Parameter' : 'Parametro', + 'Permission:' : 'Permessi:', + 'Response' : 'Risposta', + 'Send' : 'Invia', + 'Send a Sample Request' : 'Invia una richiesta di esempio', + 'show up to version:' : 'mostra alla versione:', + 'Size range:' : 'Intervallo dimensione:', + 'Type' : 'Tipo', + 'url' : 'url' + } +}); diff --git a/doc/locales/locale.js b/doc/locales/locale.js new file mode 100644 index 000000000..efe980ab6 --- /dev/null +++ b/doc/locales/locale.js @@ -0,0 +1,48 @@ +define([ + './locales/ca.js', + './locales/de.js', + './locales/es.js', + './locales/fr.js', + './locales/it.js', + './locales/nl.js', + './locales/pl.js', + './locales/pt_br.js', + './locales/ro.js', + './locales/ru.js', + './locales/zh.js', + './locales/zh_cn.js' +], function() { + var langId = (navigator.language || navigator.userLanguage).toLowerCase().replace('-', '_'); + var language = langId.substr(0, 2); + var locales = {}; + + for (index in arguments) { + for (property in arguments[index]) + locales[property] = arguments[index][property]; + } + if ( ! locales['en']) + locales['en'] = {}; + + if ( ! locales[langId] && ! locales[language]) + language = 'en'; + + var locale = (locales[langId] ? locales[langId] : locales[language]); + + function __(text) { + var index = locale[text]; + if (index === undefined) + return text; + return index; + }; + + function setLanguage(language) { + locale = locales[language]; + } + + return { + __ : __, + locales : locales, + locale : locale, + setLanguage: setLanguage + }; +}); diff --git a/doc/locales/nl.js b/doc/locales/nl.js new file mode 100644 index 000000000..bddfeeb18 --- /dev/null +++ b/doc/locales/nl.js @@ -0,0 +1,25 @@ +define({ + nl: { + 'Allowed values:' : 'Toegestane waarden:', + 'Compare all with predecessor': 'Vergelijk alle met voorgaande versie', + 'compare changes to:' : 'vergelijk veranderingen met:', + 'compared to' : 'vergelijk met', + 'Default value:' : 'Standaard waarde:', + 'Description' : 'Omschrijving', + 'Field' : 'Veld', + 'General' : 'Algemeen', + 'Generated with' : 'Gegenereerd met', + 'Name' : 'Naam', + 'No response values.' : 'Geen response waardes.', + 'optional' : 'optioneel', + 'Parameter' : 'Parameter', + 'Permission:' : 'Permissie:', + 'Response' : 'Antwoorden', + 'Send' : 'Sturen', + 'Send a Sample Request' : 'Stuur een sample aanvragen', + 'show up to version:' : 'toon tot en met versie:', + 'Size range:' : 'Maatbereik:', + 'Type' : 'Type', + 'url' : 'url' + } +}); diff --git a/doc/locales/pl.js b/doc/locales/pl.js new file mode 100644 index 000000000..db645ee16 --- /dev/null +++ b/doc/locales/pl.js @@ -0,0 +1,25 @@ +define({ + pl: { + 'Allowed values:' : 'Dozwolone wartości:', + 'Compare all with predecessor': 'Porównaj z poprzednimi wersjami', + 'compare changes to:' : 'porównaj zmiany do:', + 'compared to' : 'porównaj do:', + 'Default value:' : 'Wartość domyślna:', + 'Description' : 'Opis', + 'Field' : 'Pole', + 'General' : 'Generalnie', + 'Generated with' : 'Wygenerowano z', + 'Name' : 'Nazwa', + 'No response values.' : 'Brak odpowiedzi.', + 'optional' : 'opcjonalny', + 'Parameter' : 'Parametr', + 'Permission:' : 'Uprawnienia:', + 'Response' : 'Odpowiedź', + 'Send' : 'Wyślij', + 'Send a Sample Request' : 'Wyślij przykładowe żądanie', + 'show up to version:' : 'pokaż do wersji:', + 'Size range:' : 'Zakres rozmiaru:', + 'Type' : 'Typ', + 'url' : 'url' + } +}); diff --git a/doc/locales/pt_br.js b/doc/locales/pt_br.js new file mode 100644 index 000000000..2bd78b0d3 --- /dev/null +++ b/doc/locales/pt_br.js @@ -0,0 +1,25 @@ +define({ + 'pt_br': { + 'Allowed values:' : 'Valores permitidos:', + 'Compare all with predecessor': 'Compare todos com antecessores', + 'compare changes to:' : 'comparar alterações com:', + 'compared to' : 'comparado com', + 'Default value:' : 'Valor padrão:', + 'Description' : 'Descrição', + 'Field' : 'Campo', + 'General' : 'Geral', + 'Generated with' : 'Gerado com', + 'Name' : 'Nome', + 'No response values.' : 'Sem valores de resposta.', + 'optional' : 'opcional', + 'Parameter' : 'Parâmetro', + 'Permission:' : 'Permissão:', + 'Response' : 'Resposta', + 'Send' : 'Enviar', + 'Send a Sample Request' : 'Enviar um Exemplo de Pedido', + 'show up to version:' : 'aparecer para a versão:', + 'Size range:' : 'Faixa de tamanho:', + 'Type' : 'Tipo', + 'url' : 'url' + } +}); diff --git a/doc/locales/ro.js b/doc/locales/ro.js new file mode 100644 index 000000000..8d4e4ed87 --- /dev/null +++ b/doc/locales/ro.js @@ -0,0 +1,25 @@ +define({ + ro: { + 'Allowed values:' : 'Valori permise:', + 'Compare all with predecessor': 'Compară toate cu versiunea precedentă', + 'compare changes to:' : 'compară cu versiunea:', + 'compared to' : 'comparat cu', + 'Default value:' : 'Valoare implicită:', + 'Description' : 'Descriere', + 'Field' : 'Câmp', + 'General' : 'General', + 'Generated with' : 'Generat cu', + 'Name' : 'Nume', + 'No response values.' : 'Nici o valoare returnată.', + 'optional' : 'opțional', + 'Parameter' : 'Parametru', + 'Permission:' : 'Permisiune:', + 'Response' : 'Răspuns', + 'Send' : 'Trimite', + 'Send a Sample Request' : 'Trimite o cerere de probă', + 'show up to version:' : 'arată până la versiunea:', + 'Size range:' : 'Interval permis:', + 'Type' : 'Tip', + 'url' : 'url' + } +}); diff --git a/doc/locales/ru.js b/doc/locales/ru.js new file mode 100644 index 000000000..c5f338214 --- /dev/null +++ b/doc/locales/ru.js @@ -0,0 +1,25 @@ +define({ + ru: { + 'Allowed values:' : 'Допустимые значения:', + 'Compare all with predecessor': 'Сравнить с предыдущей версией', + 'compare changes to:' : 'сравнить с:', + 'compared to' : 'в сравнении с', + 'Default value:' : 'По умолчанию:', + 'Description' : 'Описание', + 'Field' : 'Название', + 'General' : 'Общая информация', + 'Generated with' : 'Сгенерировано с помощью', + 'Name' : 'Название', + 'No response values.' : 'Нет значений для ответа.', + 'optional' : 'необязательный', + 'Parameter' : 'Параметр', + 'Permission:' : 'Разрешено:', + 'Response' : 'Ответ', + 'Send' : 'Отправить', + 'Send a Sample Request' : 'Отправить тестовый запрос', + 'show up to version:' : 'показать версию:', + 'Size range:' : 'Ограничения:', + 'Type' : 'Тип', + 'url' : 'URL' + } +}); diff --git a/doc/locales/zh.js b/doc/locales/zh.js new file mode 100644 index 000000000..66522067f --- /dev/null +++ b/doc/locales/zh.js @@ -0,0 +1,25 @@ +define({ + zh: { + 'Allowed values​​:' : '允許值:', + 'Compare all with predecessor': '預先比較所有', + 'compare changes to:' : '比較變更:', + 'compared to' : '對比', + 'Default value:' : '默認值:', + 'Description' : '描述', + 'Field' : '字段', + 'General' : '概括', + 'Generated with' : '生成工具', + 'Name' : '名稱', + 'No response values​​.' : '無對應資料.', + 'optional' : '選項', + 'Parameter' : '參數', + 'Permission:' : '允許:', + 'Response' : '回應', + 'Send' : '發送', + 'Send a Sample Request' : '發送試用需求', + 'show up to version:' : '顯示到版本:', + 'Size range:' : '尺寸範圍:', + 'Type' : '類型', + 'url' : '網址' + } +}); diff --git a/doc/locales/zh_cn.js b/doc/locales/zh_cn.js new file mode 100644 index 000000000..1938ca184 --- /dev/null +++ b/doc/locales/zh_cn.js @@ -0,0 +1,25 @@ +define({ + 'zh_cn': { + 'Allowed values:' : '允许值:', + 'Compare all with predecessor': '与所有较早的比较', + 'compare changes to:' : '将当前版本与指定版本比较:', + 'compared to' : '相比于', + 'Default value:' : '默认值:', + 'Description' : '描述', + 'Field' : '字段', + 'General' : '概要', + 'Generated with' : '基于', + 'Name' : '名称', + 'No response values.' : '无返回值.', + 'optional' : '可选', + 'Parameter' : '参数', + 'Permission:' : '权限:', + 'Response' : '返回', + 'Send' : '发送', + 'Send a Sample Request' : '发送示例请求', + 'show up to version:' : '显示到指定版本:', + 'Size range:' : '取值范围:', + 'Type' : '类型', + 'url' : '网址' + } +}); diff --git a/doc/main.js b/doc/main.js new file mode 100644 index 000000000..9d31fa5f6 --- /dev/null +++ b/doc/main.js @@ -0,0 +1,827 @@ +require.config({ + paths: { + bootstrap: './vendor/bootstrap.min', + diffMatchPatch: './vendor/diff_match_patch.min', + handlebars: './vendor/handlebars.min', + handlebarsExtended: './utils/handlebars_helper', + jquery: './vendor/jquery.min', + locales: './locales/locale', + lodash: './vendor/lodash.custom.min', + pathToRegexp: './vendor/path-to-regexp/index', + prettify: './vendor/prettify/prettify', + semver: './vendor/semver.min', + utilsSampleRequest: './utils/send_sample_request', + webfontloader: './vendor/webfontloader', + list: './vendor/list.min' + }, + shim: { + bootstrap: { + deps: ['jquery'] + }, + diffMatchPatch: { + exports: 'diff_match_patch' + }, + handlebars: { + exports: 'Handlebars' + }, + handlebarsExtended: { + deps: ['jquery', 'handlebars'], + exports: 'Handlebars' + }, + prettify: { + exports: 'prettyPrint' + } + }, + urlArgs: 'v=' + (new Date()).getTime(), + waitSeconds: 15 +}); + +require([ + 'jquery', + 'lodash', + 'locales', + 'handlebarsExtended', + './api_project.js', + './api_data.js', + 'prettify', + 'utilsSampleRequest', + 'semver', + 'webfontloader', + 'bootstrap', + 'pathToRegexp', + 'list' +], function($, _, locale, Handlebars, apiProject, apiData, prettyPrint, sampleRequest, semver, WebFont) { + + // load google web fonts + loadGoogleFontCss(); + + var api = apiData.api; + + // + // Templates + // + var templateHeader = Handlebars.compile( $('#template-header').html() ); + var templateFooter = Handlebars.compile( $('#template-footer').html() ); + var templateArticle = Handlebars.compile( $('#template-article').html() ); + var templateCompareArticle = Handlebars.compile( $('#template-compare-article').html() ); + var templateGenerator = Handlebars.compile( $('#template-generator').html() ); + var templateProject = Handlebars.compile( $('#template-project').html() ); + var templateSections = Handlebars.compile( $('#template-sections').html() ); + var templateSidenav = Handlebars.compile( $('#template-sidenav').html() ); + + // + // apiProject defaults + // + if ( ! apiProject.template) + apiProject.template = {}; + + if (apiProject.template.withCompare == null) + apiProject.template.withCompare = true; + + if (apiProject.template.withGenerator == null) + apiProject.template.withGenerator = true; + + if (apiProject.template.forceLanguage) + locale.setLanguage(apiProject.template.forceLanguage); + + // Setup jQuery Ajax + $.ajaxSetup(apiProject.template.jQueryAjaxSetup); + + // + // Data transform + // + // grouped by group + var apiByGroup = _.groupBy(api, function(entry) { + return entry.group; + }); + + // grouped by group and name + var apiByGroupAndName = {}; + $.each(apiByGroup, function(index, entries) { + apiByGroupAndName[index] = _.groupBy(entries, function(entry) { + return entry.name; + }); + }); + + // + // sort api within a group by title ASC and custom order + // + var newList = []; + var umlauts = { 'ä': 'ae', 'ü': 'ue', 'ö': 'oe', 'ß': 'ss' }; // TODO: remove in version 1.0 + $.each (apiByGroupAndName, function(index, groupEntries) { + // get titles from the first entry of group[].name[] (name has versioning) + var titles = []; + $.each (groupEntries, function(titleName, entries) { + var title = entries[0].title; + if(title !== undefined) { + title.toLowerCase().replace(/[äöüß]/g, function($0) { return umlauts[$0]; }); + titles.push(title + '#~#' + titleName); // '#~#' keep reference to titleName after sorting + } + }); + // sort by name ASC + titles.sort(); + + // custom order + if (apiProject.order) + titles = sortByOrder(titles, apiProject.order, '#~#'); + + // add single elements to the new list + titles.forEach(function(name) { + var values = name.split('#~#'); + var key = values[1]; + groupEntries[key].forEach(function(entry) { + newList.push(entry); + }); + }); + }); + // api overwrite with ordered list + api = newList; + + // + // Group- and Versionlists + // + var apiGroups = {}; + var apiGroupTitles = {}; + var apiVersions = {}; + apiVersions[apiProject.version] = 1; + + $.each(api, function(index, entry) { + apiGroups[entry.group] = 1; + apiGroupTitles[entry.group] = entry.groupTitle || entry.group; + apiVersions[entry.version] = 1; + }); + + // sort groups + apiGroups = Object.keys(apiGroups); + apiGroups.sort(); + + // custom order + if (apiProject.order) + apiGroups = sortByOrder(apiGroups, apiProject.order); + + // sort versions DESC + apiVersions = Object.keys(apiVersions); + apiVersions.sort(semver.compare); + apiVersions.reverse(); + + // + // create Navigationlist + // + var nav = []; + apiGroups.forEach(function(group) { + // Mainmenu entry + nav.push({ + group: group, + isHeader: true, + title: apiGroupTitles[group] + }); + + // Submenu + var oldName = ''; + api.forEach(function(entry) { + if (entry.group === group) { + if (oldName !== entry.name) { + nav.push({ + title: entry.title, + group: group, + name: entry.name, + type: entry.type, + version: entry.version + }); + } else { + nav.push({ + title: entry.title, + group: group, + hidden: true, + name: entry.name, + type: entry.type, + version: entry.version + }); + } + oldName = entry.name; + } + }); + }); + + /** + * Add navigation items by analyzing the HTML content and searching for h1 and h2 tags + * @param nav Object the navigation array + * @param content string the compiled HTML content + * @param index where to insert items + * @return boolean true if any good-looking (i.e. with a group identifier)

tag was found + */ + function add_nav(nav, content, index) { + var found_level1 = false; + if ( ! content) { + return found_level1; + } + var topics = content.match(/(.+?)<\/h(1|2)>/gi); + if ( topics ) { + topics.forEach(function(entry) { + var level = entry.substring(2,3); + var title = entry.replace(/<.+?>/g, ''); // Remove all HTML tags for the title + var entry_tags = entry.match(/id="api-([^\-]+)(?:-(.+))?"/); // Find the group and name in the id property + var group = (entry_tags ? entry_tags[1] : null); + var name = (entry_tags ? entry_tags[2] : null); + if (level==1 && title && group) { + nav.splice(index, 0, { + group: group, + isHeader: true, + title: title, + isFixed: true + }); + index++; + found_level1 = true; + } + if (level==2 && title && group && name) { + nav.splice(index, 0, { + group: group, + name: name, + isHeader: false, + title: title, + isFixed: false, + version: '1.0' + }); + index++; + } + }); + } + return found_level1; + } + + // Mainmenu Header entry + if (apiProject.header) { + var found_level1 = add_nav(nav, apiProject.header.content, 0); // Add level 1 and 2 titles + if (!found_level1) { // If no Level 1 tags were found, make a title + nav.unshift({ + group: '_', + isHeader: true, + title: (apiProject.header.title == null) ? locale.__('General') : apiProject.header.title, + isFixed: true + }); + } + } + + // Mainmenu Footer entry + if (apiProject.footer) { + var last_nav_index = nav.length; + var found_level1 = add_nav(nav, apiProject.footer.content, nav.length); // Add level 1 and 2 titles + if (!found_level1 && apiProject.footer.title != null) { // If no Level 1 tags were found, make a title + nav.splice(last_nav_index, 0, { + group: '_footer', + isHeader: true, + title: apiProject.footer.title, + isFixed: true + }); + } + } + + // render pagetitle + var title = apiProject.title ? apiProject.title : 'apiDoc: ' + apiProject.name + ' - ' + apiProject.version; + $(document).attr('title', title); + + // remove loader + $('#loader').remove(); + + // render sidenav + var fields = { + nav: nav + }; + $('#sidenav').append( templateSidenav(fields) ); + + // render Generator + $('#generator').append( templateGenerator(apiProject) ); + + // render Project + _.extend(apiProject, { versions: apiVersions}); + $('#project').append( templateProject(apiProject) ); + + // render apiDoc, header/footer documentation + if (apiProject.header) + $('#header').append( templateHeader(apiProject.header) ); + + if (apiProject.footer) + $('#footer').append( templateFooter(apiProject.footer) ); + + // + // Render Sections and Articles + // + var articleVersions = {}; + var content = ''; + apiGroups.forEach(function(groupEntry) { + var articles = []; + var oldName = ''; + var fields = {}; + var title = groupEntry; + var description = ''; + articleVersions[groupEntry] = {}; + + // render all articles of a group + api.forEach(function(entry) { + if(groupEntry === entry.group) { + if (oldName !== entry.name) { + // determine versions + api.forEach(function(versionEntry) { + if (groupEntry === versionEntry.group && entry.name === versionEntry.name) { + if ( ! articleVersions[entry.group].hasOwnProperty(entry.name) ) { + articleVersions[entry.group][entry.name] = []; + } + articleVersions[entry.group][entry.name].push(versionEntry.version); + } + }); + fields = { + article: entry, + versions: articleVersions[entry.group][entry.name] + }; + } else { + fields = { + article: entry, + hidden: true, + versions: articleVersions[entry.group][entry.name] + }; + } + + // add prefix URL for endpoint + if (apiProject.url) + fields.article.url = apiProject.url + fields.article.url; + + addArticleSettings(fields, entry); + + if (entry.groupTitle) + title = entry.groupTitle; + + // TODO: make groupDescription compareable with older versions (not important for the moment) + if (entry.groupDescription) + description = entry.groupDescription; + + articles.push({ + article: templateArticle(fields), + group: entry.group, + name: entry.name + }); + oldName = entry.name; + } + }); + + // render Section with Articles + var fields = { + group: groupEntry, + title: title, + description: description, + articles: articles + }; + content += templateSections(fields); + }); + $('#sections').append( content ); + + // Bootstrap Scrollspy + $(this).scrollspy({ target: '#scrollingNav', offset: 18 }); + + // Content-Scroll on Navigation click. + $('.sidenav').find('a').on('click', function(e) { + e.preventDefault(); + var id = $(this).attr('href'); + if ($(id).length > 0) + $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 400); + window.location.hash = $(this).attr('href'); + }); + + // Quickjump on Pageload to hash position. + if(window.location.hash) { + var id = window.location.hash; + if ($(id).length > 0) + $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 0); + } + + /** + * Check if Parameter (sub) List has a type Field. + * Example: @apiSuccess varname1 No type. + * @apiSuccess {String} varname2 With type. + * + * @param {Object} fields + */ + function _hasTypeInFields(fields) { + var result = false; + $.each(fields, function(name) { + result = result || _.some(fields[name], function(item) { return item.type; }); + }); + return result; + } + + /** + * On Template changes, recall plugins. + */ + function initDynamic() { + // Bootstrap popover + $('button[data-toggle="popover"]').popover().click(function(e) { + e.preventDefault(); + }); + + var version = $('#version strong').html(); + $('#sidenav li').removeClass('is-new'); + if (apiProject.template.withCompare) { + $('#sidenav li[data-version=\'' + version + '\']').each(function(){ + var group = $(this).data('group'); + var name = $(this).data('name'); + var length = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').length; + var index = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').index($(this)); + if (length === 1 || index === (length - 1)) + $(this).addClass('is-new'); + }); + } + + // tabs + $('.nav-tabs-examples a').click(function (e) { + e.preventDefault(); + $(this).tab('show'); + }); + $('.nav-tabs-examples').find('a:first').tab('show'); + + // sample request switch + $('.sample-request-switch').click(function (e) { + var name = '.' + $(this).attr('name') + '-fields'; + $(name).addClass('hide'); + $(this).parent().next(name).removeClass('hide'); + }); + + // call scrollspy refresh method + $(window).scrollspy('refresh'); + + // init modules + sampleRequest.initDynamic(); + } + initDynamic(); + + // Pre- / Code-Format + prettyPrint(); + + // + // HTML-Template specific jQuery-Functions + // + // Change Main Version + $('#versions li.version a').on('click', function(e) { + e.preventDefault(); + + var selectedVersion = $(this).html(); + $('#version strong').html(selectedVersion); + + // hide all + $('article').addClass('hide'); + $('#sidenav li:not(.nav-fixed)').addClass('hide'); + + // show 1st equal or lower Version of each entry + $('article[data-version]').each(function(index) { + var group = $(this).data('group'); + var name = $(this).data('name'); + var version = $(this).data('version'); + + if (semver.lte(version, selectedVersion)) { + if ($('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible').length === 0) { + // enable Article + $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide'); + // enable Navigation + $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide'); + $('#sidenav li.nav-header[data-group=\'' + group + '\']').removeClass('hide'); + } + } + }); + + // show 1st equal or lower Version of each entry + $('article[data-version]').each(function(index) { + var group = $(this).data('group'); + $('section#api-' + group).removeClass('hide'); + if ($('section#api-' + group + ' article:visible').length === 0) { + $('section#api-' + group).addClass('hide'); + } else { + $('section#api-' + group).removeClass('hide'); + } + }); + + initDynamic(); + return; + }); + + // compare all article with their predecessor + $('#compareAllWithPredecessor').on('click', changeAllVersionCompareTo); + + // change version of an article + $('article .versions li.version a').on('click', changeVersionCompareTo); + + // compare url-parameter + $.urlParam = function(name) { + var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href); + return (results && results[1]) ? results[1] : null; + }; + + if ($.urlParam('compare')) { + // URL Paramter ?compare=1 is set + $('#compareAllWithPredecessor').trigger('click'); + + if (window.location.hash) { + var id = window.location.hash; + $('html,body').animate({ scrollTop: parseInt($(id).offset().top) - 18 }, 0); + } + } + + /** + * Initialize search + */ + var options = { + valueNames: [ 'nav-list-item' ] + }; + var endpointsList = new List('scrollingNav', options); + + /** + * Set initial focus to search input + */ + $('#scrollingNav .sidenav-search input.search').focus(); + + /** + * Detect ESC key to reset search + */ + $(document).keyup(function(e) { + if (e.keyCode === 27) $('span.search-reset').click(); + }); + + /** + * Search reset + */ + $('span.search-reset').on('click', function() { + $('#scrollingNav .sidenav-search input.search') + .val("") + .focus() + ; + endpointsList.search(); + }); + + /** + * Change version of an article to compare it to an other version. + */ + function changeVersionCompareTo(e) { + e.preventDefault(); + + var $root = $(this).parents('article'); + var selectedVersion = $(this).html(); + var $button = $root.find('.version'); + var currentVersion = $button.find('strong').html(); + $button.find('strong').html(selectedVersion); + + var group = $root.data('group'); + var name = $root.data('name'); + var version = $root.data('version'); + + var compareVersion = $root.data('compare-version'); + + if (compareVersion === selectedVersion) + return; + + if ( ! compareVersion && version == selectedVersion) + return; + + if (compareVersion && articleVersions[group][name][0] === selectedVersion || version === selectedVersion) { + // the version of the entry is set to the highest version (reset) + resetArticle(group, name, version); + } else { + var $compareToArticle = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + selectedVersion + '\']'); + + var sourceEntry = {}; + var compareEntry = {}; + $.each(apiByGroupAndName[group][name], function(index, entry) { + if (entry.version === version) + sourceEntry = entry; + if (entry.version === selectedVersion) + compareEntry = entry; + }); + + var fields = { + article: sourceEntry, + compare: compareEntry, + versions: articleVersions[group][name] + }; + + // add unique id + // TODO: replace all group-name-version in template with id. + fields.article.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version; + fields.article.id = fields.article.id.replace(/\./g, '_'); + + fields.compare.id = fields.compare.group + '-' + fields.compare.name + '-' + fields.compare.version; + fields.compare.id = fields.compare.id.replace(/\./g, '_'); + + var entry = sourceEntry; + if (entry.parameter && entry.parameter.fields) + fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields); + + if (entry.error && entry.error.fields) + fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields); + + if (entry.success && entry.success.fields) + fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields); + + if (entry.info && entry.info.fields) + fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields); + + var entry = compareEntry; + if (fields._hasTypeInParameterFields !== true && entry.parameter && entry.parameter.fields) + fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields); + + if (fields._hasTypeInErrorFields !== true && entry.error && entry.error.fields) + fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields); + + if (fields._hasTypeInSuccessFields !== true && entry.success && entry.success.fields) + fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields); + + if (fields._hasTypeInInfoFields !== true && entry.info && entry.info.fields) + fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields); + + var content = templateCompareArticle(fields); + $root.after(content); + var $content = $root.next(); + + // Event on.click re-assign + $content.find('.versions li.version a').on('click', changeVersionCompareTo); + + // select navigation + $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + currentVersion + '\']').addClass('has-modifications'); + + $root.remove(); + // TODO: on change main version or select the highest version re-render + } + + initDynamic(); + } + + /** + * Compare all currently selected Versions with their predecessor. + */ + function changeAllVersionCompareTo(e) { + e.preventDefault(); + $('article:visible .versions').each(function(){ + var $root = $(this).parents('article'); + var currentVersion = $root.data('version'); + var $foundElement = null; + $(this).find('li.version a').each(function() { + var selectVersion = $(this).html(); + if (selectVersion < currentVersion && ! $foundElement) + $foundElement = $(this); + }); + + if($foundElement) + $foundElement.trigger('click'); + }); + initDynamic(); + } + + /** + * Sort the fields. + */ + function sortFields(fields_object) { + $.each(fields_object, function (key, fields) { + + var reversed = fields.slice().reverse() + + var max_dot_count = Math.max.apply(null, reversed.map(function (item) { + return item.field.split(".").length - 1; + })) + + for (var dot_count = 1; dot_count <= max_dot_count; dot_count++) { + reversed.forEach(function (item, index) { + var parts = item.field.split("."); + if (parts.length - 1 == dot_count) { + var fields_names = fields.map(function (item) { return item.field; }); + if (parts.slice(1).length >= 1) { + var prefix = parts.slice(0, parts.length - 1).join("."); + var prefix_index = fields_names.indexOf(prefix); + if (prefix_index > -1) { + fields.splice(fields_names.indexOf(item.field), 1); + fields.splice(prefix_index + 1, 0, item); + } + } + } + }); + } + }); + } + + /** + * Add article settings. + */ + function addArticleSettings(fields, entry) { + // add unique id + // TODO: replace all group-name-version in template with id. + fields.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version; + fields.id = fields.id.replace(/\./g, '_'); + + if (entry.header && entry.header.fields) { + sortFields(entry.header.fields); + fields._hasTypeInHeaderFields = _hasTypeInFields(entry.header.fields); + } + + if (entry.parameter && entry.parameter.fields) { + sortFields(entry.parameter.fields); + fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields); + } + + if (entry.error && entry.error.fields) { + sortFields(entry.error.fields); + fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields); + } + + if (entry.success && entry.success.fields) { + sortFields(entry.success.fields); + fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields); + } + + if (entry.info && entry.info.fields) { + sortFields(entry.info.fields); + fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields); + } + + // add template settings + fields.template = apiProject.template; + } + + /** + * Render Article. + */ + function renderArticle(group, name, version) { + var entry = {}; + $.each(apiByGroupAndName[group][name], function(index, currentEntry) { + if (currentEntry.version === version) + entry = currentEntry; + }); + var fields = { + article: entry, + versions: articleVersions[group][name] + }; + + addArticleSettings(fields, entry); + + return templateArticle(fields); + } + + /** + * Render original Article and remove the current visible Article. + */ + function resetArticle(group, name, version) { + var $root = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible'); + var content = renderArticle(group, name, version); + + $root.after(content); + var $content = $root.next(); + + // Event on.click muss neu zugewiesen werden (sollte eigentlich mit on automatisch funktionieren... sollte) + $content.find('.versions li.version a').on('click', changeVersionCompareTo); + + $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('has-modifications'); + + $root.remove(); + return; + } + + /** + * Load google fonts. + */ + function loadGoogleFontCss() { + WebFont.load({ + active: function() { + // Update scrollspy + $(window).scrollspy('refresh') + }, + google: { + families: ['Source Code Pro', 'Source Sans Pro:n4,n6,n7'] + } + }); + } + + /** + * Return ordered entries by custom order and append not defined entries to the end. + * @param {String[]} elements + * @param {String[]} order + * @param {String} splitBy + * @return {String[]} Custom ordered list. + */ + function sortByOrder(elements, order, splitBy) { + var results = []; + order.forEach (function(name) { + if (splitBy) + elements.forEach (function(element) { + var parts = element.split(splitBy); + var key = parts[1]; // reference keep for sorting + if (key == name) + results.push(element); + }); + else + elements.forEach (function(key) { + if (key == name) + results.push(name); + }); + }); + // Append all other entries that ar not defined in order + elements.forEach(function(element) { + if (results.indexOf(element) === -1) + results.push(element); + }); + return results; + } + +}); diff --git a/doc/utils/handlebars_helper.js b/doc/utils/handlebars_helper.js new file mode 100644 index 000000000..a5d5c4fdc --- /dev/null +++ b/doc/utils/handlebars_helper.js @@ -0,0 +1,357 @@ +define([ + 'locales', + 'handlebars', + 'diffMatchPatch' +], function(locale, Handlebars, DiffMatchPatch) { + + /** + * Return a text as markdown. + * Currently only a little helper to replace apidoc-inline Links (#Group:Name). + * Should be replaced with a full markdown lib. + * @param string text + */ + Handlebars.registerHelper('markdown', function(text) { + if ( ! text ) { + return text; + } + text = text.replace(/((\[(.*?)\])?\(#)((.+?):(.+?))(\))/mg, function(match, p1, p2, p3, p4, p5, p6) { + var link = p3 || p5 + '/' + p6; + return '' + link + ''; + }); + return text; + }); + + /** + * start/stop timer for simple performance check. + */ + var timer; + Handlebars.registerHelper('startTimer', function(text) { + timer = new Date(); + return ''; + }); + + Handlebars.registerHelper('stopTimer', function(text) { + console.log(new Date() - timer); + return ''; + }); + + /** + * Return localized Text. + * @param string text + */ + Handlebars.registerHelper('__', function(text) { + return locale.__(text); + }); + + /** + * Console log. + * @param mixed obj + */ + Handlebars.registerHelper('cl', function(obj) { + console.log(obj); + return ''; + }); + + /** + * Replace underscore with space. + * @param string text + */ + Handlebars.registerHelper('underscoreToSpace', function(text) { + return text.replace(/(_+)/g, ' '); + }); + + /** + * + */ + Handlebars.registerHelper('assign', function(name) { + if(arguments.length > 0) { + var type = typeof(arguments[1]); + var arg = null; + if(type === 'string' || type === 'number' || type === 'boolean') arg = arguments[1]; + Handlebars.registerHelper(name, function() { return arg; }); + } + return ''; + }); + + /** + * + */ + Handlebars.registerHelper('nl2br', function(text) { + return _handlebarsNewlineToBreak(text); + }); + + /** + * + */ + Handlebars.registerHelper('if_eq', function(context, options) { + var compare = context; + // Get length if context is an object + if (context instanceof Object && ! (options.hash.compare instanceof Object)) + compare = Object.keys(context).length; + + if (compare === options.hash.compare) + return options.fn(this); + + return options.inverse(this); + }); + + /** + * + */ + Handlebars.registerHelper('if_gt', function(context, options) { + var compare = context; + // Get length if context is an object + if (context instanceof Object && ! (options.hash.compare instanceof Object)) + compare = Object.keys(context).length; + + if(compare > options.hash.compare) + return options.fn(this); + + return options.inverse(this); + }); + + /** + * + */ + var templateCache = {}; + Handlebars.registerHelper('subTemplate', function(name, sourceContext) { + if ( ! templateCache[name]) + templateCache[name] = Handlebars.compile($('#template-' + name).html()); + + var template = templateCache[name]; + var templateContext = $.extend({}, this, sourceContext.hash); + return new Handlebars.SafeString( template(templateContext) ); + }); + + /** + * + */ + Handlebars.registerHelper('toLowerCase', function(value) { + return (value && typeof value === 'string') ? value.toLowerCase() : ''; + }); + + /** + * + */ + Handlebars.registerHelper('splitFill', function(value, splitChar, fillChar) { + var splits = value.split(splitChar); + return new Array(splits.length).join(fillChar) + splits[splits.length - 1]; + }); + + /** + * Convert Newline to HTML-Break (nl2br). + * + * @param {String} text + * @returns {String} + */ + function _handlebarsNewlineToBreak(text) { + return ('' + text).replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '
' + '$2'); + } + + /** + * + */ + Handlebars.registerHelper('each_compare_list_field', function(source, compare, options) { + var fieldName = options.hash.field; + var newSource = []; + if (source) { + source.forEach(function(entry) { + var values = entry; + values['key'] = entry[fieldName]; + newSource.push(values); + }); + } + + var newCompare = []; + if (compare) { + compare.forEach(function(entry) { + var values = entry; + values['key'] = entry[fieldName]; + newCompare.push(values); + }); + } + return _handlebarsEachCompared('key', newSource, newCompare, options); + }); + + /** + * + */ + Handlebars.registerHelper('each_compare_keys', function(source, compare, options) { + var newSource = []; + if (source) { + var sourceFields = Object.keys(source); + sourceFields.forEach(function(name) { + var values = {}; + values['value'] = source[name]; + values['key'] = name; + newSource.push(values); + }); + } + + var newCompare = []; + if (compare) { + var compareFields = Object.keys(compare); + compareFields.forEach(function(name) { + var values = {}; + values['value'] = compare[name]; + values['key'] = name; + newCompare.push(values); + }); + } + return _handlebarsEachCompared('key', newSource, newCompare, options); + }); + + /** + * + */ + Handlebars.registerHelper('each_compare_field', function(source, compare, options) { + return _handlebarsEachCompared('field', source, compare, options); + }); + + /** + * + */ + Handlebars.registerHelper('each_compare_title', function(source, compare, options) { + return _handlebarsEachCompared('title', source, compare, options); + }); + + /** + * + */ + Handlebars.registerHelper('reformat', function(source, type){ + if (type == 'json') + try { + return JSON.stringify(JSON.parse(source.trim()),null, " "); + } catch(e) { + + } + return source + }); + + /** + * + */ + Handlebars.registerHelper('showDiff', function(source, compare, options) { + var ds = ''; + if(source === compare) { + ds = source; + } else { + if( ! source) + return compare; + + if( ! compare) + return source; + + var d = diffMatchPatch.diff_main(compare, source); + diffMatchPatch.diff_cleanupSemantic(d); + ds = diffMatchPatch.diff_prettyHtml(d); + ds = ds.replace(/¶/gm, ''); + } + if(options === 'nl2br') + ds = _handlebarsNewlineToBreak(ds); + + return ds; + }); + + /** + * + */ + function _handlebarsEachCompared(fieldname, source, compare, options) + { + var dataList = []; + var index = 0; + if(source) { + source.forEach(function(sourceEntry) { + var found = false; + if (compare) { + compare.forEach(function(compareEntry) { + if(sourceEntry[fieldname] === compareEntry[fieldname]) { + var data = { + typeSame: true, + source: sourceEntry, + compare: compareEntry, + index: index + }; + dataList.push(data); + found = true; + index++; + } + }); + } + if ( ! found) { + var data = { + typeIns: true, + source: sourceEntry, + index: index + }; + dataList.push(data); + index++; + } + }); + } + + if (compare) { + compare.forEach(function(compareEntry) { + var found = false; + if (source) { + source.forEach(function(sourceEntry) { + if(sourceEntry[fieldname] === compareEntry[fieldname]) + found = true; + }); + } + if ( ! found) { + var data = { + typeDel: true, + compare: compareEntry, + index: index + }; + dataList.push(data); + index++; + } + }); + } + + var ret = ''; + var length = dataList.length; + for (var index in dataList) { + if(index == (length - 1)) + dataList[index]['_last'] = true; + ret = ret + options.fn(dataList[index]); + } + return ret; + } + + var diffMatchPatch = new DiffMatchPatch(); + + /** + * Overwrite Colors + */ + DiffMatchPatch.prototype.diff_prettyHtml = function(diffs) { + var html = []; + var pattern_amp = /&/g; + var pattern_lt = //g; + var pattern_para = /\n/g; + for (var x = 0; x < diffs.length; x++) { + var op = diffs[x][0]; // Operation (insert, delete, equal) + var data = diffs[x][1]; // Text of change. + var text = data.replace(pattern_amp, '&').replace(pattern_lt, '<') + .replace(pattern_gt, '>').replace(pattern_para, '¶
'); + switch (op) { + case DIFF_INSERT: + html[x] = '' + text + ''; + break; + case DIFF_DELETE: + html[x] = '' + text + ''; + break; + case DIFF_EQUAL: + html[x] = '' + text + ''; + break; + } + } + return html.join(''); + }; + + // Exports + return Handlebars; +}); diff --git a/doc/utils/send_sample_request.js b/doc/utils/send_sample_request.js new file mode 100755 index 000000000..a03877ec6 --- /dev/null +++ b/doc/utils/send_sample_request.js @@ -0,0 +1,182 @@ +define([ + 'jquery', + 'lodash' +], function($, _) { + + var initDynamic = function() { + // Button send + $(".sample-request-send").off("click"); + $(".sample-request-send").on("click", function(e) { + e.preventDefault(); + var $root = $(this).parents("article"); + var group = $root.data("group"); + var name = $root.data("name"); + var version = $root.data("version"); + sendSampleRequest(group, name, version, $(this).data("sample-request-type")); + }); + + // Button clear + $(".sample-request-clear").off("click"); + $(".sample-request-clear").on("click", function(e) { + e.preventDefault(); + var $root = $(this).parents("article"); + var group = $root.data("group"); + var name = $root.data("name"); + var version = $root.data("version"); + clearSampleRequest(group, name, version); + }); + }; // initDynamic + + function sendSampleRequest(group, name, version, type) + { + var $root = $('article[data-group="' + group + '"][data-name="' + name + '"][data-version="' + version + '"]'); + + // Optional header + var header = {}; + $root.find(".sample-request-header:checked").each(function(i, element) { + var group = $(element).data("sample-request-header-group-id"); + $root.find("[data-sample-request-header-group=\"" + group + "\"]").each(function(i, element) { + var key = $(element).data("sample-request-header-name"); + var value = element.value; + if ( ! element.optional && element.defaultValue !== '') { + value = element.defaultValue; + } + header[key] = value; + }); + }); + + // create JSON dictionary of parameters + var param = {}; + var paramType = {}; + $root.find(".sample-request-param:checked").each(function(i, element) { + var group = $(element).data("sample-request-param-group-id"); + $root.find("[data-sample-request-param-group=\"" + group + "\"]").each(function(i, element) { + var key = $(element).data("sample-request-param-name"); + var value = element.value; + if ( ! element.optional && element.defaultValue !== '') { + value = element.defaultValue; + } + param[key] = value; + paramType[key] = $(element).next().text(); + }); + }); + + // grab user-inputted URL + var url = $root.find(".sample-request-url").val(); + + // Insert url parameter + var pattern = pathToRegexp(url, null); + var matches = pattern.exec(url); + for (var i = 1; i < matches.length; i++) { + var key = matches[i].substr(1); + if (param[key] !== undefined) { + url = url.replace(matches[i], encodeURIComponent(param[key])); + + // remove URL parameters from list + delete param[key]; + } + } // for + + $root.find(".sample-request-response").fadeTo(250, 1); + $root.find(".sample-request-response-json").html("Loading..."); + refreshScrollSpy(); + + _.each( param, function( val, key ) { + var t = paramType[ key ].toLowerCase(); + if ( t === 'object' || t === 'array' ) { + try { + param[ key ] = JSON.parse( val ); + } catch (e) { + } + } + }); + + // send AJAX request, catch success or error callback + var ajaxRequest = { + url : url, + headers : header, + data : param, + type : type.toUpperCase(), + success : displaySuccess, + error : displayError + }; + + $.ajax(ajaxRequest); + + + function displaySuccess(data, status, jqXHR) { + var jsonResponse; + try { + jsonResponse = JSON.parse(jqXHR.responseText); + jsonResponse = JSON.stringify(jsonResponse, null, 4); + } catch (e) { + jsonResponse = data; + } + $root.find(".sample-request-response-json").html(jsonResponse); + refreshScrollSpy(); + }; + + function displayError(jqXHR, textStatus, error) { + var message = "Error " + jqXHR.status + ": " + error; + var jsonResponse; + try { + jsonResponse = JSON.parse(jqXHR.responseText); + jsonResponse = JSON.stringify(jsonResponse, null, 4); + } catch (e) { + jsonResponse = escape(jqXHR.responseText); + } + + if (jsonResponse) + message += "
" + jsonResponse; + + // flicker on previous error to make clear that there is a new response + if($root.find(".sample-request-response").is(":visible")) + $root.find(".sample-request-response").fadeTo(1, 0.1); + + $root.find(".sample-request-response").fadeTo(250, 1); + $root.find(".sample-request-response-json").html(message); + refreshScrollSpy(); + }; + } + + function clearSampleRequest(group, name, version) + { + var $root = $('article[data-group="' + group + '"][data-name="' + name + '"][data-version="' + version + '"]'); + + // hide sample response + $root.find(".sample-request-response-json").html(""); + $root.find(".sample-request-response").hide(); + + // reset value of parameters + $root.find(".sample-request-param").each(function(i, element) { + element.value = ""; + }); + + // restore default URL + var $urlElement = $root.find(".sample-request-url"); + $urlElement.val($urlElement.prop("defaultValue")); + + refreshScrollSpy(); + } + + function refreshScrollSpy() + { + $('[data-spy="scroll"]').each(function () { + $(this).scrollspy("refresh"); + }); + } + + function escapeHtml(str) { + var div = document.createElement("div"); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + } + + /** + * Exports. + */ + return { + initDynamic: initDynamic + }; + +}); diff --git a/doc/vendor/bootstrap.min.css b/doc/vendor/bootstrap.min.css new file mode 100644 index 000000000..ed3905e0e --- /dev/null +++ b/doc/vendor/bootstrap.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/doc/vendor/bootstrap.min.js b/doc/vendor/bootstrap.min.js new file mode 100644 index 000000000..9bcd2fcca --- /dev/null +++ b/doc/vendor/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth

',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/doc/vendor/diff_match_patch.min.js b/doc/vendor/diff_match_patch.min.js new file mode 100644 index 000000000..c41b51327 --- /dev/null +++ b/doc/vendor/diff_match_patch.min.js @@ -0,0 +1,49 @@ +(function(){function diff_match_patch(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=0.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=0.5;this.Patch_Margin=4;this.Match_MaxBits=32} +diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[[0,a]]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);var f=this.diff_commonSuffix(a,b),g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,b.length-f);a=this.diff_compute_(a, +b,e,d);c&&a.unshift([0,c]);g&&a.push([0,g]);this.diff_cleanupMerge(a);return a}; +diff_match_patch.prototype.diff_compute_=function(a,b,c,d){if(!a)return[[1,b]];if(!b)return[[-1,a]];var e=a.length>b.length?a:b,f=a.length>b.length?b:a,g=e.indexOf(f);return-1!=g?(c=[[1,e.substring(0,g)],[0,f],[1,e.substring(g+f.length)]],a.length>b.length&&(c[0][0]=c[2][0]=-1),c):1==f.length?[[-1,a],[1,b]]:(e=this.diff_halfMatch_(a,b))?(f=e[0],a=e[1],g=e[2],b=e[3],e=e[4],f=this.diff_main(f,g,c,d),c=this.diff_main(a,b,c,d),f.concat([[0,e]],c)):c&&100c);v++){for(var n=-v+r;n<=v-t;n+=2){var l=g+n,m;m=n==-v||n!=v&&j[l-1]d)t+=2;else if(s>e)r+=2;else if(q&&(l=g+k-n,0<=l&&l= +u)return this.diff_bisectSplit_(a,b,m,s,c)}}for(n=-v+p;n<=v-w;n+=2){l=g+n;u=n==-v||n!=v&&i[l-1]d)w+=2;else if(m>e)p+=2;else if(!q&&(l=g+k-n,0<=l&&(l=u)))return this.diff_bisectSplit_(a,b,m,s,c)}}return[[-1,a],[1,b]]}; +diff_match_patch.prototype.diff_bisectSplit_=function(a,b,c,d,e){var f=a.substring(0,c),g=b.substring(0,d);a=a.substring(c);b=b.substring(d);f=this.diff_main(f,g,!1,e);e=this.diff_main(a,b,!1,e);return f.concat(e)}; +diff_match_patch.prototype.diff_linesToChars_=function(a,b){function c(a){for(var b="",c=0,f=-1,g=d.length;fd?a=a.substring(c-d):c=a.length?[h,j,n,l,g]:null}if(0>=this.Diff_Timeout)return null; +var d=a.length>b.length?a:b,e=a.length>b.length?b:a;if(4>d.length||2*e.lengthd[4].length?g:d:d:g;var j;a.length>b.length?(g=h[0],d=h[1],e=h[2],j=h[3]):(e=h[0],j=h[1],g=h[2],d=h[3]);h=h[4];return[g,d,e,j,h]}; +diff_match_patch.prototype.diff_cleanupSemantic=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=0,h=0,j=0,i=0;f=e){if(d>=b.length/2||d>=c.length/2)a.splice(f,0,[0,c.substring(0,d)]),a[f-1][1]=b.substring(0,b.length-d),a[f+1][1]=c.substring(d),f++}else if(e>=b.length/2||e>=c.length/2)a.splice(f,0,[0,b.substring(0,e)]),a[f-1][0]=1,a[f-1][1]=c.substring(0,c.length-e),a[f+1][0]=-1,a[f+1][1]=b.substring(e),f++;f++}f++}}; +diff_match_patch.prototype.diff_cleanupSemanticLossless=function(a){function b(a,b){if(!a||!b)return 6;var c=a.charAt(a.length-1),d=b.charAt(0),e=c.match(diff_match_patch.nonAlphaNumericRegex_),f=d.match(diff_match_patch.nonAlphaNumericRegex_),g=e&&c.match(diff_match_patch.whitespaceRegex_),h=f&&d.match(diff_match_patch.whitespaceRegex_),c=g&&c.match(diff_match_patch.linebreakRegex_),d=h&&d.match(diff_match_patch.linebreakRegex_),i=c&&a.match(diff_match_patch.blanklineEndRegex_),j=d&&b.match(diff_match_patch.blanklineStartRegex_); +return i||j?5:c||d?4:e&&!g&&h?3:g||h?2:e||f?1:0}for(var c=1;c=i&&(i=k,g=d,h=e,j=f)}a[c-1][1]!=g&&(g?a[c-1][1]=g:(a.splice(c-1,1),c--),a[c][1]= +h,j?a[c+1][1]=j:(a.splice(c+1,1),c--))}c++}};diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;diff_match_patch.whitespaceRegex_=/\s/;diff_match_patch.linebreakRegex_=/[\r\n]/;diff_match_patch.blanklineEndRegex_=/\n\r?\n$/;diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/; +diff_match_patch.prototype.diff_cleanupEfficiency=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=!1,h=!1,j=!1,i=!1;fb)break;e=c;f=d}return a.length!=g&&-1===a[g][0]?f:f+(b-e)}; +diff_match_patch.prototype.diff_prettyHtml=function(a){for(var b=[],c=/&/g,d=//g,f=/\n/g,g=0;g");switch(h){case 1:b[g]=''+j+"";break;case -1:b[g]=''+j+"";break;case 0:b[g]=""+j+""}}return b.join("")}; +diff_match_patch.prototype.diff_text1=function(a){for(var b=[],c=0;cthis.Match_MaxBits)throw Error("Pattern too long for this browser.");var e=this.match_alphabet_(b),f=this,g=this.Match_Threshold,h=a.indexOf(b,c);-1!=h&&(g=Math.min(d(0,h),g),h=a.lastIndexOf(b,c+b.length),-1!=h&&(g=Math.min(d(0,h),g)));for(var j=1<=i;p--){var w=e[a.charAt(p-1)];k[p]=0===t?(k[p+1]<<1|1)&w:(k[p+1]<<1|1)&w|((r[p+1]|r[p])<<1|1)|r[p+1];if(k[p]&j&&(w=d(t,p-1),w<=g))if(g=w,h=p-1,h>c)i=Math.max(1,2*c-h);else break}if(d(t+1,c)>g)break;r=k}return h}; +diff_match_patch.prototype.match_alphabet_=function(a){for(var b={},c=0;c=2*this.Patch_Margin&& +e&&(this.patch_addContext_(a,h),c.push(a),a=new diff_match_patch.patch_obj,e=0,h=d,f=g)}1!==i&&(f+=k.length);-1!==i&&(g+=k.length)}e&&(this.patch_addContext_(a,h),c.push(a));return c};diff_match_patch.prototype.patch_deepCopy=function(a){for(var b=[],c=0;cthis.Match_MaxBits){if(j=this.match_main(b,h.substring(0,this.Match_MaxBits),g),-1!=j&&(i=this.match_main(b,h.substring(h.length-this.Match_MaxBits),g+h.length-this.Match_MaxBits),-1==i||j>=i))j=-1}else j=this.match_main(b,h,g); +if(-1==j)e[f]=!1,d-=a[f].length2-a[f].length1;else if(e[f]=!0,d=j-g,g=-1==i?b.substring(j,j+h.length):b.substring(j,i+this.Match_MaxBits),h==g)b=b.substring(0,j)+this.diff_text2(a[f].diffs)+b.substring(j+h.length);else if(g=this.diff_main(h,g,!1),h.length>this.Match_MaxBits&&this.diff_levenshtein(g)/h.length>this.Patch_DeleteThreshold)e[f]=!1;else{this.diff_cleanupSemanticLossless(g);for(var h=0,k,i=0;ie[0][1].length){var f=b-e[0][1].length;e[0][1]=c.substring(e[0][1].length)+e[0][1];d.start1-=f;d.start2-=f;d.length1+=f;d.length2+=f}d=a[a.length-1];e=d.diffs;0==e.length||0!=e[e.length-1][0]?(e.push([0, +c]),d.length1+=b,d.length2+=b):b>e[e.length-1][1].length&&(f=b-e[e.length-1][1].length,e[e.length-1][1]+=c.substring(0,f),d.length1+=f,d.length2+=f);return c}; +diff_match_patch.prototype.patch_splitMax=function(a){for(var b=this.Match_MaxBits,c=0;c2*b?(h.length1+=i.length,e+=i.length,j=!1,h.diffs.push([g,i]),d.diffs.shift()):(i=i.substring(0,b-h.length1-this.Patch_Margin),h.length1+=i.length,e+=i.length,0===g?(h.length2+=i.length,f+=i.length):j=!1,h.diffs.push([g,i]),i==d.diffs[0][1]?d.diffs.shift():d.diffs[0][1]=d.diffs[0][1].substring(i.length))}g=this.diff_text2(h.diffs);g=g.substring(g.length-this.Patch_Margin);i=this.diff_text1(d.diffs).substring(0,this.Patch_Margin);""!==i&& +(h.length1+=i.length,h.length2+=i.length,0!==h.diffs.length&&0===h.diffs[h.diffs.length-1][0]?h.diffs[h.diffs.length-1][1]+=i:h.diffs.push([0,i]));j||a.splice(++c,0,h)}}};diff_match_patch.prototype.patch_toText=function(a){for(var b=[],c=0;c= 2.0.0-beta.1",7:">= 4.0.0"};b.REVISION_CHANGES=o;var p="[object Object]";d.prototype={constructor:d,logger:l["default"],log:l["default"].log,registerHelper:function(a,b){if(f.toString.call(a)===p){if(b)throw new h["default"]("Arg not supported with multiple helpers");f.extend(this.helpers,a)}else this.helpers[a]=b},unregisterHelper:function(a){delete this.helpers[a]},registerPartial:function(a,b){if(f.toString.call(a)===p)f.extend(this.partials,a);else{if("undefined"==typeof b)throw new h["default"]('Attempting to register a partial called "'+a+'" as undefined');this.partials[a]=b}},unregisterPartial:function(a){delete this.partials[a]},registerDecorator:function(a,b){if(f.toString.call(a)===p){if(b)throw new h["default"]("Arg not supported with multiple decorators");f.extend(this.decorators,a)}else this.decorators[a]=b},unregisterDecorator:function(a){delete this.decorators[a]}};var q=l["default"].log;b.log=q,b.createFrame=f.createFrame,b.logger=l["default"]},function(a,b){"use strict";function c(a){return k[a]}function d(a){for(var b=1;bc;c++)if(a[c]===b)return c;return-1}function f(a){if("string"!=typeof a){if(a&&a.toHTML)return a.toHTML();if(null==a)return"";if(!a)return a+"";a=""+a}return m.test(a)?a.replace(l,c):a}function g(a){return a||0===a?p(a)&&0===a.length?!0:!1:!0}function h(a){var b=d({},a);return b._parent=a,b}function i(a,b){return a.path=b,a}function j(a,b){return(a?a+".":"")+b}b.__esModule=!0,b.extend=d,b.indexOf=e,b.escapeExpression=f,b.isEmpty=g,b.createFrame=h,b.blockParams=i,b.appendContextPath=j;var k={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","=":"="},l=/[&<>"'`=]/g,m=/[&<>"'`=]/,n=Object.prototype.toString;b.toString=n;var o=function(a){return"function"==typeof a};o(/x/)&&(b.isFunction=o=function(a){return"function"==typeof a&&"[object Function]"===n.call(a)}),b.isFunction=o;var p=Array.isArray||function(a){return a&&"object"==typeof a?"[object Array]"===n.call(a):!1};b.isArray=p},function(a,b){"use strict";function c(a,b){var e=b&&b.loc,f=void 0,g=void 0;e&&(f=e.start.line,g=e.start.column,a+=" - "+f+":"+g);for(var h=Error.prototype.constructor.call(this,a),i=0;i0?(c.ids&&(c.ids=[c.name]),a.helpers.each(b,c)):e(this);if(c.data&&c.ids){var g=d.createFrame(c.data);g.contextPath=d.appendContextPath(c.data.contextPath,c.name),c={data:g}}return f(b,c)})},a.exports=b["default"]},function(a,b,c){"use strict";var d=c(1)["default"];b.__esModule=!0;var e=c(5),f=c(6),g=d(f);b["default"]=function(a){a.registerHelper("each",function(a,b){function c(b,c,f){j&&(j.key=b,j.index=c,j.first=0===c,j.last=!!f,k&&(j.contextPath=k+b)),i+=d(a[b],{data:j,blockParams:e.blockParams([a[b],b],[k+b,null])})}if(!b)throw new g["default"]("Must pass iterator to #each");var d=b.fn,f=b.inverse,h=0,i="",j=void 0,k=void 0;if(b.data&&b.ids&&(k=e.appendContextPath(b.data.contextPath,b.ids[0])+"."),e.isFunction(a)&&(a=a.call(this)),b.data&&(j=e.createFrame(b.data)),a&&"object"==typeof a)if(e.isArray(a))for(var l=a.length;l>h;h++)h in a&&c(h,h,h===a.length-1);else{var m=void 0;for(var n in a)a.hasOwnProperty(n)&&(void 0!==m&&c(m,h-1),m=n,h++);void 0!==m&&c(m,h-1,!0)}return 0===h&&(i=f(this)),i})},a.exports=b["default"]},function(a,b,c){"use strict";var d=c(1)["default"];b.__esModule=!0;var e=c(6),f=d(e);b["default"]=function(a){a.registerHelper("helperMissing",function(){if(1!==arguments.length)throw new f["default"]('Missing helper: "'+arguments[arguments.length-1].name+'"')})},a.exports=b["default"]},function(a,b,c){"use strict";b.__esModule=!0;var d=c(5);b["default"]=function(a){a.registerHelper("if",function(a,b){return d.isFunction(a)&&(a=a.call(this)),!b.hash.includeZero&&!a||d.isEmpty(a)?b.inverse(this):b.fn(this)}),a.registerHelper("unless",function(b,c){return a.helpers["if"].call(this,b,{fn:c.inverse,inverse:c.fn,hash:c.hash})})},a.exports=b["default"]},function(a,b){"use strict";b.__esModule=!0,b["default"]=function(a){a.registerHelper("log",function(){for(var b=[void 0],c=arguments[arguments.length-1],d=0;d=0?b:parseInt(a,10)}return a},log:function(a){if(a=e.lookupLevel(a),"undefined"!=typeof console&&e.lookupLevel(e.level)<=a){var b=e.methodMap[a];console[b]||(b="log");for(var c=arguments.length,d=Array(c>1?c-1:0),f=1;c>f;f++)d[f-1]=arguments[f];console[b].apply(console,d)}}};b["default"]=e,a.exports=b["default"]},function(a,b){"use strict";function c(a){this.string=a}b.__esModule=!0,c.prototype.toString=c.prototype.toHTML=function(){return""+this.string},b["default"]=c,a.exports=b["default"]},function(a,b,c){"use strict";function d(a){var b=a&&a[0]||1,c=r.COMPILER_REVISION;if(b!==c){if(c>b){var d=r.REVISION_CHANGES[c],e=r.REVISION_CHANGES[b];throw new q["default"]("Template was precompiled with an older version of Handlebars than the current runtime. Please update your precompiler to a newer version ("+d+") or downgrade your runtime to an older version ("+e+").")}throw new q["default"]("Template was precompiled with a newer version of Handlebars than the current runtime. Please update your runtime to a newer version ("+a[1]+").")}}function e(a,b){function c(c,d,e){e.hash&&(d=o.extend({},d,e.hash),e.ids&&(e.ids[0]=!0)),c=b.VM.resolvePartial.call(this,c,d,e);var f=b.VM.invokePartial.call(this,c,d,e);if(null==f&&b.compile&&(e.partials[e.name]=b.compile(c,a.compilerOptions,b),f=e.partials[e.name](d,e)),null!=f){if(e.indent){for(var g=f.split("\n"),h=0,i=g.length;i>h&&(g[h]||h+1!==i);h++)g[h]=e.indent+g[h];f=g.join("\n")}return f}throw new q["default"]("The partial "+e.name+" could not be compiled when running in runtime-only mode")}function d(b){function c(b){return""+a.main(e,b,e.helpers,e.partials,g,i,h)}var f=arguments.length<=1||void 0===arguments[1]?{}:arguments[1],g=f.data;d._setup(f),!f.partial&&a.useData&&(g=j(b,g));var h=void 0,i=a.useBlockParams?[]:void 0;return a.useDepths&&(h=f.depths?b!==f.depths[0]?[b].concat(f.depths):f.depths:[b]),(c=k(a.main,c,e,f.depths||[],g,i))(b,f)}if(!b)throw new q["default"]("No environment passed to template");if(!a||!a.main)throw new q["default"]("Unknown template object: "+typeof a);a.main.decorator=a.main_d,b.VM.checkRevision(a.compiler);var e={strict:function(a,b){if(!(b in a))throw new q["default"]('"'+b+'" not defined in '+a);return a[b]},lookup:function(a,b){for(var c=a.length,d=0;c>d;d++)if(a[d]&&null!=a[d][b])return a[d][b]},lambda:function(a,b){return"function"==typeof a?a.call(b):a},escapeExpression:o.escapeExpression,invokePartial:c,fn:function(b){var c=a[b];return c.decorator=a[b+"_d"],c},programs:[],program:function(a,b,c,d,e){var g=this.programs[a],h=this.fn(a);return b||e||d||c?g=f(this,a,h,b,c,d,e):g||(g=this.programs[a]=f(this,a,h)),g},data:function(a,b){for(;a&&b--;)a=a._parent;return a},merge:function(a,b){var c=a||b;return a&&b&&a!==b&&(c=o.extend({},b,a)),c},noop:b.VM.noop,compilerInfo:a.compiler};return d.isTop=!0,d._setup=function(c){c.partial?(e.helpers=c.helpers,e.partials=c.partials,e.decorators=c.decorators):(e.helpers=e.merge(c.helpers,b.helpers),a.usePartial&&(e.partials=e.merge(c.partials,b.partials)),(a.usePartial||a.useDecorators)&&(e.decorators=e.merge(c.decorators,b.decorators)))},d._child=function(b,c,d,g){if(a.useBlockParams&&!d)throw new q["default"]("must pass block params");if(a.useDepths&&!g)throw new q["default"]("must pass parent depths");return f(e,b,a[b],c,0,d,g)},d}function f(a,b,c,d,e,f,g){function h(b){var e=arguments.length<=1||void 0===arguments[1]?{}:arguments[1],h=g;return g&&b!==g[0]&&(h=[b].concat(g)),c(a,b,a.helpers,a.partials,e.data||d,f&&[e.blockParams].concat(f),h)}return h=k(c,h,a,g,d,f),h.program=b,h.depth=g?g.length:0,h.blockParams=e||0,h}function g(a,b,c){return a?a.call||c.name||(c.name=a,a=c.partials[a]):a="@partial-block"===c.name?c.data["partial-block"]:c.partials[c.name],a}function h(a,b,c){c.partial=!0,c.ids&&(c.data.contextPath=c.ids[0]||c.data.contextPath);var d=void 0;if(c.fn&&c.fn!==i&&(c.data=r.createFrame(c.data),d=c.data["partial-block"]=c.fn,d.partials&&(c.partials=o.extend({},c.partials,d.partials))),void 0===a&&d&&(a=d),void 0===a)throw new q["default"]("The partial "+c.name+" could not be found");return a instanceof Function?a(b,c):void 0}function i(){return""}function j(a,b){return b&&"root"in b||(b=b?r.createFrame(b):{},b.root=a),b}function k(a,b,c,d,e,f){if(a.decorator){var g={};b=a.decorator(b,g,c,d&&d[0],e,f,d),o.extend(b,g)}return b}var l=c(3)["default"],m=c(1)["default"];b.__esModule=!0,b.checkRevision=d,b.template=e,b.wrapProgram=f,b.resolvePartial=g,b.invokePartial=h,b.noop=i;var n=c(5),o=l(n),p=c(6),q=m(p),r=c(4)},function(a,b){(function(c){"use strict";b.__esModule=!0,b["default"]=function(a){var b="undefined"!=typeof c?c:window,d=b.Handlebars;a.noConflict=function(){return b.Handlebars===a&&(b.Handlebars=d),a}},a.exports=b["default"]}).call(b,function(){return this}())},function(a,b){"use strict";b.__esModule=!0;var c={helpers:{helperExpression:function(a){return"SubExpression"===a.type||("MustacheStatement"===a.type||"BlockStatement"===a.type)&&!!(a.params&&a.params.length||a.hash)},scopedId:function(a){return/^\.|this\b/.test(a.original)},simpleId:function(a){return 1===a.parts.length&&!c.helpers.scopedId(a)&&!a.depth}}};b["default"]=c,a.exports=b["default"]},function(a,b,c){"use strict";function d(a,b){if("Program"===a.type)return a;h["default"].yy=n,n.locInfo=function(a){return new n.SourceLocation(b&&b.srcName,a)};var c=new j["default"](b);return c.accept(h["default"].parse(a))}var e=c(1)["default"],f=c(3)["default"];b.__esModule=!0,b.parse=d;var g=c(23),h=e(g),i=c(24),j=e(i),k=c(26),l=f(k),m=c(5);b.parser=h["default"];var n={};m.extend(n,l)},function(a,b){"use strict";var c=function(){function a(){this.yy={}}var b={trace:function(){},yy:{},symbols_:{error:2,root:3,program:4,EOF:5,program_repetition0:6,statement:7,mustache:8,block:9,rawBlock:10,partial:11,partialBlock:12,content:13,COMMENT:14,CONTENT:15,openRawBlock:16,rawBlock_repetition_plus0:17,END_RAW_BLOCK:18,OPEN_RAW_BLOCK:19,helperName:20,openRawBlock_repetition0:21,openRawBlock_option0:22,CLOSE_RAW_BLOCK:23,openBlock:24,block_option0:25,closeBlock:26,openInverse:27,block_option1:28,OPEN_BLOCK:29,openBlock_repetition0:30,openBlock_option0:31,openBlock_option1:32,CLOSE:33,OPEN_INVERSE:34,openInverse_repetition0:35,openInverse_option0:36,openInverse_option1:37,openInverseChain:38,OPEN_INVERSE_CHAIN:39,openInverseChain_repetition0:40,openInverseChain_option0:41,openInverseChain_option1:42,inverseAndProgram:43,INVERSE:44,inverseChain:45,inverseChain_option0:46,OPEN_ENDBLOCK:47,OPEN:48,mustache_repetition0:49,mustache_option0:50,OPEN_UNESCAPED:51,mustache_repetition1:52,mustache_option1:53,CLOSE_UNESCAPED:54,OPEN_PARTIAL:55,partialName:56,partial_repetition0:57,partial_option0:58,openPartialBlock:59,OPEN_PARTIAL_BLOCK:60,openPartialBlock_repetition0:61,openPartialBlock_option0:62,param:63,sexpr:64,OPEN_SEXPR:65,sexpr_repetition0:66,sexpr_option0:67,CLOSE_SEXPR:68,hash:69,hash_repetition_plus0:70,hashSegment:71,ID:72,EQUALS:73,blockParams:74,OPEN_BLOCK_PARAMS:75,blockParams_repetition_plus0:76,CLOSE_BLOCK_PARAMS:77,path:78,dataName:79,STRING:80,NUMBER:81,BOOLEAN:82,UNDEFINED:83,NULL:84,DATA:85,pathSegments:86,SEP:87,$accept:0,$end:1},terminals_:{2:"error",5:"EOF",14:"COMMENT",15:"CONTENT",18:"END_RAW_BLOCK",19:"OPEN_RAW_BLOCK",23:"CLOSE_RAW_BLOCK",29:"OPEN_BLOCK",33:"CLOSE",34:"OPEN_INVERSE",39:"OPEN_INVERSE_CHAIN",44:"INVERSE",47:"OPEN_ENDBLOCK",48:"OPEN",51:"OPEN_UNESCAPED",54:"CLOSE_UNESCAPED",55:"OPEN_PARTIAL",60:"OPEN_PARTIAL_BLOCK",65:"OPEN_SEXPR",68:"CLOSE_SEXPR",72:"ID",73:"EQUALS",75:"OPEN_BLOCK_PARAMS",77:"CLOSE_BLOCK_PARAMS",80:"STRING",81:"NUMBER",82:"BOOLEAN",83:"UNDEFINED",84:"NULL",85:"DATA",87:"SEP"},productions_:[0,[3,2],[4,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[13,1],[10,3],[16,5],[9,4],[9,4],[24,6],[27,6],[38,6],[43,2],[45,3],[45,1],[26,3],[8,5],[8,5],[11,5],[12,3],[59,5],[63,1],[63,1],[64,5],[69,1],[71,3],[74,3],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[56,1],[56,1],[79,2],[78,1],[86,3],[86,1],[6,0],[6,2],[17,1],[17,2],[21,0],[21,2],[22,0],[22,1],[25,0],[25,1],[28,0],[28,1],[30,0],[30,2],[31,0],[31,1],[32,0],[32,1],[35,0],[35,2],[36,0],[36,1],[37,0],[37,1],[40,0],[40,2],[41,0],[41,1],[42,0],[42,1],[46,0],[46,1],[49,0],[49,2],[50,0],[50,1],[52,0],[52,2],[53,0],[53,1],[57,0],[57,2],[58,0],[58,1],[61,0],[61,2],[62,0],[62,1],[66,0],[66,2],[67,0],[67,1],[70,1],[70,2],[76,1],[76,2]],performAction:function(a,b,c,d,e,f,g){var h=f.length-1;switch(e){case 1:return f[h-1];case 2:this.$=d.prepareProgram(f[h]);break;case 3:this.$=f[h];break;case 4:this.$=f[h];break;case 5:this.$=f[h];break;case 6:this.$=f[h];break;case 7:this.$=f[h];break;case 8:this.$=f[h];break;case 9:this.$={type:"CommentStatement",value:d.stripComment(f[h]),strip:d.stripFlags(f[h],f[h]),loc:d.locInfo(this._$)};break;case 10:this.$={type:"ContentStatement",original:f[h],value:f[h],loc:d.locInfo(this._$)};break;case 11:this.$=d.prepareRawBlock(f[h-2],f[h-1],f[h],this._$);break;case 12:this.$={path:f[h-3],params:f[h-2],hash:f[h-1]};break;case 13:this.$=d.prepareBlock(f[h-3],f[h-2],f[h-1],f[h],!1,this._$);break;case 14:this.$=d.prepareBlock(f[h-3],f[h-2],f[h-1],f[h],!0,this._$);break;case 15:this.$={open:f[h-5],path:f[h-4],params:f[h-3],hash:f[h-2],blockParams:f[h-1],strip:d.stripFlags(f[h-5],f[h])};break;case 16:this.$={path:f[h-4],params:f[h-3],hash:f[h-2],blockParams:f[h-1],strip:d.stripFlags(f[h-5],f[h])};break;case 17:this.$={path:f[h-4],params:f[h-3],hash:f[h-2],blockParams:f[h-1],strip:d.stripFlags(f[h-5],f[h])};break;case 18:this.$={strip:d.stripFlags(f[h-1],f[h-1]),program:f[h]};break;case 19:var i=d.prepareBlock(f[h-2],f[h-1],f[h],f[h],!1,this._$),j=d.prepareProgram([i],f[h-1].loc);j.chained=!0,this.$={strip:f[h-2].strip,program:j,chain:!0};break;case 20:this.$=f[h];break;case 21:this.$={path:f[h-1],strip:d.stripFlags(f[h-2],f[h])};break;case 22:this.$=d.prepareMustache(f[h-3],f[h-2],f[h-1],f[h-4],d.stripFlags(f[h-4],f[h]),this._$);break;case 23:this.$=d.prepareMustache(f[h-3],f[h-2],f[h-1],f[h-4],d.stripFlags(f[h-4],f[h]),this._$);break;case 24:this.$={type:"PartialStatement",name:f[h-3],params:f[h-2],hash:f[h-1],indent:"",strip:d.stripFlags(f[h-4],f[h]),loc:d.locInfo(this._$)};break;case 25:this.$=d.preparePartialBlock(f[h-2],f[h-1],f[h],this._$);break;case 26:this.$={path:f[h-3],params:f[h-2],hash:f[h-1],strip:d.stripFlags(f[h-4],f[h])};break;case 27:this.$=f[h];break;case 28:this.$=f[h];break;case 29:this.$={type:"SubExpression",path:f[h-3],params:f[h-2],hash:f[h-1],loc:d.locInfo(this._$)};break;case 30:this.$={type:"Hash",pairs:f[h],loc:d.locInfo(this._$)};break;case 31:this.$={type:"HashPair",key:d.id(f[h-2]),value:f[h],loc:d.locInfo(this._$)};break;case 32:this.$=d.id(f[h-1]);break;case 33:this.$=f[h];break;case 34:this.$=f[h];break;case 35:this.$={type:"StringLiteral",value:f[h],original:f[h],loc:d.locInfo(this._$)};break;case 36:this.$={type:"NumberLiteral",value:Number(f[h]),original:Number(f[h]),loc:d.locInfo(this._$)};break;case 37:this.$={type:"BooleanLiteral",value:"true"===f[h],original:"true"===f[h],loc:d.locInfo(this._$)};break;case 38:this.$={type:"UndefinedLiteral",original:void 0,value:void 0,loc:d.locInfo(this._$)};break;case 39:this.$={type:"NullLiteral",original:null,value:null,loc:d.locInfo(this._$)};break;case 40:this.$=f[h];break;case 41:this.$=f[h];break;case 42:this.$=d.preparePath(!0,f[h],this._$);break;case 43:this.$=d.preparePath(!1,f[h],this._$);break;case 44:f[h-2].push({part:d.id(f[h]),original:f[h],separator:f[h-1]}),this.$=f[h-2];break;case 45:this.$=[{part:d.id(f[h]),original:f[h]}];break;case 46:this.$=[];break;case 47:f[h-1].push(f[h]);break;case 48:this.$=[f[h]];break;case 49:f[h-1].push(f[h]);break;case 50:this.$=[];break;case 51:f[h-1].push(f[h]);break;case 58:this.$=[];break;case 59:f[h-1].push(f[h]);break;case 64:this.$=[];break;case 65:f[h-1].push(f[h]);break;case 70:this.$=[];break;case 71:f[h-1].push(f[h]);break;case 78:this.$=[];break;case 79:f[h-1].push(f[h]);break;case 82:this.$=[];break;case 83:f[h-1].push(f[h]);break;case 86:this.$=[];break;case 87:f[h-1].push(f[h]);break;case 90:this.$=[];break;case 91:f[h-1].push(f[h]);break;case 94:this.$=[];break;case 95:f[h-1].push(f[h]);break;case 98:this.$=[f[h]];break;case 99:f[h-1].push(f[h]);break;case 100:this.$=[f[h]];break;case 101:f[h-1].push(f[h])}},table:[{3:1,4:2,5:[2,46],6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{1:[3]},{5:[1,4]},{5:[2,2],7:5,8:6,9:7,10:8,11:9,12:10,13:11,14:[1,12],15:[1,20],16:17,19:[1,23],24:15,27:16,29:[1,21],34:[1,22],39:[2,2],44:[2,2],47:[2,2],48:[1,13],51:[1,14],55:[1,18],59:19,60:[1,24]},{1:[2,1]},{5:[2,47],14:[2,47],15:[2,47],19:[2,47],29:[2,47],34:[2,47],39:[2,47],44:[2,47],47:[2,47],48:[2,47],51:[2,47],55:[2,47],60:[2,47]},{5:[2,3],14:[2,3],15:[2,3],19:[2,3],29:[2,3],34:[2,3],39:[2,3],44:[2,3],47:[2,3],48:[2,3],51:[2,3],55:[2,3],60:[2,3]},{5:[2,4],14:[2,4],15:[2,4],19:[2,4],29:[2,4],34:[2,4],39:[2,4],44:[2,4],47:[2,4],48:[2,4],51:[2,4],55:[2,4],60:[2,4]},{5:[2,5],14:[2,5],15:[2,5],19:[2,5],29:[2,5],34:[2,5],39:[2,5],44:[2,5],47:[2,5],48:[2,5],51:[2,5],55:[2,5],60:[2,5]},{5:[2,6],14:[2,6],15:[2,6],19:[2,6],29:[2,6],34:[2,6],39:[2,6],44:[2,6],47:[2,6],48:[2,6],51:[2,6],55:[2,6],60:[2,6]},{5:[2,7],14:[2,7],15:[2,7],19:[2,7],29:[2,7],34:[2,7],39:[2,7],44:[2,7],47:[2,7],48:[2,7],51:[2,7],55:[2,7],60:[2,7]},{5:[2,8],14:[2,8],15:[2,8],19:[2,8],29:[2,8],34:[2,8],39:[2,8],44:[2,8],47:[2,8],48:[2,8],51:[2,8],55:[2,8],60:[2,8]},{5:[2,9],14:[2,9],15:[2,9],19:[2,9],29:[2,9],34:[2,9],39:[2,9],44:[2,9],47:[2,9],48:[2,9],51:[2,9],55:[2,9],60:[2,9]},{20:25,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:36,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{4:37,6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],39:[2,46],44:[2,46],47:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{4:38,6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],44:[2,46],47:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{13:40,15:[1,20],17:39},{20:42,56:41,64:43,65:[1,44],72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{4:45,6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],47:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{5:[2,10],14:[2,10],15:[2,10],18:[2,10],19:[2,10],29:[2,10],34:[2,10],39:[2,10],44:[2,10],47:[2,10],48:[2,10],51:[2,10],55:[2,10],60:[2,10]},{20:46,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:47,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:48,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:42,56:49,64:43,65:[1,44],72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{33:[2,78],49:50,65:[2,78],72:[2,78],80:[2,78],81:[2,78],82:[2,78],83:[2,78],84:[2,78],85:[2,78]},{23:[2,33],33:[2,33],54:[2,33],65:[2,33],68:[2,33],72:[2,33],75:[2,33],80:[2,33],81:[2,33],82:[2,33],83:[2,33],84:[2,33],85:[2,33]},{23:[2,34],33:[2,34],54:[2,34],65:[2,34],68:[2,34],72:[2,34],75:[2,34],80:[2,34],81:[2,34],82:[2,34],83:[2,34],84:[2,34],85:[2,34]},{23:[2,35],33:[2,35],54:[2,35],65:[2,35],68:[2,35],72:[2,35],75:[2,35],80:[2,35],81:[2,35],82:[2,35],83:[2,35],84:[2,35],85:[2,35]},{23:[2,36],33:[2,36],54:[2,36],65:[2,36],68:[2,36],72:[2,36],75:[2,36],80:[2,36],81:[2,36],82:[2,36],83:[2,36],84:[2,36],85:[2,36]},{23:[2,37],33:[2,37],54:[2,37],65:[2,37],68:[2,37],72:[2,37],75:[2,37],80:[2,37],81:[2,37],82:[2,37],83:[2,37],84:[2,37],85:[2,37]},{23:[2,38],33:[2,38],54:[2,38],65:[2,38],68:[2,38],72:[2,38],75:[2,38],80:[2,38],81:[2,38],82:[2,38],83:[2,38],84:[2,38],85:[2,38]},{23:[2,39],33:[2,39],54:[2,39],65:[2,39],68:[2,39],72:[2,39],75:[2,39],80:[2,39],81:[2,39],82:[2,39],83:[2,39],84:[2,39],85:[2,39]},{23:[2,43],33:[2,43],54:[2,43],65:[2,43],68:[2,43],72:[2,43],75:[2,43],80:[2,43],81:[2,43],82:[2,43],83:[2,43],84:[2,43],85:[2,43],87:[1,51]},{72:[1,35],86:52},{23:[2,45],33:[2,45],54:[2,45],65:[2,45],68:[2,45],72:[2,45],75:[2,45],80:[2,45],81:[2,45],82:[2,45],83:[2,45],84:[2,45],85:[2,45],87:[2,45]},{52:53,54:[2,82],65:[2,82],72:[2,82],80:[2,82],81:[2,82],82:[2,82],83:[2,82],84:[2,82],85:[2,82]},{25:54,38:56,39:[1,58],43:57,44:[1,59],45:55,47:[2,54]},{28:60,43:61,44:[1,59],47:[2,56]},{13:63,15:[1,20],18:[1,62]},{15:[2,48],18:[2,48]},{33:[2,86],57:64,65:[2,86],72:[2,86],80:[2,86],81:[2,86],82:[2,86],83:[2,86],84:[2,86],85:[2,86]},{33:[2,40],65:[2,40],72:[2,40],80:[2,40],81:[2,40],82:[2,40],83:[2,40],84:[2,40],85:[2,40]},{33:[2,41],65:[2,41],72:[2,41],80:[2,41],81:[2,41],82:[2,41],83:[2,41],84:[2,41],85:[2,41]},{20:65,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{26:66,47:[1,67]},{30:68,33:[2,58],65:[2,58],72:[2,58],75:[2,58],80:[2,58],81:[2,58],82:[2,58],83:[2,58],84:[2,58],85:[2,58]},{33:[2,64],35:69,65:[2,64],72:[2,64],75:[2,64],80:[2,64],81:[2,64],82:[2,64],83:[2,64],84:[2,64],85:[2,64]},{21:70,23:[2,50],65:[2,50],72:[2,50],80:[2,50],81:[2,50],82:[2,50],83:[2,50],84:[2,50],85:[2,50]},{33:[2,90],61:71,65:[2,90],72:[2,90],80:[2,90],81:[2,90],82:[2,90],83:[2,90],84:[2,90],85:[2,90]},{20:75,33:[2,80],50:72,63:73,64:76,65:[1,44],69:74,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{72:[1,80]},{23:[2,42],33:[2,42],54:[2,42],65:[2,42],68:[2,42],72:[2,42],75:[2,42],80:[2,42],81:[2,42],82:[2,42],83:[2,42],84:[2,42],85:[2,42],87:[1,51]},{20:75,53:81,54:[2,84],63:82,64:76,65:[1,44],69:83,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{26:84,47:[1,67]},{47:[2,55]},{4:85,6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],39:[2,46],44:[2,46],47:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{47:[2,20]},{20:86,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{4:87,6:3,14:[2,46],15:[2,46],19:[2,46],29:[2,46],34:[2,46],47:[2,46],48:[2,46],51:[2,46],55:[2,46],60:[2,46]},{26:88,47:[1,67]},{47:[2,57]},{5:[2,11],14:[2,11],15:[2,11],19:[2,11],29:[2,11],34:[2,11],39:[2,11],44:[2,11],47:[2,11],48:[2,11],51:[2,11],55:[2,11],60:[2,11]},{15:[2,49],18:[2,49]},{20:75,33:[2,88],58:89,63:90,64:76,65:[1,44],69:91,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{65:[2,94],66:92,68:[2,94],72:[2,94],80:[2,94],81:[2,94],82:[2,94],83:[2,94],84:[2,94],85:[2,94]},{5:[2,25],14:[2,25],15:[2,25],19:[2,25],29:[2,25],34:[2,25],39:[2,25],44:[2,25],47:[2,25],48:[2,25],51:[2,25],55:[2,25],60:[2,25]},{20:93,72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:75,31:94,33:[2,60],63:95,64:76,65:[1,44],69:96,70:77,71:78,72:[1,79],75:[2,60],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:75,33:[2,66],36:97,63:98,64:76,65:[1,44],69:99,70:77,71:78,72:[1,79],75:[2,66],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:75,22:100,23:[2,52],63:101,64:76,65:[1,44],69:102,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{20:75,33:[2,92],62:103,63:104,64:76,65:[1,44],69:105,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{33:[1,106]},{33:[2,79],65:[2,79],72:[2,79],80:[2,79],81:[2,79],82:[2,79],83:[2,79],84:[2,79],85:[2,79]},{33:[2,81]},{23:[2,27],33:[2,27],54:[2,27],65:[2,27],68:[2,27],72:[2,27],75:[2,27],80:[2,27],81:[2,27],82:[2,27],83:[2,27],84:[2,27],85:[2,27]},{23:[2,28],33:[2,28],54:[2,28],65:[2,28],68:[2,28],72:[2,28],75:[2,28],80:[2,28],81:[2,28],82:[2,28],83:[2,28],84:[2,28],85:[2,28]},{23:[2,30],33:[2,30],54:[2,30],68:[2,30],71:107,72:[1,108],75:[2,30]},{23:[2,98],33:[2,98],54:[2,98],68:[2,98],72:[2,98],75:[2,98]},{23:[2,45],33:[2,45],54:[2,45],65:[2,45],68:[2,45],72:[2,45],73:[1,109],75:[2,45],80:[2,45],81:[2,45],82:[2,45],83:[2,45],84:[2,45],85:[2,45],87:[2,45]},{23:[2,44],33:[2,44],54:[2,44],65:[2,44],68:[2,44],72:[2,44],75:[2,44],80:[2,44],81:[2,44],82:[2,44],83:[2,44],84:[2,44],85:[2,44],87:[2,44]},{54:[1,110]},{54:[2,83],65:[2,83],72:[2,83],80:[2,83],81:[2,83],82:[2,83],83:[2,83],84:[2,83],85:[2,83]},{54:[2,85]},{5:[2,13],14:[2,13],15:[2,13],19:[2,13],29:[2,13],34:[2,13],39:[2,13],44:[2,13],47:[2,13],48:[2,13],51:[2,13],55:[2,13],60:[2,13]},{38:56,39:[1,58],43:57,44:[1,59],45:112,46:111,47:[2,76]},{33:[2,70],40:113,65:[2,70],72:[2,70],75:[2,70],80:[2,70],81:[2,70],82:[2,70],83:[2,70],84:[2,70],85:[2,70]},{47:[2,18]},{5:[2,14],14:[2,14],15:[2,14],19:[2,14],29:[2,14],34:[2,14],39:[2,14],44:[2,14],47:[2,14],48:[2,14],51:[2,14],55:[2,14],60:[2,14]},{33:[1,114]},{33:[2,87],65:[2,87],72:[2,87],80:[2,87],81:[2,87],82:[2,87],83:[2,87],84:[2,87],85:[2,87]},{33:[2,89]},{20:75,63:116,64:76,65:[1,44],67:115,68:[2,96],69:117,70:77,71:78,72:[1,79],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{33:[1,118]},{32:119,33:[2,62],74:120,75:[1,121]},{33:[2,59],65:[2,59],72:[2,59],75:[2,59],80:[2,59],81:[2,59],82:[2,59],83:[2,59],84:[2,59],85:[2,59]},{33:[2,61],75:[2,61]},{33:[2,68],37:122,74:123,75:[1,121]},{33:[2,65],65:[2,65],72:[2,65],75:[2,65],80:[2,65],81:[2,65],82:[2,65],83:[2,65],84:[2,65],85:[2,65]},{33:[2,67],75:[2,67]},{23:[1,124]},{23:[2,51],65:[2,51],72:[2,51],80:[2,51],81:[2,51],82:[2,51],83:[2,51],84:[2,51],85:[2,51]},{23:[2,53]},{33:[1,125]},{33:[2,91],65:[2,91],72:[2,91],80:[2,91],81:[2,91],82:[2,91],83:[2,91],84:[2,91],85:[2,91]},{33:[2,93]},{5:[2,22],14:[2,22],15:[2,22],19:[2,22],29:[2,22],34:[2,22],39:[2,22],44:[2,22],47:[2,22],48:[2,22],51:[2,22],55:[2,22],60:[2,22]},{23:[2,99],33:[2,99],54:[2,99],68:[2,99],72:[2,99],75:[2,99]},{73:[1,109]},{20:75,63:126,64:76,65:[1,44],72:[1,35],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{5:[2,23],14:[2,23],15:[2,23],19:[2,23],29:[2,23],34:[2,23],39:[2,23],44:[2,23],47:[2,23],48:[2,23],51:[2,23],55:[2,23],60:[2,23]},{47:[2,19]},{47:[2,77]},{20:75,33:[2,72],41:127,63:128,64:76,65:[1,44],69:129,70:77,71:78,72:[1,79],75:[2,72],78:26,79:27,80:[1,28],81:[1,29],82:[1,30],83:[1,31],84:[1,32],85:[1,34],86:33},{5:[2,24],14:[2,24],15:[2,24],19:[2,24],29:[2,24],34:[2,24],39:[2,24],44:[2,24],47:[2,24],48:[2,24],51:[2,24],55:[2,24],60:[2,24]},{68:[1,130]},{65:[2,95],68:[2,95],72:[2,95],80:[2,95],81:[2,95],82:[2,95],83:[2,95],84:[2,95],85:[2,95]},{68:[2,97]},{5:[2,21],14:[2,21],15:[2,21],19:[2,21],29:[2,21],34:[2,21],39:[2,21],44:[2,21],47:[2,21],48:[2,21],51:[2,21],55:[2,21],60:[2,21]},{33:[1,131]},{33:[2,63]},{72:[1,133],76:132},{33:[1,134]},{33:[2,69]},{15:[2,12]},{14:[2,26],15:[2,26],19:[2,26],29:[2,26],34:[2,26],47:[2,26],48:[2,26],51:[2,26],55:[2,26],60:[2,26]},{23:[2,31],33:[2,31],54:[2,31],68:[2,31],72:[2,31],75:[2,31]},{33:[2,74],42:135,74:136,75:[1,121]},{33:[2,71],65:[2,71],72:[2,71],75:[2,71],80:[2,71],81:[2,71],82:[2,71],83:[2,71],84:[2,71],85:[2,71]},{33:[2,73],75:[2,73]},{23:[2,29],33:[2,29],54:[2,29],65:[2,29],68:[2,29],72:[2,29],75:[2,29],80:[2,29],81:[2,29],82:[2,29],83:[2,29],84:[2,29],85:[2,29]},{14:[2,15],15:[2,15],19:[2,15],29:[2,15],34:[2,15],39:[2,15],44:[2,15],47:[2,15],48:[2,15],51:[2,15],55:[2,15],60:[2,15]},{72:[1,138],77:[1,137]},{72:[2,100],77:[2,100]},{14:[2,16],15:[2,16],19:[2,16],29:[2,16],34:[2,16],44:[2,16],47:[2,16], +48:[2,16],51:[2,16],55:[2,16],60:[2,16]},{33:[1,139]},{33:[2,75]},{33:[2,32]},{72:[2,101],77:[2,101]},{14:[2,17],15:[2,17],19:[2,17],29:[2,17],34:[2,17],39:[2,17],44:[2,17],47:[2,17],48:[2,17],51:[2,17],55:[2,17],60:[2,17]}],defaultActions:{4:[2,1],55:[2,55],57:[2,20],61:[2,57],74:[2,81],83:[2,85],87:[2,18],91:[2,89],102:[2,53],105:[2,93],111:[2,19],112:[2,77],117:[2,97],120:[2,63],123:[2,69],124:[2,12],136:[2,75],137:[2,32]},parseError:function(a,b){throw new Error(a)},parse:function(a){function b(){var a;return a=c.lexer.lex()||1,"number"!=typeof a&&(a=c.symbols_[a]||a),a}var c=this,d=[0],e=[null],f=[],g=this.table,h="",i=0,j=0,k=0;this.lexer.setInput(a),this.lexer.yy=this.yy,this.yy.lexer=this.lexer,this.yy.parser=this,"undefined"==typeof this.lexer.yylloc&&(this.lexer.yylloc={});var l=this.lexer.yylloc;f.push(l);var m=this.lexer.options&&this.lexer.options.ranges;"function"==typeof this.yy.parseError&&(this.parseError=this.yy.parseError);for(var n,o,p,q,r,s,t,u,v,w={};;){if(p=d[d.length-1],this.defaultActions[p]?q=this.defaultActions[p]:((null===n||"undefined"==typeof n)&&(n=b()),q=g[p]&&g[p][n]),"undefined"==typeof q||!q.length||!q[0]){var x="";if(!k){v=[];for(s in g[p])this.terminals_[s]&&s>2&&v.push("'"+this.terminals_[s]+"'");x=this.lexer.showPosition?"Parse error on line "+(i+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+v.join(", ")+", got '"+(this.terminals_[n]||n)+"'":"Parse error on line "+(i+1)+": Unexpected "+(1==n?"end of input":"'"+(this.terminals_[n]||n)+"'"),this.parseError(x,{text:this.lexer.match,token:this.terminals_[n]||n,line:this.lexer.yylineno,loc:l,expected:v})}}if(q[0]instanceof Array&&q.length>1)throw new Error("Parse Error: multiple actions possible at state: "+p+", token: "+n);switch(q[0]){case 1:d.push(n),e.push(this.lexer.yytext),f.push(this.lexer.yylloc),d.push(q[1]),n=null,o?(n=o,o=null):(j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,l=this.lexer.yylloc,k>0&&k--);break;case 2:if(t=this.productions_[q[1]][1],w.$=e[e.length-t],w._$={first_line:f[f.length-(t||1)].first_line,last_line:f[f.length-1].last_line,first_column:f[f.length-(t||1)].first_column,last_column:f[f.length-1].last_column},m&&(w._$.range=[f[f.length-(t||1)].range[0],f[f.length-1].range[1]]),r=this.performAction.call(w,h,j,i,this.yy,q[1],e,f),"undefined"!=typeof r)return r;t&&(d=d.slice(0,-1*t*2),e=e.slice(0,-1*t),f=f.slice(0,-1*t)),d.push(this.productions_[q[1]][0]),e.push(w.$),f.push(w._$),u=g[d[d.length-2]][d[d.length-1]],d.push(u);break;case 3:return!0}}return!0}},c=function(){var a={EOF:1,parseError:function(a,b){if(!this.yy.parser)throw new Error(a);this.yy.parser.parseError(a,b)},setInput:function(a){return this._input=a,this._more=this._less=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var a=this._input[0];this.yytext+=a,this.yyleng++,this.offset++,this.match+=a,this.matched+=a;var b=a.match(/(?:\r\n?|\n).*/g);return b?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),a},unput:function(a){var b=a.length,c=a.split(/(?:\r\n?|\n)/g);this._input=a+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-b-1),this.offset-=b;var d=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),c.length-1&&(this.yylineno-=c.length-1);var e=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:c?(c.length===d.length?this.yylloc.first_column:0)+d[d.length-c.length].length-c[0].length:this.yylloc.first_column-b},this.options.ranges&&(this.yylloc.range=[e[0],e[0]+this.yyleng-b]),this},more:function(){return this._more=!0,this},less:function(a){this.unput(this.match.slice(a))},pastInput:function(){var a=this.matched.substr(0,this.matched.length-this.match.length);return(a.length>20?"...":"")+a.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var a=this.match;return a.length<20&&(a+=this._input.substr(0,20-a.length)),(a.substr(0,20)+(a.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var a=this.pastInput(),b=new Array(a.length+1).join("-");return a+this.upcomingInput()+"\n"+b+"^"},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var a,b,c,d,e;this._more||(this.yytext="",this.match="");for(var f=this._currentRules(),g=0;gb[0].length)||(b=c,d=g,this.options.flex));g++);return b?(e=b[0].match(/(?:\r\n?|\n).*/g),e&&(this.yylineno+=e.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:e?e[e.length-1].length-e[e.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+b[0].length},this.yytext+=b[0],this.match+=b[0],this.matches=b,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._input=this._input.slice(b[0].length),this.matched+=b[0],a=this.performAction.call(this,this.yy,this,f[d],this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),a?a:void 0):""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var a=this.next();return"undefined"!=typeof a?a:this.lex()},begin:function(a){this.conditionStack.push(a)},popState:function(){return this.conditionStack.pop()},_currentRules:function(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules},topState:function(){return this.conditionStack[this.conditionStack.length-2]},pushState:function(a){this.begin(a)}};return a.options={},a.performAction=function(a,b,c,d){function e(a,c){return b.yytext=b.yytext.substr(a,b.yyleng-c)}switch(c){case 0:if("\\\\"===b.yytext.slice(-2)?(e(0,1),this.begin("mu")):"\\"===b.yytext.slice(-1)?(e(0,1),this.begin("emu")):this.begin("mu"),b.yytext)return 15;break;case 1:return 15;case 2:return this.popState(),15;case 3:return this.begin("raw"),15;case 4:return this.popState(),"raw"===this.conditionStack[this.conditionStack.length-1]?15:(b.yytext=b.yytext.substr(5,b.yyleng-9),"END_RAW_BLOCK");case 5:return 15;case 6:return this.popState(),14;case 7:return 65;case 8:return 68;case 9:return 19;case 10:return this.popState(),this.begin("raw"),23;case 11:return 55;case 12:return 60;case 13:return 29;case 14:return 47;case 15:return this.popState(),44;case 16:return this.popState(),44;case 17:return 34;case 18:return 39;case 19:return 51;case 20:return 48;case 21:this.unput(b.yytext),this.popState(),this.begin("com");break;case 22:return this.popState(),14;case 23:return 48;case 24:return 73;case 25:return 72;case 26:return 72;case 27:return 87;case 28:break;case 29:return this.popState(),54;case 30:return this.popState(),33;case 31:return b.yytext=e(1,2).replace(/\\"/g,'"'),80;case 32:return b.yytext=e(1,2).replace(/\\'/g,"'"),80;case 33:return 85;case 34:return 82;case 35:return 82;case 36:return 83;case 37:return 84;case 38:return 81;case 39:return 75;case 40:return 77;case 41:return 72;case 42:return b.yytext=b.yytext.replace(/\\([\\\]])/g,"$1"),72;case 43:return"INVALID";case 44:return 5}},a.rules=[/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:\{\{\{\{(?=[^\/]))/,/^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/,/^(?:[^\x00]*?(?=(\{\{\{\{)))/,/^(?:[\s\S]*?--(~)?\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{\{\{)/,/^(?:\}\}\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#>)/,/^(?:\{\{(~)?#\*?)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^\s*(~)?\}\})/,/^(?:\{\{(~)?\s*else\s*(~)?\}\})/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{(~)?!--)/,/^(?:\{\{(~)?![\s\S]*?\}\})/,/^(?:\{\{(~)?\*?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)|])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:undefined(?=([~}\s)])))/,/^(?:null(?=([~}\s)])))/,/^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/,/^(?:as\s+\|)/,/^(?:\|)/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/,/^(?:\[(\\\]|[^\]])*\])/,/^(?:.)/,/^(?:$)/],a.conditions={mu:{rules:[7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],inclusive:!1},emu:{rules:[2],inclusive:!1},com:{rules:[6],inclusive:!1},raw:{rules:[3,4,5],inclusive:!1},INITIAL:{rules:[0,1,44],inclusive:!0}},a}();return b.lexer=c,a.prototype=b,b.Parser=a,new a}();b.__esModule=!0,b["default"]=c},function(a,b,c){"use strict";function d(){var a=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];this.options=a}function e(a,b,c){void 0===b&&(b=a.length);var d=a[b-1],e=a[b-2];return d?"ContentStatement"===d.type?(e||!c?/\r?\n\s*?$/:/(^|\r?\n)\s*?$/).test(d.original):void 0:c}function f(a,b,c){void 0===b&&(b=-1);var d=a[b+1],e=a[b+2];return d?"ContentStatement"===d.type?(e||!c?/^\s*?\r?\n/:/^\s*?(\r?\n|$)/).test(d.original):void 0:c}function g(a,b,c){var d=a[null==b?0:b+1];if(d&&"ContentStatement"===d.type&&(c||!d.rightStripped)){var e=d.value;d.value=d.value.replace(c?/^\s+/:/^[ \t]*\r?\n?/,""),d.rightStripped=d.value!==e}}function h(a,b,c){var d=a[null==b?a.length-1:b-1];if(d&&"ContentStatement"===d.type&&(c||!d.leftStripped)){var e=d.value;return d.value=d.value.replace(c?/\s+$/:/[ \t]+$/,""),d.leftStripped=d.value!==e,d.leftStripped}}var i=c(1)["default"];b.__esModule=!0;var j=c(25),k=i(j);d.prototype=new k["default"],d.prototype.Program=function(a){var b=!this.options.ignoreStandalone,c=!this.isRootSeen;this.isRootSeen=!0;for(var d=a.body,i=0,j=d.length;j>i;i++){var k=d[i],l=this.accept(k);if(l){var m=e(d,i,c),n=f(d,i,c),o=l.openStandalone&&m,p=l.closeStandalone&&n,q=l.inlineStandalone&&m&&n;l.close&&g(d,i,!0),l.open&&h(d,i,!0),b&&q&&(g(d,i),h(d,i)&&"PartialStatement"===k.type&&(k.indent=/([ \t]+$)/.exec(d[i-1].original)[1])),b&&o&&(g((k.program||k.inverse).body),h(d,i)),b&&p&&(g(d,i),h((k.inverse||k.program).body))}}return a},d.prototype.BlockStatement=d.prototype.DecoratorBlock=d.prototype.PartialBlockStatement=function(a){this.accept(a.program),this.accept(a.inverse);var b=a.program||a.inverse,c=a.program&&a.inverse,d=c,i=c;if(c&&c.chained)for(d=c.body[0].program;i.chained;)i=i.body[i.body.length-1].program;var j={open:a.openStrip.open,close:a.closeStrip.close,openStandalone:f(b.body),closeStandalone:e((d||b).body)};if(a.openStrip.close&&g(b.body,null,!0),c){var k=a.inverseStrip;k.open&&h(b.body,null,!0),k.close&&g(d.body,null,!0),a.closeStrip.open&&h(i.body,null,!0),!this.options.ignoreStandalone&&e(b.body)&&f(d.body)&&(h(b.body),g(d.body))}else a.closeStrip.open&&h(b.body,null,!0);return j},d.prototype.Decorator=d.prototype.MustacheStatement=function(a){return a.strip},d.prototype.PartialStatement=d.prototype.CommentStatement=function(a){var b=a.strip||{};return{inlineStandalone:!0,open:b.open,close:b.close}},b["default"]=d,a.exports=b["default"]},function(a,b,c){"use strict";function d(){this.parents=[]}function e(a){this.acceptRequired(a,"path"),this.acceptArray(a.params),this.acceptKey(a,"hash")}function f(a){e.call(this,a),this.acceptKey(a,"program"),this.acceptKey(a,"inverse")}function g(a){this.acceptRequired(a,"name"),this.acceptArray(a.params),this.acceptKey(a,"hash")}var h=c(1)["default"];b.__esModule=!0;var i=c(6),j=h(i);d.prototype={constructor:d,mutating:!1,acceptKey:function(a,b){var c=this.accept(a[b]);if(this.mutating){if(c&&!d.prototype[c.type])throw new j["default"]('Unexpected node type "'+c.type+'" found when accepting '+b+" on "+a.type);a[b]=c}},acceptRequired:function(a,b){if(this.acceptKey(a,b),!a[b])throw new j["default"](a.type+" requires "+b)},acceptArray:function(a){for(var b=0,c=a.length;c>b;b++)this.acceptKey(a,b),a[b]||(a.splice(b,1),b--,c--)},accept:function(a){if(a){if(!this[a.type])throw new j["default"]("Unknown type: "+a.type,a);this.current&&this.parents.unshift(this.current),this.current=a;var b=this[a.type](a);return this.current=this.parents.shift(),!this.mutating||b?b:b!==!1?a:void 0}},Program:function(a){this.acceptArray(a.body)},MustacheStatement:e,Decorator:e,BlockStatement:f,DecoratorBlock:f,PartialStatement:g,PartialBlockStatement:function(a){g.call(this,a),this.acceptKey(a,"program")},ContentStatement:function(){},CommentStatement:function(){},SubExpression:e,PathExpression:function(){},StringLiteral:function(){},NumberLiteral:function(){},BooleanLiteral:function(){},UndefinedLiteral:function(){},NullLiteral:function(){},Hash:function(a){this.acceptArray(a.pairs)},HashPair:function(a){this.acceptRequired(a,"value")}},b["default"]=d,a.exports=b["default"]},function(a,b,c){"use strict";function d(a,b){if(b=b.path?b.path.original:b,a.path.original!==b){var c={loc:a.path.loc};throw new q["default"](a.path.original+" doesn't match "+b,c)}}function e(a,b){this.source=a,this.start={line:b.first_line,column:b.first_column},this.end={line:b.last_line,column:b.last_column}}function f(a){return/^\[.*\]$/.test(a)?a.substr(1,a.length-2):a}function g(a,b){return{open:"~"===a.charAt(2),close:"~"===b.charAt(b.length-3)}}function h(a){return a.replace(/^\{\{~?\!-?-?/,"").replace(/-?-?~?\}\}$/,"")}function i(a,b,c){c=this.locInfo(c);for(var d=a?"@":"",e=[],f=0,g="",h=0,i=b.length;i>h;h++){var j=b[h].part,k=b[h].original!==j;if(d+=(b[h].separator||"")+j,k||".."!==j&&"."!==j&&"this"!==j)e.push(j);else{if(e.length>0)throw new q["default"]("Invalid path: "+d,{loc:c});".."===j&&(f++,g+="../")}}return{type:"PathExpression",data:a,depth:f,parts:e,original:d,loc:c}}function j(a,b,c,d,e,f){var g=d.charAt(3)||d.charAt(2),h="{"!==g&&"&"!==g,i=/\*/.test(d);return{type:i?"Decorator":"MustacheStatement",path:a,params:b,hash:c,escaped:h,strip:e,loc:this.locInfo(f)}}function k(a,b,c,e){d(a,c),e=this.locInfo(e);var f={type:"Program",body:b,strip:{},loc:e};return{type:"BlockStatement",path:a.path,params:a.params,hash:a.hash,program:f,openStrip:{},inverseStrip:{},closeStrip:{},loc:e}}function l(a,b,c,e,f,g){e&&e.path&&d(a,e);var h=/\*/.test(a.open);b.blockParams=a.blockParams;var i=void 0,j=void 0;if(c){if(h)throw new q["default"]("Unexpected inverse block on decorator",c);c.chain&&(c.program.body[0].closeStrip=e.strip),j=c.strip,i=c.program}return f&&(f=i,i=b,b=f),{type:h?"DecoratorBlock":"BlockStatement",path:a.path,params:a.params,hash:a.hash,program:b,inverse:i,openStrip:a.strip,inverseStrip:j,closeStrip:e&&e.strip,loc:this.locInfo(g)}}function m(a,b){if(!b&&a.length){var c=a[0].loc,d=a[a.length-1].loc;c&&d&&(b={source:c.source,start:{line:c.start.line,column:c.start.column},end:{line:d.end.line,column:d.end.column}})}return{type:"Program",body:a,strip:{},loc:b}}function n(a,b,c,e){return d(a,c),{type:"PartialBlockStatement",name:a.path,params:a.params,hash:a.hash,program:b,openStrip:a.strip,closeStrip:c&&c.strip,loc:this.locInfo(e)}}var o=c(1)["default"];b.__esModule=!0,b.SourceLocation=e,b.id=f,b.stripFlags=g,b.stripComment=h,b.preparePath=i,b.prepareMustache=j,b.prepareRawBlock=k,b.prepareBlock=l,b.prepareProgram=m,b.preparePartialBlock=n;var p=c(6),q=o(p)},function(a,b,c){"use strict";function d(){}function e(a,b,c){if(null==a||"string"!=typeof a&&"Program"!==a.type)throw new k["default"]("You must pass a string or Handlebars AST to Handlebars.precompile. You passed "+a);b=b||{},"data"in b||(b.data=!0),b.compat&&(b.useDepths=!0);var d=c.parse(a,b),e=(new c.Compiler).compile(d,b);return(new c.JavaScriptCompiler).compile(e,b)}function f(a,b,c){function d(){var d=c.parse(a,b),e=(new c.Compiler).compile(d,b),f=(new c.JavaScriptCompiler).compile(e,b,void 0,!0);return c.template(f)}function e(a,b){return f||(f=d()),f.call(this,a,b)}if(void 0===b&&(b={}),null==a||"string"!=typeof a&&"Program"!==a.type)throw new k["default"]("You must pass a string or Handlebars AST to Handlebars.compile. You passed "+a);"data"in b||(b.data=!0),b.compat&&(b.useDepths=!0);var f=void 0;return e._setup=function(a){return f||(f=d()),f._setup(a)},e._child=function(a,b,c,e){return f||(f=d()),f._child(a,b,c,e)},e}function g(a,b){if(a===b)return!0;if(l.isArray(a)&&l.isArray(b)&&a.length===b.length){for(var c=0;cc;c++){var d=this.opcodes[c],e=a.opcodes[c];if(d.opcode!==e.opcode||!g(d.args,e.args))return!1}b=this.children.length;for(var c=0;b>c;c++)if(!this.children[c].equals(a.children[c]))return!1;return!0},guid:0,compile:function(a,b){this.sourceNode=[],this.opcodes=[],this.children=[],this.options=b,this.stringParams=b.stringParams,this.trackIds=b.trackIds,b.blockParams=b.blockParams||[];var c=b.knownHelpers;if(b.knownHelpers={helperMissing:!0,blockHelperMissing:!0,each:!0,"if":!0,unless:!0,"with":!0,log:!0,lookup:!0},c)for(var d in c)d in c&&(b.knownHelpers[d]=c[d]);return this.accept(a)},compileProgram:function(a){var b=new this.compiler,c=b.compile(a,this.options),d=this.guid++;return this.usePartial=this.usePartial||c.usePartial,this.children[d]=c,this.useDepths=this.useDepths||c.useDepths,d},accept:function(a){if(!this[a.type])throw new k["default"]("Unknown type: "+a.type,a);this.sourceNode.unshift(a);var b=this[a.type](a);return this.sourceNode.shift(),b},Program:function(a){this.options.blockParams.unshift(a.blockParams);for(var b=a.body,c=b.length,d=0;c>d;d++)this.accept(b[d]);return this.options.blockParams.shift(),this.isSimple=1===c,this.blockParams=a.blockParams?a.blockParams.length:0,this},BlockStatement:function(a){h(a);var b=a.program,c=a.inverse;b=b&&this.compileProgram(b),c=c&&this.compileProgram(c);var d=this.classifySexpr(a);"helper"===d?this.helperSexpr(a,b,c):"simple"===d?(this.simpleSexpr(a),this.opcode("pushProgram",b),this.opcode("pushProgram",c),this.opcode("emptyHash"),this.opcode("blockValue",a.path.original)):(this.ambiguousSexpr(a,b,c),this.opcode("pushProgram",b),this.opcode("pushProgram",c),this.opcode("emptyHash"),this.opcode("ambiguousBlockValue")),this.opcode("append")},DecoratorBlock:function(a){var b=a.program&&this.compileProgram(a.program),c=this.setupFullMustacheParams(a,b,void 0),d=a.path;this.useDecorators=!0,this.opcode("registerDecorator",c.length,d.original)},PartialStatement:function(a){this.usePartial=!0;var b=a.program;b&&(b=this.compileProgram(a.program));var c=a.params;if(c.length>1)throw new k["default"]("Unsupported number of partial arguments: "+c.length,a);c.length||(this.options.explicitPartialContext?this.opcode("pushLiteral","undefined"):c.push({type:"PathExpression",parts:[],depth:0}));var d=a.name.original,e="SubExpression"===a.name.type;e&&this.accept(a.name),this.setupFullMustacheParams(a,b,void 0,!0);var f=a.indent||"";this.options.preventIndent&&f&&(this.opcode("appendContent",f),f=""),this.opcode("invokePartial",e,d,f),this.opcode("append")},PartialBlockStatement:function(a){this.PartialStatement(a)},MustacheStatement:function(a){this.SubExpression(a),a.escaped&&!this.options.noEscape?this.opcode("appendEscaped"):this.opcode("append")},Decorator:function(a){this.DecoratorBlock(a)},ContentStatement:function(a){a.value&&this.opcode("appendContent",a.value)},CommentStatement:function(){},SubExpression:function(a){h(a);var b=this.classifySexpr(a);"simple"===b?this.simpleSexpr(a):"helper"===b?this.helperSexpr(a):this.ambiguousSexpr(a)},ambiguousSexpr:function(a,b,c){var d=a.path,e=d.parts[0],f=null!=b||null!=c;this.opcode("getContext",d.depth),this.opcode("pushProgram",b),this.opcode("pushProgram",c),d.strict=!0,this.accept(d),this.opcode("invokeAmbiguous",e,f)},simpleSexpr:function(a){var b=a.path;b.strict=!0,this.accept(b),this.opcode("resolvePossibleLambda")},helperSexpr:function(a,b,c){var d=this.setupFullMustacheParams(a,b,c),e=a.path,f=e.parts[0];if(this.options.knownHelpers[f])this.opcode("invokeKnownHelper",d.length,f);else{if(this.options.knownHelpersOnly)throw new k["default"]("You specified knownHelpersOnly, but used the unknown helper "+f,a);e.strict=!0,e.falsy=!0,this.accept(e),this.opcode("invokeHelper",d.length,e.original,n["default"].helpers.simpleId(e))}},PathExpression:function(a){this.addDepth(a.depth),this.opcode("getContext",a.depth);var b=a.parts[0],c=n["default"].helpers.scopedId(a),d=!a.depth&&!c&&this.blockParamIndex(b);d?this.opcode("lookupBlockParam",d,a.parts):b?a.data?(this.options.data=!0,this.opcode("lookupData",a.depth,a.parts,a.strict)):this.opcode("lookupOnContext",a.parts,a.falsy,a.strict,c):this.opcode("pushContext")},StringLiteral:function(a){this.opcode("pushString",a.value)},NumberLiteral:function(a){this.opcode("pushLiteral",a.value)},BooleanLiteral:function(a){this.opcode("pushLiteral",a.value)},UndefinedLiteral:function(){this.opcode("pushLiteral","undefined")},NullLiteral:function(){this.opcode("pushLiteral","null")},Hash:function(a){var b=a.pairs,c=0,d=b.length;for(this.opcode("pushHash");d>c;c++)this.pushParam(b[c].value);for(;c--;)this.opcode("assignToHash",b[c].key);this.opcode("popHash")},opcode:function(a){this.opcodes.push({opcode:a,args:o.call(arguments,1),loc:this.sourceNode[0].loc})},addDepth:function(a){a&&(this.useDepths=!0)},classifySexpr:function(a){var b=n["default"].helpers.simpleId(a.path),c=b&&!!this.blockParamIndex(a.path.parts[0]),d=!c&&n["default"].helpers.helperExpression(a),e=!c&&(d||b);if(e&&!d){var f=a.path.parts[0],g=this.options;g.knownHelpers[f]?d=!0:g.knownHelpersOnly&&(e=!1)}return d?"helper":e?"ambiguous":"simple"},pushParams:function(a){for(var b=0,c=a.length;c>b;b++)this.pushParam(a[b])},pushParam:function(a){var b=null!=a.value?a.value:a.original||"";if(this.stringParams)b.replace&&(b=b.replace(/^(\.?\.\/)*/g,"").replace(/\//g,".")),a.depth&&this.addDepth(a.depth),this.opcode("getContext",a.depth||0),this.opcode("pushStringParam",b,a.type),"SubExpression"===a.type&&this.accept(a);else{if(this.trackIds){var c=void 0;if(!a.parts||n["default"].helpers.scopedId(a)||a.depth||(c=this.blockParamIndex(a.parts[0])),c){var d=a.parts.slice(1).join(".");this.opcode("pushId","BlockParam",c,d)}else b=a.original||b,b.replace&&(b=b.replace(/^this(?:\.|$)/,"").replace(/^\.\//,"").replace(/^\.$/,"")),this.opcode("pushId",a.type,b)}this.accept(a)}},setupFullMustacheParams:function(a,b,c,d){var e=a.params;return this.pushParams(e),this.opcode("pushProgram",b),this.opcode("pushProgram",c),a.hash?this.accept(a.hash):this.opcode("emptyHash",d),e},blockParamIndex:function(a){for(var b=0,c=this.options.blockParams.length;c>b;b++){var d=this.options.blockParams[b],e=d&&l.indexOf(d,a);if(d&&e>=0)return[b,e]}}}},function(a,b,c){"use strict";function d(a){this.value=a}function e(){}function f(a,b,c,d){var e=b.popStack(),f=0,g=c.length;for(a&&g--;g>f;f++)e=b.nameLookup(e,c[f],d);return a?[b.aliasable("container.strict"),"(",e,", ",b.quotedString(c[f]),")"]:e}var g=c(1)["default"];b.__esModule=!0;var h=c(4),i=c(6),j=g(i),k=c(5),l=c(29),m=g(l);e.prototype={nameLookup:function(a,b){return e.isValidJavaScriptVariableName(b)?[a,".",b]:[a,"[",JSON.stringify(b),"]"]},depthedLookup:function(a){return[this.aliasable("container.lookup"),'(depths, "',a,'")']},compilerInfo:function(){var a=h.COMPILER_REVISION,b=h.REVISION_CHANGES[a];return[a,b]},appendToBuffer:function(a,b,c){return k.isArray(a)||(a=[a]),a=this.source.wrap(a,b),this.environment.isSimple?["return ",a,";"]:c?["buffer += ",a,";"]:(a.appendToBuffer=!0,a)},initializeBuffer:function(){return this.quotedString("")},compile:function(a,b,c,d){this.environment=a,this.options=b,this.stringParams=this.options.stringParams,this.trackIds=this.options.trackIds,this.precompile=!d,this.name=this.environment.name,this.isChild=!!c,this.context=c||{decorators:[],programs:[],environments:[]},this.preamble(),this.stackSlot=0,this.stackVars=[],this.aliases={},this.registers={list:[]},this.hashes=[],this.compileStack=[],this.inlineStack=[],this.blockParams=[],this.compileChildren(a,b),this.useDepths=this.useDepths||a.useDepths||a.useDecorators||this.options.compat,this.useBlockParams=this.useBlockParams||a.useBlockParams;var e=a.opcodes,f=void 0,g=void 0,h=void 0,i=void 0;for(h=0,i=e.length;i>h;h++)f=e[h],this.source.currentLocation=f.loc,g=g||f.loc,this[f.opcode].apply(this,f.args);if(this.source.currentLocation=g,this.pushSource(""),this.stackSlot||this.inlineStack.length||this.compileStack.length)throw new j["default"]("Compile completed with content left on stack");this.decorators.isEmpty()?this.decorators=void 0:(this.useDecorators=!0,this.decorators.prepend("var decorators = container.decorators;\n"),this.decorators.push("return fn;"),d?this.decorators=Function.apply(this,["fn","props","container","depth0","data","blockParams","depths",this.decorators.merge()]):(this.decorators.prepend("function(fn, props, container, depth0, data, blockParams, depths) {\n"),this.decorators.push("}\n"),this.decorators=this.decorators.merge()));var k=this.createFunctionContext(d);if(this.isChild)return k;var l={compiler:this.compilerInfo(),main:k};this.decorators&&(l.main_d=this.decorators,l.useDecorators=!0);var m=this.context,n=m.programs,o=m.decorators;for(h=0,i=n.length;i>h;h++)n[h]&&(l[h]=n[h],o[h]&&(l[h+"_d"]=o[h],l.useDecorators=!0));return this.environment.usePartial&&(l.usePartial=!0),this.options.data&&(l.useData=!0),this.useDepths&&(l.useDepths=!0),this.useBlockParams&&(l.useBlockParams=!0),this.options.compat&&(l.compat=!0),d?l.compilerOptions=this.options:(l.compiler=JSON.stringify(l.compiler),this.source.currentLocation={start:{line:1,column:0}},l=this.objectLiteral(l),b.srcName?(l=l.toStringWithSourceMap({file:b.destName}),l.map=l.map&&l.map.toString()):l=l.toString()),l},preamble:function(){this.lastContext=0,this.source=new m["default"](this.options.srcName),this.decorators=new m["default"](this.options.srcName)},createFunctionContext:function(a){var b="",c=this.stackVars.concat(this.registers.list);c.length>0&&(b+=", "+c.join(", "));var d=0;for(var e in this.aliases){var f=this.aliases[e];this.aliases.hasOwnProperty(e)&&f.children&&f.referenceCount>1&&(b+=", alias"+ ++d+"="+e,f.children[0]="alias"+d)}var g=["container","depth0","helpers","partials","data"];(this.useBlockParams||this.useDepths)&&g.push("blockParams"),this.useDepths&&g.push("depths");var h=this.mergeSource(b);return a?(g.push(h),Function.apply(this,g)):this.source.wrap(["function(",g.join(","),") {\n ",h,"}"])},mergeSource:function(a){var b=this.environment.isSimple,c=!this.forceBuffer,d=void 0,e=void 0,f=void 0,g=void 0;return this.source.each(function(a){a.appendToBuffer?(f?a.prepend(" + "):f=a,g=a):(f&&(e?f.prepend("buffer += "):d=!0,g.add(";"),f=g=void 0),e=!0,b||(c=!1))}),c?f?(f.prepend("return "),g.add(";")):e||this.source.push('return "";'):(a+=", buffer = "+(d?"":this.initializeBuffer()),f?(f.prepend("return buffer + "),g.add(";")):this.source.push("return buffer;")),a&&this.source.prepend("var "+a.substring(2)+(d?"":";\n")),this.source.merge()},blockValue:function(a){var b=this.aliasable("helpers.blockHelperMissing"),c=[this.contextName(0)];this.setupHelperArgs(a,0,c);var d=this.popStack();c.splice(1,0,d),this.push(this.source.functionCall(b,"call",c))},ambiguousBlockValue:function(){var a=this.aliasable("helpers.blockHelperMissing"),b=[this.contextName(0)];this.setupHelperArgs("",0,b,!0),this.flushInline();var c=this.topStack();b.splice(1,0,c),this.pushSource(["if (!",this.lastHelper,") { ",c," = ",this.source.functionCall(a,"call",b),"}"])},appendContent:function(a){this.pendingContent?a=this.pendingContent+a:this.pendingLocation=this.source.currentLocation,this.pendingContent=a},append:function(){if(this.isInline())this.replaceStack(function(a){return[" != null ? ",a,' : ""']}),this.pushSource(this.appendToBuffer(this.popStack()));else{var a=this.popStack();this.pushSource(["if (",a," != null) { ",this.appendToBuffer(a,void 0,!0)," }"]),this.environment.isSimple&&this.pushSource(["else { ",this.appendToBuffer("''",void 0,!0)," }"])}},appendEscaped:function(){this.pushSource(this.appendToBuffer([this.aliasable("container.escapeExpression"),"(",this.popStack(),")"]))},getContext:function(a){this.lastContext=a},pushContext:function(){this.pushStackLiteral(this.contextName(this.lastContext))},lookupOnContext:function(a,b,c,d){var e=0;d||!this.options.compat||this.lastContext?this.pushContext():this.push(this.depthedLookup(a[e++])),this.resolvePath("context",a,e,b,c)},lookupBlockParam:function(a,b){this.useBlockParams=!0,this.push(["blockParams[",a[0],"][",a[1],"]"]),this.resolvePath("context",b,1)},lookupData:function(a,b,c){a?this.pushStackLiteral("container.data(data, "+a+")"):this.pushStackLiteral("data"),this.resolvePath("data",b,0,!0,c)},resolvePath:function(a,b,c,d,e){var g=this;if(this.options.strict||this.options.assumeObjects)return void this.push(f(this.options.strict&&e,this,b,a));for(var h=b.length;h>c;c++)this.replaceStack(function(e){var f=g.nameLookup(e,b[c],a);return d?[" && ",f]:[" != null ? ",f," : ",e]})},resolvePossibleLambda:function(){this.push([this.aliasable("container.lambda"),"(",this.popStack(),", ",this.contextName(0),")"])},pushStringParam:function(a,b){this.pushContext(),this.pushString(b),"SubExpression"!==b&&("string"==typeof a?this.pushString(a):this.pushStackLiteral(a))},emptyHash:function(a){this.trackIds&&this.push("{}"),this.stringParams&&(this.push("{}"),this.push("{}")),this.pushStackLiteral(a?"undefined":"{}")},pushHash:function(){this.hash&&this.hashes.push(this.hash),this.hash={values:[],types:[],contexts:[],ids:[]}},popHash:function(){var a=this.hash;this.hash=this.hashes.pop(),this.trackIds&&this.push(this.objectLiteral(a.ids)),this.stringParams&&(this.push(this.objectLiteral(a.contexts)),this.push(this.objectLiteral(a.types))),this.push(this.objectLiteral(a.values))},pushString:function(a){this.pushStackLiteral(this.quotedString(a))},pushLiteral:function(a){this.pushStackLiteral(a)},pushProgram:function(a){null!=a?this.pushStackLiteral(this.programExpression(a)):this.pushStackLiteral(null)},registerDecorator:function(a,b){var c=this.nameLookup("decorators",b,"decorator"),d=this.setupHelperArgs(b,a);this.decorators.push(["fn = ",this.decorators.functionCall(c,"",["fn","props","container",d])," || fn;"])},invokeHelper:function(a,b,c){var d=this.popStack(),e=this.setupHelper(a,b),f=c?[e.name," || "]:"",g=["("].concat(f,d);this.options.strict||g.push(" || ",this.aliasable("helpers.helperMissing")),g.push(")"),this.push(this.source.functionCall(g,"call",e.callParams))},invokeKnownHelper:function(a,b){var c=this.setupHelper(a,b);this.push(this.source.functionCall(c.name,"call",c.callParams))},invokeAmbiguous:function(a,b){this.useRegister("helper");var c=this.popStack();this.emptyHash();var d=this.setupHelper(0,a,b),e=this.lastHelper=this.nameLookup("helpers",a,"helper"),f=["(","(helper = ",e," || ",c,")"];this.options.strict||(f[0]="(helper = ",f.push(" != null ? helper : ",this.aliasable("helpers.helperMissing"))),this.push(["(",f,d.paramsInit?["),(",d.paramsInit]:[],"),","(typeof helper === ",this.aliasable('"function"')," ? ",this.source.functionCall("helper","call",d.callParams)," : helper))"])},invokePartial:function(a,b,c){var d=[],e=this.setupParams(b,1,d);a&&(b=this.popStack(),delete e.name),c&&(e.indent=JSON.stringify(c)),e.helpers="helpers",e.partials="partials",e.decorators="container.decorators",a?d.unshift(b):d.unshift(this.nameLookup("partials",b,"partial")),this.options.compat&&(e.depths="depths"),e=this.objectLiteral(e), +d.push(e),this.push(this.source.functionCall("container.invokePartial","",d))},assignToHash:function(a){var b=this.popStack(),c=void 0,d=void 0,e=void 0;this.trackIds&&(e=this.popStack()),this.stringParams&&(d=this.popStack(),c=this.popStack());var f=this.hash;c&&(f.contexts[a]=c),d&&(f.types[a]=d),e&&(f.ids[a]=e),f.values[a]=b},pushId:function(a,b,c){"BlockParam"===a?this.pushStackLiteral("blockParams["+b[0]+"].path["+b[1]+"]"+(c?" + "+JSON.stringify("."+c):"")):"PathExpression"===a?this.pushString(b):"SubExpression"===a?this.pushStackLiteral("true"):this.pushStackLiteral("null")},compiler:e,compileChildren:function(a,b){for(var c=a.children,d=void 0,e=void 0,f=0,g=c.length;g>f;f++){d=c[f],e=new this.compiler;var h=this.matchExistingProgram(d);null==h?(this.context.programs.push(""),h=this.context.programs.length,d.index=h,d.name="program"+h,this.context.programs[h]=e.compile(d,b,this.context,!this.precompile),this.context.decorators[h]=e.decorators,this.context.environments[h]=d,this.useDepths=this.useDepths||e.useDepths,this.useBlockParams=this.useBlockParams||e.useBlockParams):(d.index=h,d.name="program"+h,this.useDepths=this.useDepths||d.useDepths,this.useBlockParams=this.useBlockParams||d.useBlockParams)}},matchExistingProgram:function(a){for(var b=0,c=this.context.environments.length;c>b;b++){var d=this.context.environments[b];if(d&&d.equals(a))return b}},programExpression:function(a){var b=this.environment.children[a],c=[b.index,"data",b.blockParams];return(this.useBlockParams||this.useDepths)&&c.push("blockParams"),this.useDepths&&c.push("depths"),"container.program("+c.join(", ")+")"},useRegister:function(a){this.registers[a]||(this.registers[a]=!0,this.registers.list.push(a))},push:function(a){return a instanceof d||(a=this.source.wrap(a)),this.inlineStack.push(a),a},pushStackLiteral:function(a){this.push(new d(a))},pushSource:function(a){this.pendingContent&&(this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent),this.pendingLocation)),this.pendingContent=void 0),a&&this.source.push(a)},replaceStack:function(a){var b=["("],c=void 0,e=void 0,f=void 0;if(!this.isInline())throw new j["default"]("replaceStack on non-inline");var g=this.popStack(!0);if(g instanceof d)c=[g.value],b=["(",c],f=!0;else{e=!0;var h=this.incrStack();b=["((",this.push(h)," = ",g,")"],c=this.topStack()}var i=a.call(this,c);f||this.popStack(),e&&this.stackSlot--,this.push(b.concat(i,")"))},incrStack:function(){return this.stackSlot++,this.stackSlot>this.stackVars.length&&this.stackVars.push("stack"+this.stackSlot),this.topStackName()},topStackName:function(){return"stack"+this.stackSlot},flushInline:function(){var a=this.inlineStack;this.inlineStack=[];for(var b=0,c=a.length;c>b;b++){var e=a[b];if(e instanceof d)this.compileStack.push(e);else{var f=this.incrStack();this.pushSource([f," = ",e,";"]),this.compileStack.push(f)}}},isInline:function(){return this.inlineStack.length},popStack:function(a){var b=this.isInline(),c=(b?this.inlineStack:this.compileStack).pop();if(!a&&c instanceof d)return c.value;if(!b){if(!this.stackSlot)throw new j["default"]("Invalid stack pop");this.stackSlot--}return c},topStack:function(){var a=this.isInline()?this.inlineStack:this.compileStack,b=a[a.length-1];return b instanceof d?b.value:b},contextName:function(a){return this.useDepths&&a?"depths["+a+"]":"depth"+a},quotedString:function(a){return this.source.quotedString(a)},objectLiteral:function(a){return this.source.objectLiteral(a)},aliasable:function(a){var b=this.aliases[a];return b?(b.referenceCount++,b):(b=this.aliases[a]=this.source.wrap(a),b.aliasable=!0,b.referenceCount=1,b)},setupHelper:function(a,b,c){var d=[],e=this.setupHelperArgs(b,a,d,c),f=this.nameLookup("helpers",b,"helper"),g=this.aliasable(this.contextName(0)+" != null ? "+this.contextName(0)+" : {}");return{params:d,paramsInit:e,name:f,callParams:[g].concat(d)}},setupParams:function(a,b,c){var d={},e=[],f=[],g=[],h=!c,i=void 0;h&&(c=[]),d.name=this.quotedString(a),d.hash=this.popStack(),this.trackIds&&(d.hashIds=this.popStack()),this.stringParams&&(d.hashTypes=this.popStack(),d.hashContexts=this.popStack());var j=this.popStack(),k=this.popStack();(k||j)&&(d.fn=k||"container.noop",d.inverse=j||"container.noop");for(var l=b;l--;)i=this.popStack(),c[l]=i,this.trackIds&&(g[l]=this.popStack()),this.stringParams&&(f[l]=this.popStack(),e[l]=this.popStack());return h&&(d.args=this.source.generateArray(c)),this.trackIds&&(d.ids=this.source.generateArray(g)),this.stringParams&&(d.types=this.source.generateArray(f),d.contexts=this.source.generateArray(e)),this.options.data&&(d.data="data"),this.useBlockParams&&(d.blockParams="blockParams"),d},setupHelperArgs:function(a,b,c,d){var e=this.setupParams(a,b,c);return e=this.objectLiteral(e),d?(this.useRegister("options"),c.push("options"),["options=",e]):c?(c.push(e),""):e}},function(){for(var a="break else new var case finally return void catch for switch while continue function this with default if throw delete in try do instanceof typeof abstract enum int short boolean export interface static byte extends long super char final native synchronized class float package throws const goto private transient debugger implements protected volatile double import public let yield await null true false".split(" "),b=e.RESERVED_WORDS={},c=0,d=a.length;d>c;c++)b[a[c]]=!0}(),e.isValidJavaScriptVariableName=function(a){return!e.RESERVED_WORDS[a]&&/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(a)},b["default"]=e,a.exports=b["default"]},function(a,b,c){"use strict";function d(a,b,c){if(f.isArray(a)){for(var d=[],e=0,g=a.length;g>e;e++)d.push(b.wrap(a[e],c));return d}return"boolean"==typeof a||"number"==typeof a?a+"":a}function e(a){this.srcFile=a,this.source=[]}b.__esModule=!0;var f=c(5),g=void 0;try{}catch(h){}g||(g=function(a,b,c,d){this.src="",d&&this.add(d)},g.prototype={add:function(a){f.isArray(a)&&(a=a.join("")),this.src+=a},prepend:function(a){f.isArray(a)&&(a=a.join("")),this.src=a+this.src},toStringWithSourceMap:function(){return{code:this.toString()}},toString:function(){return this.src}}),e.prototype={isEmpty:function(){return!this.source.length},prepend:function(a,b){this.source.unshift(this.wrap(a,b))},push:function(a,b){this.source.push(this.wrap(a,b))},merge:function(){var a=this.empty();return this.each(function(b){a.add([" ",b,"\n"])}),a},each:function(a){for(var b=0,c=this.source.length;c>b;b++)a(this.source[b])},empty:function(){var a=this.currentLocation||{start:{}};return new g(a.start.line,a.start.column,this.srcFile)},wrap:function(a){var b=arguments.length<=1||void 0===arguments[1]?this.currentLocation||{start:{}}:arguments[1];return a instanceof g?a:(a=d(a,this,b),new g(b.start.line,b.start.column,this.srcFile,a))},functionCall:function(a,b,c){return c=this.generateList(c),this.wrap([a,b?"."+b+"(":"(",c,")"])},quotedString:function(a){return'"'+(a+"").replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029")+'"'},objectLiteral:function(a){var b=[];for(var c in a)if(a.hasOwnProperty(c)){var e=d(a[c],this);"undefined"!==e&&b.push([this.quotedString(c),":",e])}var f=this.generateList(b);return f.prepend("{"),f.add("}"),f},generateList:function(a){for(var b=this.empty(),c=0,e=a.length;e>c;c++)c&&b.add(","),b.add(d(a[c],this));return b},generateArray:function(a){var b=this.generateList(a);return b.prepend("["),b.add("]"),b}},b["default"]=e,a.exports=b["default"]}])}); \ No newline at end of file diff --git a/doc/vendor/jquery.min.js b/doc/vendor/jquery.min.js new file mode 100644 index 000000000..349030de9 --- /dev/null +++ b/doc/vendor/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.2.1 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.1",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!k.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c}catch(e){}O.set(a,b,c); +}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.rnamespace||a.rnamespace.test(g.namespace))&&(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("