diff --git a/app/app/components/auth-settings.js b/app/app/components/auth-settings.js index 3afff469..070bac5b 100644 --- a/app/app/components/auth-settings.js +++ b/app/app/components/auth-settings.js @@ -23,10 +23,12 @@ export default Ember.Component.extend({ KeycloakUrlError: computed.empty('keycloakConfig.url'), KeycloakRealmError: computed.empty('keycloakConfig.realm'), KeycloakClientIdError: computed.empty('keycloakConfig.clientId'), + KeycloakPublicKeyError: computed.empty('keycloakConfig.publicKey'), keycloakConfig: { url: '', realm: '', - clientId: '' + clientId: '', + publicKey: '', }, didReceiveAttrs() { @@ -81,6 +83,20 @@ export default Ember.Component.extend({ this.$("#keycloak-clientId").focus(); return; } + if (this.get('KeycloakPublicKeyError')) { + this.$("#keycloak-publicKey").focus(); + return; + } + + let pk = this.get('keycloakConfig.publicKey'); + if (is.not.startWith(pk, '-----BEGIN PUBLIC KEY-----')) { + pk = '-----BEGIN PUBLIC KEY-----' + pk; + } + if (is.not.endWith(pk, '-----END PUBLIC KEY-----')) { + pk = pk + '-----END PUBLIC KEY-----' ; + } + + this.set('keycloakConfig.publicKey', pk); config = this.get('keycloakConfig'); break; diff --git a/app/app/pods/auth/keycloak/route.js b/app/app/pods/auth/keycloak/route.js index c87a6b19..ef72e79d 100644 --- a/app/app/pods/auth/keycloak/route.js +++ b/app/app/pods/auth/keycloak/route.js @@ -44,6 +44,8 @@ export default Ember.Route.extend({ this.get('kcAuth').fetchProfile(kc).then((profile) => { let data = this.get('kcAuth').mapProfile(kc, profile); + console.log(kc); + console.log(profile); console.log(data); // this.get("session").authenticate('authenticator:keycloak', data) diff --git a/app/app/pods/auth/keycloak/template.hbs b/app/app/pods/auth/keycloak/template.hbs index 07400c1c..9f545853 100644 --- a/app/app/pods/auth/keycloak/template.hbs +++ b/app/app/pods/auth/keycloak/template.hbs @@ -1,4 +1,4 @@
-

Keycloak authentication...

+

Authenticating with Keycloak...

diff --git a/app/app/services/kc-auth.js b/app/app/services/kc-auth.js index 962dbd8d..5f2a888a 100644 --- a/app/app/services/kc-auth.js +++ b/app/app/services/kc-auth.js @@ -69,6 +69,7 @@ export default Ember.Service.extend({ username: profile.username, firstname: profile.firstName, lastname: profile.lastName, + remoteId: profile.id }; } }); diff --git a/app/app/templates/components/auth-settings.hbs b/app/app/templates/components/auth-settings.hbs index 3f65bb93..6c46eb4c 100644 --- a/app/app/templates/components/auth-settings.hbs +++ b/app/app/templates/components/auth-settings.hbs @@ -11,6 +11,10 @@ {{#if isKeycloakProvider}} +
+
Keycloak Configuration
+
Connection parameters
+
e.g. http://localhost:8888/auth
@@ -26,6 +30,11 @@
e.g. account
{{input id="keycloak-clientId" type="text" value=keycloakConfig.clientId class=(if KeycloakClientIdError 'error')}}
+
+ +
Copy the RSA public key from Realm Settings → Keys
+ {{textarea id="keycloak-publicKey" type="text" value=keycloakConfig.publicKey rows=7 class=(if KeycloakPublicKeyError 'error')}} +
{{/if}}
save
diff --git a/app/vendor/is.js b/app/vendor/is.js index 504517c5..6e106821 100644 --- a/app/vendor/is.js +++ b/app/vendor/is.js @@ -1,36 +1,35 @@ -// is.js 0.7.4 -// Author: Aras Atasaygin +/*! + * is.js 0.9.0 + * Author: Aras Atasaygin + */ // AMD with global, Node, or global -;(function(root, factory) { - if(typeof define === 'function' && define.amd) { +;(function(root, factory) { // eslint-disable-line no-extra-semi + if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. - define(['is'], function(is) { + define(function() { // Also create a global in case some scripts // that are loaded still are looking for // a global even when an AMD loader is in use. - return (root.is = factory(is)); + return (root.is = factory()); }); - } else if(typeof exports === 'object') { + } else if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like enviroments that support module.exports, // like Node. - module.exports = factory(require('is_js')); + module.exports = factory(); } else { - // Browser globals (root is window) - root.is = factory(root.is); + // Browser globals (root is self) + root.is = factory(); } -} (this, function(is) { +}(this, function() { // Baseline /* -------------------------------------------------------------------------- */ - var root = this || global; - var previousIs = root.is; - // define 'is' object and current version - is = {}; - is.VERSION = '0.7.4'; + var is = {}; + is.VERSION = '0.9.0'; // define interfaces is.not = {}; @@ -39,27 +38,23 @@ // cache some methods to call later on var toString = Object.prototype.toString; - var arraySlice = Array.prototype.slice; + var slice = Array.prototype.slice; var hasOwnProperty = Object.prototype.hasOwnProperty; // helper function which reverses the sense of predicate result function not(func) { return function() { - return !func.apply(null, arraySlice.call(arguments)); + return !func.apply(null, slice.call(arguments)); }; } // helper function which call predicate function per parameter and return true if all pass function all(func) { return function() { - var parameters = arraySlice.call(arguments); - var length = parameters.length; - if(length === 1 && is.array(parameters[0])) { // support array - parameters = parameters[0]; - length = parameters.length; - } + var params = getParams(arguments); + var length = params.length; for (var i = 0; i < length; i++) { - if (!func.call(null, parameters[i])) { + if (!func.call(null, params[i])) { return false; } } @@ -70,14 +65,10 @@ // helper function which call predicate function per parameter and return true if any pass function any(func) { return function() { - var parameters = arraySlice.call(arguments); - var length = parameters.length; - if(length === 1 && is.array(parameters[0])) { // support array - parameters = parameters[0]; - length = parameters.length; - } + var params = getParams(arguments); + var length = params.length; for (var i = 0; i < length; i++) { - if (func.call(null, parameters[i])) { + if (func.call(null, params[i])) { return true; } } @@ -85,12 +76,39 @@ }; } + // build a 'comparator' object for various comparison checks + var comparator = { + '<': function(a, b) { return a < b; }, + '<=': function(a, b) { return a <= b; }, + '>': function(a, b) { return a > b; }, + '>=': function(a, b) { return a >= b; } + }; + + // helper function which compares a version to a range + function compareVersion(version, range) { + var string = (range + ''); + var n = +(string.match(/\d+/) || NaN); + var op = string.match(/^[<>]=?|/)[0]; + return comparator[op] ? comparator[op](version, n) : (version == n || n !== n); + } + + // helper function which extracts params from arguments + function getParams(args) { + var params = slice.call(args); + var length = params.length; + if (length === 1 && is.array(params[0])) { // support array + params = params[0]; + } + return params; + } + // Type checks /* -------------------------------------------------------------------------- */ // is a given value Arguments? is.arguments = function(value) { // fallback check is for IE - return is.not.null(value) && (toString.call(value) === '[object Arguments]' || (typeof value === 'object' && 'callee' in value)); + return toString.call(value) === '[object Arguments]' || + (value != null && typeof value === 'object' && 'callee' in value); }; // is a given value Array? @@ -103,28 +121,43 @@ return value === true || value === false || toString.call(value) === '[object Boolean]'; }; + // is a given value Char? + is.char = function(value) { + return is.string(value) && value.length === 1; + }; + // is a given value Date Object? is.date = function(value) { return toString.call(value) === '[object Date]'; }; + // is a given object a DOM node? + is.domNode = function(object) { + return is.object(object) && object.nodeType > 0; + }; + // is a given value Error object? is.error = function(value) { return toString.call(value) === '[object Error]'; }; // is a given value function? - is.function = function(value) { // fallback check is for IE + is['function'] = function(value) { // fallback check is for IE return toString.call(value) === '[object Function]' || typeof value === 'function'; }; + // is given value a pure JSON object? + is.json = function(value) { + return toString.call(value) === '[object Object]'; + }; + // is a given value NaN? is.nan = function(value) { // NaN is number :) Also it is the only value which does not equal itself return value !== value; }; // is a given value null? - is.null = function(value) { + is['null'] = function(value) { return value === null; }; @@ -135,13 +168,7 @@ // is a given value object? is.object = function(value) { - var type = typeof value; - return type === 'function' || type === 'object' && !!value; - }; - - // is given value a pure JSON object? - is.json = function(value) { - return toString.call(value) === '[object Object]'; + return Object(value) === value; }; // is a given value RegExp? @@ -151,11 +178,15 @@ // are given values same type? // prevent NaN, Number same type check - is.sameType = function(value1, value2) { - if(is.nan(value1) || is.nan(value2)) { - return is.nan(value1) === is.nan(value2); + is.sameType = function(value, other) { + var tag = toString.call(value); + if (tag !== toString.call(other)) { + return false; } - return toString.call(value1) === toString.call(value2); + if (tag === '[object Number]') { + return !is.any.nan(value, other) || is.all.nan(value, other); + } + return true; }; // sameType method does not support 'all' and 'any' interfaces is.sameType.api = ['not']; @@ -165,72 +196,74 @@ return toString.call(value) === '[object String]'; }; - // is a given value Char? - is.char = function(value) { - return is.string(value) && value.length === 1; - }; - // is a given value undefined? is.undefined = function(value) { return value === void 0; }; + // is a given value window? + // setInterval method is only available for window object + is.windowObject = function(value) { + return value != null && typeof value === 'object' && 'setInterval' in value; + }; + // Presence checks /* -------------------------------------------------------------------------- */ //is a given value empty? Objects, arrays, strings is.empty = function(value) { - if(is.object(value)){ - var num = Object.getOwnPropertyNames(value).length; - if(num === 0 || (num === 1 && is.array(value)) || (num === 2 && is.arguments(value))){ + if (is.object(value)) { + var length = Object.getOwnPropertyNames(value).length; + if (length === 0 || (length === 1 && is.array(value)) || + (length === 2 && is.arguments(value))) { return true; } return false; - } else { - return value === ''; } + return value === ''; }; // is a given value existy? is.existy = function(value) { - return value !== null && value !== undefined; - }; - - // is a given value truthy? - is.truthy = function(value) { - return is.existy(value) && value !== false && is.not.nan(value) && value !== "" && value !== 0; + return value != null; }; // is a given value falsy? - is.falsy = not(is.truthy); - - // is a given value space? - // horizantal tab: 9, line feed: 10, vertical tab: 11, form feed: 12, carriage return: 13, space: 32 - is.space = function(value) { - if(is.char(value)) { - var characterCode = value.charCodeAt(0); - return (characterCode > 8 && characterCode < 14) || characterCode === 32; - } else { - return false; - } + is.falsy = function(value) { + return !value; }; + // is a given value truthy? + is.truthy = not(is.falsy); + // Arithmetic checks /* -------------------------------------------------------------------------- */ - // are given values equal? supports numbers, strings, regexps, booleans + // is a given number above minimum parameter? + is.above = function(n, min) { + return is.all.number(n, min) && n > min; + }; + // above method does not support 'all' and 'any' interfaces + is.above.api = ['not']; + + // is a given number decimal? + is.decimal = function(n) { + return is.number(n) && n % 1 !== 0; + }; + + // are given values equal? supports numbers, strings, regexes, booleans // TODO: Add object and array support - is.equal = function(value1, value2) { + is.equal = function(value, other) { // check 0 and -0 equity with Infinity and -Infinity - if(is.all.number(value1, value2)) { - return value1 === value2 && 1 / value1 === 1 / value2; + if (is.all.number(value, other)) { + return value === other && 1 / value === 1 / other; } - // check regexps as strings too - if(is.all.string(value1, value2) || is.all.regexp(value1, value2)) { - return '' + value1 === '' + value2; + // check regexes as strings too + if (is.all.string(value, other) || is.all.regexp(value, other)) { + return '' + value === '' + other; } - if(is.all.boolean(value1, value2)) { - return value1 === value2; + if (is.all.boolean(value, other)) { + return value === other; } return false; }; @@ -238,157 +271,181 @@ is.equal.api = ['not']; // is a given number even? - is.even = function(numb) { - return is.number(numb) && numb % 2 === 0; + is.even = function(n) { + return is.number(n) && n % 2 === 0; }; - // is a given number odd? - is.odd = function(numb) { - return is.number(numb) && numb % 2 !== 0; + // is a given number finite? + is.finite = isFinite || function(n) { + return is.not.infinite(n) && is.not.nan(n); }; - // is a given number positive? - is.positive = function(numb) { - return is.number(numb) && numb > 0; + // is a given number infinite? + is.infinite = function(n) { + return n === Infinity || n === -Infinity; + }; + + // is a given number integer? + is.integer = function(n) { + return is.number(n) && n % 1 === 0; }; // is a given number negative? - is.negative = function(numb) { - return is.number(numb) && numb < 0; + is.negative = function(n) { + return is.number(n) && n < 0; }; - // is a given number above minimum parameter? - is.above = function(numb, min) { - return is.all.number(numb, min) && numb > min; + // is a given number odd? + is.odd = function(n) { + return is.number(n) && n % 2 === 1; + }; + + // is a given number positive? + is.positive = function(n) { + return is.number(n) && n > 0; }; - // above method does not support 'all' and 'any' interfaces - is.above.api = ['not']; // is a given number above maximum parameter? - is.under = function(numb, max) { - return is.all.number(numb, max) && numb < max; + is.under = function(n, max) { + return is.all.number(n, max) && n < max; }; // least method does not support 'all' and 'any' interfaces is.under.api = ['not']; // is a given number within minimum and maximum parameters? - is.within = function(numb, min, max) { - return is.all.number(numb, min, max) && numb > min && numb < max; + is.within = function(n, min, max) { + return is.all.number(n, min, max) && n > min && n < max; }; // within method does not support 'all' and 'any' interfaces is.within.api = ['not']; - // is a given number decimal? - is.decimal = function(numb) { - return is.number(numb) && numb % 1 !== 0; - }; - - // is a given number integer? - is.integer = function(numb) { - return is.number(numb) && numb % 1 === 0; - }; - - // is a given number finite? - is.finite = isFinite || function(numb) { - return numb !== Infinity && numb !== -Infinity && is.not.nan(numb); - }; - - // is a given number infinite? - is.infinite = not(is.finite); - // Regexp checks /* -------------------------------------------------------------------------- */ // Steven Levithan, Jan Goyvaerts: Regular Expressions Cookbook // Scott Gonzalez: Email address validation + // dateString match m/d/yy and mm/dd/yyyy, allowing any combination of one or two digits for the day and month, and two or four digits for the year // eppPhone match extensible provisioning protocol format // nanpPhone match north american number plan format - // dateString match m/d/yy and mm/dd/yyyy, allowing any combination of one or two digits for the day and month, and two or four digits for the year // time match hours, minutes, and seconds, 24-hour clock - var regexps = { - url: /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i, - email: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, - creditCard: /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/, - alphaNumeric: /^[A-Za-z0-9]+$/, - timeString: /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/, - dateString: /^(1[0-2]|0?[1-9])\/(3[01]|[12][0-9]|0?[1-9])\/(?:[0-9]{2})?[0-9]{2}$/, - usZipCode: /^[0-9]{5}(?:-[0-9]{4})?$/, - caPostalCode: /^(?!.*[DFIOQU])[A-VXY][0-9][A-Z]\s?[0-9][A-Z][0-9]$/, - ukPostCode: /^[A-Z]{1,2}[0-9RCHNQ][0-9A-Z]?\s?[0-9][ABD-HJLNP-UW-Z]{2}$|^[A-Z]{2}-?[0-9]{4}$/, - nanpPhone: /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/, - eppPhone: /^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/, - socialSecurityNumber: /^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$/, + var regexes = { affirmative: /^(?:1|t(?:rue)?|y(?:es)?|ok(?:ay)?)$/, - hexadecimal: /^[0-9a-fA-F]+$/, + alphaNumeric: /^[A-Za-z0-9]+$/, + caPostalCode: /^(?!.*[DFIOQU])[A-VXY][0-9][A-Z]\s?[0-9][A-Z][0-9]$/, + creditCard: /^(?:(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11}))$/, + dateString: /^(1[0-2]|0?[1-9])([\/-])(3[01]|[12][0-9]|0?[1-9])(?:\2)(?:[0-9]{2})?[0-9]{2}$/, + email: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, // eslint-disable-line no-control-regex + eppPhone: /^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/, + hexadecimal: /^(?:0x)?[0-9a-fA-F]+$/, hexColor: /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, ipv4: /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/, - ipv6: /^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/, - ip: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/ + ipv6: /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i, + nanpPhone: /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/, + socialSecurityNumber: /^(?!000|666)[0-8][0-9]{2}-?(?!00)[0-9]{2}-?(?!0000)[0-9]{4}$/, + timeString: /^(2[0-3]|[01]?[0-9]):([0-5]?[0-9]):([0-5]?[0-9])$/, + ukPostCode: /^[A-Z]{1,2}[0-9RCHNQ][0-9A-Z]?\s?[0-9][ABD-HJLNP-UW-Z]{2}$|^[A-Z]{2}-?[0-9]{4}$/, + url: /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i, + usZipCode: /^[0-9]{5}(?:-[0-9]{4})?$/ }; - // create regexp checks methods from 'regexp' object - for(var regexp in regexps) { - if(regexps.hasOwnProperty(regexp)) { - regexpCheck(regexp, regexps); + function regexpCheck(regexp, regexes) { + is[regexp] = function(value) { + return is.existy(value) && regexes[regexp].test(value); + }; + } + + // create regexp checks methods from 'regexes' object + for (var regexp in regexes) { + if (regexes.hasOwnProperty(regexp)) { + regexpCheck(regexp, regexes); } } - function regexpCheck(regexp, regexps) { - is[regexp] = function(value) { - return regexps[regexp].test(value); - }; - } + // simplify IP checks by calling the regex helpers for IPv4 and IPv6 + is.ip = function(value) { + return is.ipv4(value) || is.ipv6(value); + }; // String checks /* -------------------------------------------------------------------------- */ - // is a given string include parameter substring? - is.include = function(str, substr) { - return str.indexOf(substr) > -1; - }; - // include method does not support 'all' and 'any' interfaces - is.include.api = ['not']; - - // is a given string all uppercase? - is.upperCase = function(str) { - return is.string(str) && str === str.toUpperCase(); + // is a given string or sentence capitalized? + is.capitalized = function(string) { + if (is.not.string(string)) { + return false; + } + var words = string.split(' '); + for (var i = 0; i < words.length; i++) { + var word = words[i]; + if (word.length) { + var chr = word.charAt(0); + if (chr !== chr.toUpperCase()) { + return false; + } + } + } + return true; }; - // is a given string all lowercase? - is.lowerCase = function(str) { - return is.string(str) && str === str.toLowerCase(); - }; - - // is string start with a given startWith parameter? - is.startWith = function(str, startWith) { - return is.string(str) && str.indexOf(startWith) === 0; - }; - // startWith method does not support 'all' and 'any' interfaces - is.startWith.api = ['not']; - - // is string end with a given endWith parameter? - is.endWith = function(str, endWith) { - return is.string(str) && str.indexOf(endWith) > -1 && str.indexOf(endWith) === str.length - endWith.length; + // is string end with a given target parameter? + is.endWith = function(string, target) { + if (is.not.string(string)) { + return false; + } + target += ''; + var position = string.length - target.length; + return position >= 0 && string.indexOf(target, position) === position; }; // endWith method does not support 'all' and 'any' interfaces is.endWith.api = ['not']; - // is a given string or sentence capitalized? - is.capitalized = function(str) { - if(is.not.string(str)) { - return false; - } - var words = str.split(' '); - var capitalized = []; - for(var i = 0; i < words.length; i++) { - capitalized.push(words[i][0] === words[i][0].toUpperCase()); - } - return is.all.truthy.apply(null, capitalized); + // is a given string include parameter target? + is.include = function(string, target) { + return string.indexOf(target) > -1; + }; + // include method does not support 'all' and 'any' interfaces + is.include.api = ['not']; + + // is a given string all lowercase? + is.lowerCase = function(string) { + return is.string(string) && string === string.toLowerCase(); }; // is a given string palindrome? - is.palindrome = function(str) { - return is.string(str) && str == str.split('').reverse().join(''); + is.palindrome = function(string) { + if (is.not.string(string)) { + return false; + } + string = string.replace(/[^a-zA-Z0-9]+/g, '').toLowerCase(); + var length = string.length - 1; + for (var i = 0, half = Math.floor(length / 2); i <= half; i++) { + if (string.charAt(i) !== string.charAt(length - i)) { + return false; + } + } + return true; + }; + + // is a given value space? + // horizontal tab: 9, line feed: 10, vertical tab: 11, form feed: 12, carriage return: 13, space: 32 + is.space = function(value) { + if (is.not.char(value)) { + return false; + } + var charCode = value.charCodeAt(0); + return (charCode > 8 && charCode < 14) || charCode === 32; + }; + + // is string start with a given target parameter? + is.startWith = function(string, target) { + return is.string(string) && string.indexOf(target) === 0; + }; + // startWith method does not support 'all' and 'any' interfaces + is.startWith.api = ['not']; + + // is a given string all uppercase? + is.upperCase = function(string) { + return is.string(string) && string === string.toUpperCase(); }; // Time checks @@ -397,366 +454,388 @@ var days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; var months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']; - // is a given date indicate today? - is.today = function(obj) { - var now = new Date(); - var todayString = now.toDateString(); - return is.date(obj) && obj.toDateString() === todayString; - }; - - // is a given date indicate yesterday? - is.yesterday = function(obj) { - var now = new Date(); - var yesterdayString = new Date(now.setDate(now.getDate() - 1)).toDateString(); - return is.date(obj) && obj.toDateString() === yesterdayString; - }; - - // is a given date indicate tomorrow? - is.tomorrow = function(obj) { - var now = new Date(); - var tomorrowString = new Date(now.setDate(now.getDate() + 1)).toDateString(); - return is.date(obj) && obj.toDateString() === tomorrowString; - }; - - // is a given date past? - is.past = function(obj) { - var now = new Date(); - return is.date(obj) && obj.getTime() < now.getTime(); - }; - - // is a given date future? - is.future = not(is.past); - - // is a given dates day equal given dayString parameter? - is.day = function(obj, dayString) { - return is.date(obj) && dayString.toLowerCase() === days[obj.getDay()]; + // is a given dates day equal given day parameter? + is.day = function(date, day) { + return is.date(date) && day.toLowerCase() === days[date.getDay()]; }; // day method does not support 'all' and 'any' interfaces is.day.api = ['not']; - // is a given dates month equal given monthString parameter? - is.month = function(obj, monthString) { - return is.date(obj) && monthString.toLowerCase() === months[obj.getMonth()]; + // is a given date in daylight saving time? + is.dayLightSavingTime = function(date) { + var january = new Date(date.getFullYear(), 0, 1); + var july = new Date(date.getFullYear(), 6, 1); + var stdTimezoneOffset = Math.max(january.getTimezoneOffset(), july.getTimezoneOffset()); + return date.getTimezoneOffset() < stdTimezoneOffset; }; - // month method does not support 'all' and 'any' interfaces - is.month.api = ['not']; - // is a given dates year equal given year parameter? - is.year = function(obj, year) { - return is.date(obj) && is.number(year) && year === obj.getFullYear(); + // is a given date future? + is.future = function(date) { + var now = new Date(); + return is.date(date) && date.getTime() > now.getTime(); + }; + + // is date within given range? + is.inDateRange = function(date, start, end) { + if (is.not.date(date) || is.not.date(start) || is.not.date(end)) { + return false; + } + var stamp = date.getTime(); + return stamp > start.getTime() && stamp < end.getTime(); + }; + // inDateRange method does not support 'all' and 'any' interfaces + is.inDateRange.api = ['not']; + + // is a given date in last month range? + is.inLastMonth = function(date) { + return is.inDateRange(date, new Date(new Date().setMonth(new Date().getMonth() - 1)), new Date()); + }; + + // is a given date in last week range? + is.inLastWeek = function(date) { + return is.inDateRange(date, new Date(new Date().setDate(new Date().getDate() - 7)), new Date()); + }; + + // is a given date in last year range? + is.inLastYear = function(date) { + return is.inDateRange(date, new Date(new Date().setFullYear(new Date().getFullYear() - 1)), new Date()); + }; + + // is a given date in next month range? + is.inNextMonth = function(date) { + return is.inDateRange(date, new Date(), new Date(new Date().setMonth(new Date().getMonth() + 1))); + }; + + // is a given date in next week range? + is.inNextWeek = function(date) { + return is.inDateRange(date, new Date(), new Date(new Date().setDate(new Date().getDate() + 7))); + }; + + // is a given date in next year range? + is.inNextYear = function(date) { + return is.inDateRange(date, new Date(), new Date(new Date().setFullYear(new Date().getFullYear() + 1))); }; - // year method does not support 'all' and 'any' interfaces - is.year.api = ['not']; // is the given year a leap year? is.leapYear = function(year) { return is.number(year) && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); }; + // is a given dates month equal given month parameter? + is.month = function(date, month) { + return is.date(date) && month.toLowerCase() === months[date.getMonth()]; + }; + // month method does not support 'all' and 'any' interfaces + is.month.api = ['not']; + + // is a given date past? + is.past = function(date) { + var now = new Date(); + return is.date(date) && date.getTime() < now.getTime(); + }; + + // is a given date in the parameter quarter? + is.quarterOfYear = function(date, quarter) { + return is.date(date) && is.number(quarter) && quarter === Math.floor((date.getMonth() + 3) / 3); + }; + // quarterOfYear method does not support 'all' and 'any' interfaces + is.quarterOfYear.api = ['not']; + + // is a given date indicate today? + is.today = function(date) { + var now = new Date(); + var todayString = now.toDateString(); + return is.date(date) && date.toDateString() === todayString; + }; + + // is a given date indicate tomorrow? + is.tomorrow = function(date) { + var now = new Date(); + var tomorrowString = new Date(now.setDate(now.getDate() + 1)).toDateString(); + return is.date(date) && date.toDateString() === tomorrowString; + }; + // is a given date weekend? // 6: Saturday, 0: Sunday - is.weekend = function(obj) { - return is.date(obj) && (obj.getDay() === 6 || obj.getDay() === 0); + is.weekend = function(date) { + return is.date(date) && (date.getDay() === 6 || date.getDay() === 0); }; // is a given date weekday? is.weekday = not(is.weekend); - // is date within given range? - is.inDateRange = function(obj, startObj, endObj) { - if(is.not.date(obj) || is.not.date(startObj) || is.not.date(endObj)) { - return false; - } - var givenDate = obj.getTime(); - var start = startObj.getTime(); - var end = endObj.getTime(); - return givenDate > start && givenDate < end; + // is a given dates year equal given year parameter? + is.year = function(date, year) { + return is.date(date) && is.number(year) && year === date.getFullYear(); }; - // inDateRange method does not support 'all' and 'any' interfaces - is.inDateRange.api = ['not']; + // year method does not support 'all' and 'any' interfaces + is.year.api = ['not']; - // is a given date in last week range? - is.inLastWeek = function(obj) { - return is.inDateRange(obj, new Date(new Date().setDate(new Date().getDate() - 7)), new Date()); - }; - - // is a given date in last month range? - is.inLastMonth = function(obj) { - return is.inDateRange(obj, new Date(new Date().setMonth(new Date().getMonth() - 1)), new Date()); - }; - - // is a given date in last year range? - is.inLastYear = function(obj) { - return is.inDateRange(obj, new Date(new Date().setFullYear(new Date().getFullYear() - 1)), new Date()); - }; - - // is a given date in next week range? - is.inNextWeek = function(obj) { - return is.inDateRange(obj, new Date(), new Date(new Date().setDate(new Date().getDate() + 7))); - }; - - // is a given date in next month range? - is.inNextMonth = function(obj) { - return is.inDateRange(obj, new Date(), new Date(new Date().setMonth(new Date().getMonth() + 1))); - }; - - // is a given date in next year range? - is.inNextYear = function(obj) { - return is.inDateRange(obj, new Date(), new Date(new Date().setFullYear(new Date().getFullYear() + 1))); - }; - - // is a given date in the parameter quarter? - is.quarterOfYear = function(obj, quarterNumber) { - return is.date(obj) && is.number(quarterNumber) && quarterNumber === Math.floor((obj.getMonth() + 3) / 3); - }; - // quarterOfYear method does not support 'all' and 'any' interfaces - is.quarterOfYear.api = ['not']; - - // is a given date in daylight saving time? - is.dayLightSavingTime = function(obj) { - var january = new Date(obj.getFullYear(), 0, 1); - var july = new Date(obj.getFullYear(), 6, 1); - var stdTimezoneOffset = Math.max(january.getTimezoneOffset(), july.getTimezoneOffset()); - return obj.getTimezoneOffset() < stdTimezoneOffset; + // is a given date indicate yesterday? + is.yesterday = function(date) { + var now = new Date(); + var yesterdayString = new Date(now.setDate(now.getDate() - 1)).toDateString(); + return is.date(date) && date.toDateString() === yesterdayString; }; // Environment checks /* -------------------------------------------------------------------------- */ - // check if library is used as a Node.js module - if(typeof window !== 'undefined') { + var freeGlobal = is.windowObject(typeof global == 'object' && global) && global; + var freeSelf = is.windowObject(typeof self == 'object' && self) && self; + var thisGlobal = is.windowObject(typeof this == 'object' && this) && this; + var root = freeGlobal || freeSelf || thisGlobal || Function('return this')(); - // store navigator properties to use later - var userAgent = 'navigator' in window && 'userAgent' in navigator && navigator.userAgent.toLowerCase() || ''; - var vendor = 'navigator' in window && 'vendor' in navigator && navigator.vendor.toLowerCase() || ''; - var appVersion = 'navigator' in window && 'appVersion' in navigator && navigator.appVersion.toLowerCase() || ''; + var document = freeSelf && freeSelf.document; + var previousIs = root.is; - // is current browser chrome? - is.chrome = function() { - return /chrome|chromium/i.test(userAgent) && /google inc/.test(vendor); - }; - // chrome method does not support 'all' and 'any' interfaces - is.chrome.api = ['not']; + // store navigator properties to use later + var navigator = freeSelf && freeSelf.navigator; + var appVersion = (navigator && navigator.appVersion || '').toLowerCase(); + var userAgent = (navigator && navigator.userAgent || '').toLowerCase(); + var vendor = (navigator && navigator.vendor || '').toLowerCase(); - // is current browser firefox? - is.firefox = function() { - return /firefox/i.test(userAgent); - }; - // firefox method does not support 'all' and 'any' interfaces - is.firefox.api = ['not']; + // is current device android? + is.android = function() { + return /android/.test(userAgent); + }; + // android method does not support 'all' and 'any' interfaces + is.android.api = ['not']; - // is current browser internet explorer? - // parameter is optional - is.ie = function(version) { - if(!version) { - return /msie/i.test(userAgent) || "ActiveXObject" in window; - } - if(version >= 11) { - return "ActiveXObject" in window; - } - return new RegExp('msie ' + version).test(userAgent); - }; - // ie method does not support 'all' and 'any' interfaces - is.ie.api = ['not']; + // is current device android phone? + is.androidPhone = function() { + return /android/.test(userAgent) && /mobile/.test(userAgent); + }; + // androidPhone method does not support 'all' and 'any' interfaces + is.androidPhone.api = ['not']; - // is current browser opera? - is.opera = function() { - return /^Opera\//.test(userAgent) || // Opera 12 and older versions - /\x20OPR\//.test(userAgent); // Opera 15+ - }; - // opera method does not support 'all' and 'any' interfaces - is.opera.api = ['not']; + // is current device android tablet? + is.androidTablet = function() { + return /android/.test(userAgent) && !/mobile/.test(userAgent); + }; + // androidTablet method does not support 'all' and 'any' interfaces + is.androidTablet.api = ['not']; - // is current browser safari? - is.safari = function() { - return /safari/i.test(userAgent) && /apple computer/i.test(vendor); - }; - // safari method does not support 'all' and 'any' interfaces - is.safari.api = ['not']; + // is current device blackberry? + is.blackberry = function() { + return /blackberry/.test(userAgent) || /bb10/.test(userAgent); + }; + // blackberry method does not support 'all' and 'any' interfaces + is.blackberry.api = ['not']; - // is current device ios? - is.ios = function() { - return is.iphone() || is.ipad() || is.ipod(); - }; - // ios method does not support 'all' and 'any' interfaces - is.ios.api = ['not']; + // is current browser chrome? + // parameter is optional + is.chrome = function(range) { + var match = /google inc/.test(vendor) ? userAgent.match(/(?:chrome|crios)\/(\d+)/) : null; + return match !== null && is.not.opera() && compareVersion(match[1], range); + }; + // chrome method does not support 'all' and 'any' interfaces + is.chrome.api = ['not']; - // is current device iphone? - is.iphone = function() { - return /iphone/i.test(userAgent); - }; - // iphone method does not support 'all' and 'any' interfaces - is.iphone.api = ['not']; + // is current device desktop? + is.desktop = function() { + return is.not.mobile() && is.not.tablet(); + }; + // desktop method does not support 'all' and 'any' interfaces + is.desktop.api = ['not']; - // is current device ipad? - is.ipad = function() { - return /ipad/i.test(userAgent); - }; - // ipad method does not support 'all' and 'any' interfaces - is.ipad.api = ['not']; + // is current browser edge? + // parameter is optional + is.edge = function(range) { + var match = userAgent.match(/edge\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // edge method does not support 'all' and 'any' interfaces + is.edge.api = ['not']; - // is current device ipod? - is.ipod = function() { - return /ipod/i.test(userAgent); - }; - // ipod method does not support 'all' and 'any' interfaces - is.ipod.api = ['not']; + // is current browser firefox? + // parameter is optional + is.firefox = function(range) { + var match = userAgent.match(/(?:firefox|fxios)\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // firefox method does not support 'all' and 'any' interfaces + is.firefox.api = ['not']; - // is current device android? - is.android = function() { - return /android/i.test(userAgent); - }; - // android method does not support 'all' and 'any' interfaces - is.android.api = ['not']; + // is current browser internet explorer? + // parameter is optional + is.ie = function(range) { + var match = userAgent.match(/(?:msie |trident.+?; rv:)(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // ie method does not support 'all' and 'any' interfaces + is.ie.api = ['not']; - // is current device android phone? - is.androidPhone = function() { - return /android/i.test(userAgent) && /mobile/i.test(userAgent); - }; - // androidPhone method does not support 'all' and 'any' interfaces - is.androidPhone.api = ['not']; + // is current device ios? + is.ios = function() { + return is.iphone() || is.ipad() || is.ipod(); + }; + // ios method does not support 'all' and 'any' interfaces + is.ios.api = ['not']; - // is current device android tablet? - is.androidTablet = function() { - return /android/i.test(userAgent) && !/mobile/i.test(userAgent); - }; - // androidTablet method does not support 'all' and 'any' interfaces - is.androidTablet.api = ['not']; + // is current device ipad? + // parameter is optional + is.ipad = function(range) { + var match = userAgent.match(/ipad.+?os (\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // ipad method does not support 'all' and 'any' interfaces + is.ipad.api = ['not']; - // is current device blackberry? - is.blackberry = function() { - return /blackberry/i.test(userAgent) || /BB10/i.test(userAgent); - }; - // blackberry method does not support 'all' and 'any' interfaces - is.blackberry.api = ['not']; + // is current device iphone? + // parameter is optional + is.iphone = function(range) { + // avoid false positive for Facebook in-app browser on ipad; + // original iphone doesn't have the OS portion of the UA + var match = is.ipad() ? null : userAgent.match(/iphone(?:.+?os (\d+))?/); + return match !== null && compareVersion(match[1] || 1, range); + }; + // iphone method does not support 'all' and 'any' interfaces + is.iphone.api = ['not']; - // is current device desktop? - is.desktop = function() { - return is.not.mobile() && is.not.tablet(); - }; - // desktop method does not support 'all' and 'any' interfaces - is.desktop.api = ['not']; + // is current device ipod? + // parameter is optional + is.ipod = function(range) { + var match = userAgent.match(/ipod.+?os (\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // ipod method does not support 'all' and 'any' interfaces + is.ipod.api = ['not']; - // is current operating system linux? - is.linux = function() { - return /linux/i.test(appVersion); - }; - // linux method does not support 'all' and 'any' interfaces - is.linux.api = ['not']; + // is current operating system linux? + is.linux = function() { + return /linux/.test(appVersion); + }; + // linux method does not support 'all' and 'any' interfaces + is.linux.api = ['not']; - // is current operating system mac? - is.mac = function() { - return /mac/i.test(appVersion); - }; - // mac method does not support 'all' and 'any' interfaces - is.mac.api = ['not']; + // is current operating system mac? + is.mac = function() { + return /mac/.test(appVersion); + }; + // mac method does not support 'all' and 'any' interfaces + is.mac.api = ['not']; - // is current operating system windows? - is.windows = function() { - return /win/i.test(appVersion); - }; - // windows method does not support 'all' and 'any' interfaces - is.windows.api = ['not']; + // is current device mobile? + is.mobile = function() { + return is.iphone() || is.ipod() || is.androidPhone() || is.blackberry() || is.windowsPhone(); + }; + // mobile method does not support 'all' and 'any' interfaces + is.mobile.api = ['not']; - // is current device windows phone? - is.windowsPhone = function() { - return is.windows() && /phone/i.test(userAgent); - }; - // windowsPhone method does not support 'all' and 'any' interfaces - is.windowsPhone.api = ['not']; + // is current state offline? + is.offline = not(is.online); + // offline method does not support 'all' and 'any' interfaces + is.offline.api = ['not']; - // is current device windows tablet? - is.windowsTablet = function() { - return is.windows() && is.not.windowsPhone() && /touch/i.test(userAgent); - }; - // windowsTablet method does not support 'all' and 'any' interfaces - is.windowsTablet.api = ['not']; + // is current state online? + is.online = function() { + return !navigator || navigator.onLine === true; + }; + // online method does not support 'all' and 'any' interfaces + is.online.api = ['not']; - // is current device mobile? - is.mobile = function() { - return is.iphone() || is.ipod() || is.androidPhone() || is.blackberry() || is.windowsPhone(); - }; - // mobile method does not support 'all' and 'any' interfaces - is.mobile.api = ['not']; + // is current browser opera? + // parameter is optional + is.opera = function(range) { + var match = userAgent.match(/(?:^opera.+?version|opr)\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // opera method does not support 'all' and 'any' interfaces + is.opera.api = ['not']; - // is current device tablet? - is.tablet = function() { - return is.ipad() || is.androidTablet() || is.windowsTablet(); - }; - // tablet method does not support 'all' and 'any' interfaces - is.tablet.api = ['not']; + // is current browser phantomjs? + // parameter is optional + is.phantom = function(range) { + var match = userAgent.match(/phantomjs\/(\d+)/); + return match !== null && compareVersion(match[1], range); + }; + // phantom method does not support 'all' and 'any' interfaces + is.phantom.api = ['not']; - // is current state online? - is.online = function() { - return navigator.onLine; - }; - // online method does not support 'all' and 'any' interfaces - is.online.api = ['not']; + // is current browser safari? + // parameter is optional + is.safari = function(range) { + var match = userAgent.match(/version\/(\d+).+?safari/); + return match !== null && compareVersion(match[1], range); + }; + // safari method does not support 'all' and 'any' interfaces + is.safari.api = ['not']; - // is current state offline? - is.offline = not(is.online); - // offline method does not support 'all' and 'any' interfaces - is.offline.api = ['not']; + // is current device tablet? + is.tablet = function() { + return is.ipad() || is.androidTablet() || is.windowsTablet(); + }; + // tablet method does not support 'all' and 'any' interfaces + is.tablet.api = ['not']; - // is current device supports touch? - is.touchDevice = function() { - return 'ontouchstart' in window ||'DocumentTouch' in window && document instanceof DocumentTouch; - }; - // touchDevice method does not support 'all' and 'any' interfaces - is.touchDevice.api = ['not']; - } + // is current device supports touch? + is.touchDevice = function() { + return !!document && ('ontouchstart' in freeSelf || + ('DocumentTouch' in freeSelf && document instanceof DocumentTouch)); + }; + // touchDevice method does not support 'all' and 'any' interfaces + is.touchDevice.api = ['not']; + + // is current operating system windows? + is.windows = function() { + return /win/.test(appVersion); + }; + // windows method does not support 'all' and 'any' interfaces + is.windows.api = ['not']; + + // is current device windows phone? + is.windowsPhone = function() { + return is.windows() && /phone/.test(userAgent); + }; + // windowsPhone method does not support 'all' and 'any' interfaces + is.windowsPhone.api = ['not']; + + // is current device windows tablet? + is.windowsTablet = function() { + return is.windows() && is.not.windowsPhone() && /touch/.test(userAgent); + }; + // windowsTablet method does not support 'all' and 'any' interfaces + is.windowsTablet.api = ['not']; // Object checks /* -------------------------------------------------------------------------- */ // has a given object got parameterized count property? - is.propertyCount = function(obj, count) { - if(!is.object(obj) || !is.number(count)) { + is.propertyCount = function(object, count) { + if (is.not.object(object) || is.not.number(count)) { return false; } - if(Object.keys) { - return Object.keys(obj).length === count; - } - var properties = [], - property; - for(property in obj) { - if (hasOwnProperty.call(obj, property)) { - properties.push(property); + var n = 0; + for (var property in object) { + if (hasOwnProperty.call(object, property) && ++n > count) { + return false; } } - return properties.length === count; + return n === count; }; // propertyCount method does not support 'all' and 'any' interfaces is.propertyCount.api = ['not']; // is given object has parameterized property? - is.propertyDefined = function(obj, property) { - return is.object(obj) && is.string(property) && property in obj; + is.propertyDefined = function(object, property) { + return is.object(object) && is.string(property) && property in object; }; // propertyDefined method does not support 'all' and 'any' interfaces is.propertyDefined.api = ['not']; - // is a given object window? - // setInterval method is only available for window object - is.windowObject = function(obj) { - return typeof obj === 'object' && 'setInterval' in obj; - }; - - // is a given object a DOM node? - is.domNode = function(obj) { - return is.object(obj) && obj.nodeType > 0; - }; - // Array checks /* -------------------------------------------------------------------------- */ // is a given item in an array? - is.inArray = function(val, arr){ - if(is.not.array(arr)) { + is.inArray = function(value, array) { + if (is.not.array(array)) { return false; } - for(var i = 0; i < arr.length; i++) { - if (arr[i] === val) return true; + for (var i = 0; i < array.length; i++) { + if (array[i] === value) { + return true; + } } return false; }; @@ -764,12 +843,15 @@ is.inArray.api = ['not']; // is a given array sorted? - is.sorted = function(arr) { - if(is.not.array(arr)) { + is.sorted = function(array, sign) { + if (is.not.array(array)) { return false; } - for(var i = 0; i < arr.length; i++) { - if(arr[i] > arr[i + 1]) return false; + var predicate = comparator[sign] || comparator['>=']; + for (var i = 1; i < array.length; i++) { + if (!predicate(array[i], array[i - 1])) { + return false; + } } return true; }; @@ -780,17 +862,17 @@ function setInterfaces() { var options = is; - for(var option in options) { - if(hasOwnProperty.call(options, option) && is.function(options[option])) { + for (var option in options) { + if (hasOwnProperty.call(options, option) && is['function'](options[option])) { var interfaces = options[option].api || ['not', 'all', 'any']; for (var i = 0; i < interfaces.length; i++) { - if(interfaces[i] === 'not') { + if (interfaces[i] === 'not') { is.not[option] = not(is[option]); } - if(interfaces[i] === 'all') { + if (interfaces[i] === 'all') { is.all[option] = all(is[option]); } - if(interfaces[i] === 'any') { + if (interfaces[i] === 'any') { is.any[option] = any(is[option]); } } @@ -803,15 +885,6 @@ // Intentionally added after setInterfaces function /* -------------------------------------------------------------------------- */ - // set optional regexps to methods if you think they suck - is.setRegexp = function(regexp, regexpName) { - for(var r in regexps) { - if(hasOwnProperty.call(regexps, r) && (regexpName === r)) { - regexps[r] = regexp; - } - } - }; - // change namespace of library to prevent name collisions // var preferredName = is.setNamespace(); // preferredName.odd(3); @@ -821,5 +894,14 @@ return this; }; + // set optional regexes to methods + is.setRegexp = function(regexp, name) { + for (var r in regexes) { + if (hasOwnProperty.call(regexes, r) && (name === r)) { + regexes[r] = regexp; + } + } + }; + return is; -})); +})); \ No newline at end of file diff --git a/core/api/endpoint/authentication_endpoint.go b/core/api/endpoint/authentication_endpoint.go index 994a4545..f11773f0 100644 --- a/core/api/endpoint/authentication_endpoint.go +++ b/core/api/endpoint/authentication_endpoint.go @@ -12,22 +12,17 @@ package endpoint import ( - "crypto/rand" "database/sql" "encoding/json" "errors" "fmt" "net/http" "strings" - "time" - - jwt "github.com/dgrijalva/jwt-go" "github.com/documize/community/core/api/endpoint/models" "github.com/documize/community/core/api/entity" "github.com/documize/community/core/api/request" "github.com/documize/community/core/api/util" - "github.com/documize/community/core/environment" "github.com/documize/community/core/log" "github.com/documize/community/core/section/provider" "github.com/documize/community/core/utility" @@ -308,130 +303,3 @@ func preAuthorizeStaticAssets(r *http.Request) bool { return false } - -var jwtKey string - -func init() { - environment.GetString(&jwtKey, "salt", false, "the salt string used to encode JWT tokens, if not set a random value will be generated", - func(t *string, n string) bool { - if jwtKey == "" { - b := make([]byte, 17) - _, err := rand.Read(b) - if err != nil { - jwtKey = err.Error() - log.Error("problem using crypto/rand", err) - return false - } - for k, v := range b { - if (v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z') || (v >= '0' && v <= '0') { - b[k] = v - } else { - s := fmt.Sprintf("%x", v) - b[k] = s[0] - } - } - jwtKey = string(b) - log.Info("Please set DOCUMIZESALT or use -salt with this value: " + jwtKey) - } - return true - }) -} - -// Generates JSON Web Token (http://jwt.io) -func generateJWT(user, org, domain string) string { - token := jwt.New(jwt.SigningMethodHS256) - - // issuer - token.Claims["iss"] = "Documize" - // subject - token.Claims["sub"] = "webapp" - // expiry - token.Claims["exp"] = time.Now().Add(time.Hour * 168).Unix() - // data - token.Claims["user"] = user - token.Claims["org"] = org - token.Claims["domain"] = domain - - tokenString, _ := token.SignedString([]byte(jwtKey)) - - return tokenString -} - -// Check for authorization token. -// We look for 'Authorization' request header OR query string "?token=XXX". -func findJWT(r *http.Request) (token string) { - header := r.Header.Get("Authorization") - - if header != "" { - header = strings.Replace(header, "Bearer ", "", 1) - } - - if len(header) > 1 { - token = header - } else { - query := r.URL.Query() - token = query.Get("token") - } - - if token == "null" { - token = "" - } - - return -} - -// We take in raw token string and decode it. -func decodeJWT(tokenString string) (c request.Context, claims map[string]interface{}, err error) { - method := "decodeJWT" - - // sensible defaults - c.UserID = "" - c.OrgID = "" - c.Authenticated = false - c.Guest = false - - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { - return []byte(jwtKey), nil - }) - - if err != nil { - err = fmt.Errorf("bad authorization token") - return - } - - if !token.Valid { - if ve, ok := err.(*jwt.ValidationError); ok { - if ve.Errors&jwt.ValidationErrorMalformed != 0 { - log.Error("invalid token", err) - err = fmt.Errorf("bad token") - return - } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { - log.Error("expired token", err) - err = fmt.Errorf("expired token") - return - } else { - log.Error("invalid token", err) - err = fmt.Errorf("bad token") - return - } - } else { - log.Error("invalid token", err) - err = fmt.Errorf("bad token") - return - } - } - - c = request.NewContext() - c.UserID = token.Claims["user"].(string) - c.OrgID = token.Claims["org"].(string) - - if len(c.UserID) == 0 || len(c.OrgID) == 0 { - err = fmt.Errorf("%s : unable parse token data", method) - return - } - - c.Authenticated = true - c.Guest = false - - return c, token.Claims, nil -} diff --git a/core/api/endpoint/jwt.go b/core/api/endpoint/jwt.go new file mode 100644 index 00000000..066970aa --- /dev/null +++ b/core/api/endpoint/jwt.go @@ -0,0 +1,153 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package endpoint + +import ( + "crypto/rand" + "fmt" + "net/http" + "strings" + "time" + + jwt "github.com/dgrijalva/jwt-go" + + "github.com/documize/community/core/api/request" + "github.com/documize/community/core/environment" + "github.com/documize/community/core/log" +) + +var jwtKey string + +func init() { + environment.GetString(&jwtKey, "salt", false, "the salt string used to encode JWT tokens, if not set a random value will be generated", + func(t *string, n string) bool { + if jwtKey == "" { + b := make([]byte, 17) + _, err := rand.Read(b) + if err != nil { + jwtKey = err.Error() + log.Error("problem using crypto/rand", err) + return false + } + for k, v := range b { + if (v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z') || (v >= '0' && v <= '0') { + b[k] = v + } else { + s := fmt.Sprintf("%x", v) + b[k] = s[0] + } + } + jwtKey = string(b) + log.Info("Please set DOCUMIZESALT or use -salt with this value: " + jwtKey) + } + return true + }) +} + +// Generates JSON Web Token (http://jwt.io) +func generateJWT(user, org, domain string) string { + token := jwt.New(jwt.SigningMethodHS256) + + // issuer + token.Claims["iss"] = "Documize" + // subject + token.Claims["sub"] = "webapp" + // expiry + token.Claims["exp"] = time.Now().Add(time.Hour * 168).Unix() + // data + token.Claims["user"] = user + token.Claims["org"] = org + token.Claims["domain"] = domain + + tokenString, _ := token.SignedString([]byte(jwtKey)) + + return tokenString +} + +// Check for authorization token. +// We look for 'Authorization' request header OR query string "?token=XXX". +func findJWT(r *http.Request) (token string) { + header := r.Header.Get("Authorization") + + if header != "" { + header = strings.Replace(header, "Bearer ", "", 1) + } + + if len(header) > 1 { + token = header + } else { + query := r.URL.Query() + token = query.Get("token") + } + + if token == "null" { + token = "" + } + + return +} + +// We take in raw token string and decode it. +func decodeJWT(tokenString string) (c request.Context, claims map[string]interface{}, err error) { + method := "decodeJWT" + + // sensible defaults + c.UserID = "" + c.OrgID = "" + c.Authenticated = false + c.Guest = false + + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return []byte(jwtKey), nil + }) + + if err != nil { + err = fmt.Errorf("bad authorization token") + return + } + + if !token.Valid { + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed != 0 { + log.Error("invalid token", err) + err = fmt.Errorf("bad token") + return + } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { + log.Error("expired token", err) + err = fmt.Errorf("expired token") + return + } else { + log.Error("invalid token", err) + err = fmt.Errorf("bad token") + return + } + } else { + log.Error("invalid token", err) + err = fmt.Errorf("bad token") + return + } + } + + c = request.NewContext() + c.UserID = token.Claims["user"].(string) + c.OrgID = token.Claims["org"].(string) + + if len(c.UserID) == 0 || len(c.OrgID) == 0 { + err = fmt.Errorf("%s : unable parse token data", method) + return + } + + c.Authenticated = true + c.Guest = false + + return c, token.Claims, nil +}