Commit a489773f authored by Curtis Adam's avatar Curtis Adam

Revert "Revert "Replace cas meteor package with a local one""

This reverts commit 8e58be68
parent fd0453ef
......@@ -27,7 +27,6 @@ service-configuration@1.0.11
accounts-facebook@1.3.2
accounts-google@1.3.2
zimme:active-route
atoy40:accounts-cas
random@1.1.0
momentjs:moment
rzymek:moment-locales
......@@ -73,3 +72,4 @@ dsyko:jquery-ui-touch-punch
simple:json-routes
underscore@1.0.10
yasseraudio:multidatepicker
wekan:accounts-cas
......@@ -14,7 +14,6 @@ aldeed:simple-schema@1.5.4
allow-deny@1.1.0
anti:i18n@0.4.3
aslagle:reactive-table@0.8.45
atoy40:accounts-cas@0.0.2
autoupdate@1.6.0
babel-compiler@7.3.4
babel-runtime@1.3.0
......@@ -238,6 +237,7 @@ underscore@1.0.10
url@1.2.0
webapp@1.7.3
webapp-hashing@1.0.9
wekan:accounts-cas@0.1.0
wtfzn:dompurify@0.6.1
yasseraudio:multidatepicker@0.0.5
zimme:active-route@2.3.2
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.
{
"lockfileVersion": 1,
"dependencies": {
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"cas": {
"version": "https://github.com/anrizal/node-cas/tarball/2baed530842e7a437f8f71b9346bcac8e84773cc",
"integrity": "sha512-OcCKzWU3iFe97tsGJnNPoGzbWlX+Y5V3P3iqRVaC53D18Mb32mEugoSjiGnhz9aSafxc/yp4XSImEEXRNypcmw=="
},
"cheerio": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz",
"integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=",
"dependencies": {
"lodash": {
"version": "3.10.1",
"resolved": "http://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
"integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
}
}
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"css-select": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz",
"integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA="
},
"css-what": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz",
"integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w="
},
"dom-serializer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
"integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
"dependencies": {
"domelementtype": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
}
}
},
"domelementtype": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
"integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI="
},
"domhandler": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
"integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg="
},
"domutils": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz",
"integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8="
},
"entities": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
},
"htmlparser2": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
"integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
"dependencies": {
"domutils": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8="
},
"entities": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY="
}
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"lodash": {
"version": "4.17.4",
"resolved": "http://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
},
"nth-check": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
"integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk="
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"xml2js": {
"version": "0.4.17",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz",
"integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg="
},
"xmlbuilder": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz",
"integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU="
}
}
}
accounts-base@1.2.8
allow-deny@1.0.5
babel-compiler@6.8.5
babel-runtime@0.1.9_1
base64@1.0.9
binary-heap@1.0.9
blaze@2.1.8
blaze-tools@1.0.9
boilerplate-generator@1.0.9
callback-hook@1.0.9
check@1.2.3
ddp@1.2.5
ddp-client@1.2.9
ddp-common@1.2.6
ddp-rate-limiter@1.0.5
ddp-server@1.2.10
deps@1.0.12
diff-sequence@1.0.6
ecmascript@0.4.8
ecmascript-runtime@0.2.12
ejson@1.0.12
geojson-utils@1.0.9
html-tools@1.0.10
htmljs@1.0.10
id-map@1.0.8
jquery@1.11.9
localstorage@1.0.11
logging@1.0.14
meteor@1.1.16
minimongo@1.0.17
modules@0.6.5
modules-runtime@0.6.5
mongo@1.1.9_1
mongo-id@1.0.5
npm-mongo@1.4.45
observe-sequence@1.0.12
ordered-dict@1.0.8
promise@0.7.3
random@1.0.10
rate-limit@1.0.5
reactive-var@1.0.10
retry@1.0.8
routepolicy@1.0.11
service-configuration@1.0.10
spacebars@1.0.12
spacebars-compiler@1.0.12
tracker@1.0.15
ui@1.0.11
underscore@1.0.9
vnorguet:accounts-cas@0.0.6
webapp@1.2.11
webapp-hashing@1.0.9
The MIT License (MIT)
Copyright (c) 2014-2019 The Wekan Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This is a merged repository of useful forks of: atoy40:accounts-cas
===================
([(https://atmospherejs.com/atoy40/accounts-cas](https://atmospherejs.com/atoy40/accounts-cas))
## Essential improvements by ppoulard to atoy40 and xaionaro versions
* Added support of CAS attributes
With this plugin, you can pick CAS attributes : https://github.com/joshchan/node-cas/wiki/CAS-Attributes
Moved to Wekan GitHub org from from https://github.com/ppoulard/meteor-accounts-cas
## Install
```
cd ~site
mkdir packages
cd packages
git clone https://github.com/wekan/meteor-accounts-cas
cd ~site
meteor add wekan:accounts-cas
```
## Usage
Put CAS settings in Meteor.settings (for example using METEOR_SETTINGS env or --settings) like so:
If casVersion is not defined, it will assume you use CAS 1.0. (note by xaionaro: option `casVersion` seems to be just ignored in the code, ATM).
Server side settings:
```
Meteor.settings = {
"cas": {
"baseUrl": "https://cas.example.com/cas",
"autoClose": true,
"validateUrl":"https://cas.example.com/cas/p3/serviceValidate",
"casVersion": 3.0,
"attributes": {
"debug" : true
}
},
}
```
CAS `attributes` settings :
* `attributes`: by default `{}` : all default values below will apply
* * `debug` : by default `false` ; `true` will print to the server console the CAS attribute names to map, the CAS attributes values retrieved, if necessary the new user account created, and finally the user to use
* * `id` : by default, the CAS user is used for the user account, but you can specified another CAS attribute
* * `firstname` : by default `cas:givenName` ; but you can use your own CAS attribute
* * `lastname` : by default `cas:sn` (respectively) ; but you can use your own CAS attribute
* * `fullname` : by default unused, but if you specify your own CAS attribute, it will be used instead of the `firstname` + `lastname`
* * `mail` : by default `cas:mail`
Client side settings:
```
Meteor.settings = {
"public": {
"cas": {
"loginUrl": "https://cas.example.com/login",
"serviceParam": "service",
"popupWidth": 810,
"popupHeight": 610,
"popup": true,
}
}
}
```
`proxyUrl` is not required. Setup [ROOT_URL](http://docs.meteor.com/api/core.html#Meteor-absoluteUrl) environment variable instead.
Then, to start authentication, you have to call the following method from the client (for example in a click handler) :
```
Meteor.loginWithCas([callback]);
```
It must open a popup containing you CAS login form or redirect to the CAS login form (depending on "popup" setting).
If popup is disabled (== false), then it's required to execute `Meteor.initCas([callback])` in `Meteor.startup` of the client side. ATM, `Meteor.initCas()` completes authentication.
## Examples
* [https://devel.mephi.ru/dyokunev/start-mephi-ru](https://devel.mephi.ru/dyokunev/start-mephi-ru)
function addParameterToURL(url, param){
var urlSplit = url.split('?');
return url+(urlSplit.length>0 ? '?':'&') + param;
}
Meteor.initCas = function(callback) {
const casTokenMatch = window.location.href.match(/[?&]casToken=([^&]+)/);
if (casTokenMatch == null) {
return;
}
window.history.pushState('', document.title, window.location.href.replace(/([&?])casToken=[^&]+[&]?/, '$1').replace(/[?&]+$/g, ''));
Accounts.callLoginMethod({
methodArguments: [{ cas: { credentialToken: casTokenMatch[1] } }],
userCallback: function(err){
if (err == null) {
// should we do anything on success?
}
if (callback != null) {
callback(err);
}
}
});
}
Meteor.loginWithCas = function(options, callback) {
var credentialToken = Random.id();
if (!Meteor.settings.public &&
!Meteor.settings.public.cas &&
!Meteor.settings.public.cas.loginUrl) {
return;
}
var settings = Meteor.settings.public.cas;
var backURL = window.location.href.replace('#', '');
if (options != null && options.redirectUrl != null)
backURL = options.redirectUrl;
var serviceURL = addParameterToURL(backURL, 'casToken='+credentialToken);
var loginUrl = settings.loginUrl +
"?" + (settings.serviceParam || "service") + "=" +
encodeURIComponent(serviceURL)
if (settings.popup == false) {
window.location = loginUrl;
return;
}
var popup = openCenteredPopup(
loginUrl,
settings.width || 800,
settings.height || 600
);
var checkPopupOpen = setInterval(function() {
try {
if(popup && popup.document && popup.document.getElementById('popupCanBeClosed')) {
popup.close();
}
// Fix for #328 - added a second test criteria (popup.closed === undefined)
// to humour this Android quirk:
// http://code.google.com/p/android/issues/detail?id=21061
var popupClosed = popup.closed || popup.closed === undefined;
} catch (e) {
// For some unknown reason, IE9 (and others?) sometimes (when
// the popup closes too quickly?) throws "SCRIPT16386: No such
// interface supported" when trying to read 'popup.closed'. Try
// again in 100ms.
return;
}
if (popupClosed) {
clearInterval(checkPopupOpen);
// check auth on server.
Accounts.callLoginMethod({
methodArguments: [{ cas: { credentialToken: credentialToken } }],
userCallback: err => {
// Fix redirect bug after login successfully
if (!err) {
window.location.href = '/';
}
}
});
}
}, 100);
};
var openCenteredPopup = function(url, width, height) {
var screenX = typeof window.screenX !== 'undefined'
? window.screenX : window.screenLeft;
var screenY = typeof window.screenY !== 'undefined'
? window.screenY : window.screenTop;
var outerWidth = typeof window.outerWidth !== 'undefined'
? window.outerWidth : document.body.clientWidth;
var outerHeight = typeof window.outerHeight !== 'undefined'
? window.outerHeight : (document.body.clientHeight - 22);
// XXX what is the 22?
// Use `outerWidth - width` and `outerHeight - height` for help in
// positioning the popup centered relative to the current window
var left = screenX + (outerWidth - width) / 2;
var top = screenY + (outerHeight - height) / 2;
var features = ('width=' + width + ',height=' + height +
',left=' + left + ',top=' + top + ',scrollbars=yes');
var newwindow = window.open(url, '_blank', features);
if (newwindow.focus)
newwindow.focus();
return newwindow;
};
Meteor.loginWithCas = function(callback) {
var credentialToken = Random.id();
if (!Meteor.settings.public &&
!Meteor.settings.public.cas &&
!Meteor.settings.public.cas.loginUrl) {
return;
}
var settings = Meteor.settings.public.cas;
var loginUrl = settings.loginUrl +
"?" + (settings.service || "service") + "=" +
Meteor.absoluteUrl('_cas/') +
credentialToken;
var fail = function (err) {
Meteor._debug("Error from OAuth popup: " + JSON.stringify(err));
};
// When running on an android device, we sometimes see the
// `pageLoaded` callback fire twice for the final page in the OAuth
// popup, even though the page only loads once. This is maybe an
// Android bug or maybe something intentional about how onPageFinished
// works that we don't understand and isn't well-documented.
var oauthFinished = false;
var pageLoaded = function (event) {
if (oauthFinished) {
return;
}
if (event.url.indexOf(Meteor.absoluteUrl('_cas')) === 0) {
oauthFinished = true;
// On iOS, this seems to prevent "Warning: Attempt to dismiss from
// view controller <MainViewController: ...> while a presentation
// or dismiss is in progress". My guess is that the last
// navigation of the OAuth popup is still in progress while we try
// to close the popup. See
// https://issues.apache.org/jira/browse/CB-2285.
//
// XXX Can we make this timeout smaller?
setTimeout(function () {
popup.close();
// check auth on server.
Accounts.callLoginMethod({
methodArguments: [{ cas: { credentialToken: credentialToken } }],
userCallback: callback
});
}, 100);
}
};
var onExit = function () {
popup.removeEventListener('loadstop', pageLoaded);
popup.removeEventListener('loaderror', fail);
popup.removeEventListener('exit', onExit);
};
var popup = window.open(loginUrl, '_blank', 'location=no,hidden=no');
popup.addEventListener('loadstop', pageLoaded);
popup.addEventListener('loaderror', fail);
popup.addEventListener('exit', onExit);
popup.show();
};
\ No newline at end of file
"use strict";
const Fiber = Npm.require('fibers');
const https = Npm.require('https');
const url = Npm.require('url');
const xmlParser = Npm.require('xml2js');
// Library
class CAS {
constructor(options) {
options = options || {};
if (!options.validate_url) {
throw new Error('Required CAS option `validateUrl` missing.');
}
if (!options.service) {
throw new Error('Required CAS option `service` missing.');
}
const cas_url = url.parse(options.validate_url);
if (cas_url.protocol != 'https:' ) {
throw new Error('Only https CAS servers are supported.');
} else if (!cas_url.hostname) {
throw new Error('Option `validateUrl` must be a valid url like: https://example.com/cas/serviceValidate');
} else {
this.hostname = cas_url.host;
this.port = 443;// Should be 443 for https
this.validate_path = cas_url.pathname;
}
this.service = options.service;
}
validate(ticket, callback) {
const httparams = {
host: this.hostname,
port: this.port,
path: url.format({
pathname: this.validate_path,
query: {ticket: ticket, service: this.service},
}),
};
https.get(httparams, (res) => {
res.on('error', (e) => {
console.log('error' + e);
callback(e);
});
// Read result
res.setEncoding('utf8');
let response = '';
res.on('data', (chunk) => {
response += chunk;
});
res.on('end', (error) => {
if (error) {
console.log('error callback');
console.log(error);
callback(undefined, false);
} else {
xmlParser.parseString(response, (err, result) => {
if (err) {
console.log('Bad response format.');
callback({message: 'Bad response format. XML could not parse it'});
} else {
if (result['cas:serviceResponse'] == null) {
console.log('Empty response.');
callback({message: 'Empty response.'});
}
if (result['cas:serviceResponse']['cas:authenticationSuccess']) {
const userData = {
id: result['cas:serviceResponse']['cas:authenticationSuccess'][0]['cas:user'][0].toLowerCase(),
};
const attributes = result['cas:serviceResponse']['cas:authenticationSuccess'][0]['cas:attributes'][0];
// Check allowed ldap groups if exist (array only)
// example cas settings : "allowedLdapGroups" : ["wekan", "admin"],
let findedGroup = false;
const allowedLdapGroups = Meteor.settings.cas.allowedLdapGroups || false;
for (const fieldName in attributes) {
if (allowedLdapGroups && fieldName === 'cas:memberOf') {
for (const groups in attributes[fieldName]) {
const str = attributes[fieldName][groups];
if (!Array.isArray(allowedLdapGroups)) {
callback({message: 'Settings "allowedLdapGroups" must be an array'});
}
for (const allowedLdapGroup in allowedLdapGroups) {
if (str.search(`cn=${allowedLdapGroups[allowedLdapGroup]}`) >= 0) {
findedGroup = true;
}
}
}
}
userData[fieldName] = attributes[fieldName][0];
}
if (allowedLdapGroups && !findedGroup) {
callback({message: 'Group not finded.'}, false);
} else {
callback(undefined, true, userData);
}
} else {
callback(undefined, false);
}
}
});
}
});
});
}
}
////// END OF CAS MODULE
let _casCredentialTokens = {};
let _userData = {};
//RoutePolicy.declare('/_cas/', 'network');
// Listen to incoming OAuth http requests
WebApp.connectHandlers.use((req, res, next) => {
// Need to create a Fiber since we're using synchronous http calls and nothing
// else is wrapping this in a fiber automatically
Fiber(() => {
middleware(req, res, next);
}).run();
});
const middleware = (req, res, next) => {
// Make sure to catch any exceptions because otherwise we'd crash
// the runner
try {
urlParsed = url.parse(req.url, true);
// Getting the ticket (if it's defined in GET-params)
// If no ticket, then request will continue down the default
// middlewares.
const query = urlParsed.query;
if (query == null) {
next();
return;
}
const ticket = query.ticket;
if (ticket == null) {
next();
return;
}
const serviceUrl = Meteor.absoluteUrl(urlParsed.href.replace(/^\//g, '')).replace(/([&?])ticket=[^&]+[&]?/g, '$1').replace(/[?&]+$/g, '');
const redirectUrl = serviceUrl;//.replace(/([&?])casToken=[^&]+[&]?/g, '$1').replace(/[?&]+$/g, '');
// get auth token
const credentialToken = query.casToken;
if (!credentialToken) {
end(res, redirectUrl);
return;
}
// validate ticket
casValidate(req, ticket, credentialToken, serviceUrl, () => {
end(res, redirectUrl);
});
} catch (err) {
console.log("account-cas: unexpected error : " + err.message);
end(res, redirectUrl);
}
};
const casValidate = (req, ticket, token, service, callback) => {
// get configuration
if (!Meteor.settings.cas/* || !Meteor.settings.cas.validate*/) {
throw new Error('accounts-cas: unable to get configuration.');
}
const cas = new CAS({