Skip to content
This repository was archived by the owner on Dec 14, 2023. It is now read-only.

Commit 0d39144

Browse files
committed
Merge branch 'master' into enchanment/bruteforce-protection
2 parents 2718b26 + 4f4c9b5 commit 0d39144

22 files changed

+923
-111
lines changed

circle.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ dependencies:
1515
test:
1616
override:
1717
- npm run test
18+
post:
19+
- curl -sSL https://download.sourceclear.com/ci.sh | bash
1820
deployment:
1921
production:
2022
branch: master

config/config.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ module.exports = function () {
2828
'postgresql-store': pgConfig(),
2929
'email-notifications': {
3030
sendemail: true,
31-
sendFrom: 'The CoderDojo Team <[email protected]>',
32-
email: {
33-
}
31+
sendFrom: 'The CoderDojo Team <[email protected]>'
3432
},
3533
mailtrap: {
3634
folder: path.resolve(__dirname + '/../email-templates'),

config/perm/profiles.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = function(){
2121
}, {
2222
role: 'basic-user',
2323
userType: 'champion',
24+
extendedUserTypes: true,
2425
customValidator: [{
2526
role: 'cd-dojos',
2627
cmd: 'belongs_to_dojo'
@@ -116,7 +117,22 @@ module.exports = function(){
116117
}],
117118
'load_parents_for_user': [
118119
{ role: 'basic-user',
119-
userType: 'champion'
120+
userType: 'champion',
121+
extendedUserTypes: true
122+
},
123+
{ role: 'basic-user',
124+
customValidator: [{
125+
role: 'cd-dojos',
126+
cmd: 'have_permissions',
127+
perm: 'dojo-admin'
128+
}]
129+
},
130+
{ role: 'basic-user',
131+
customValidator: [{
132+
role: 'cd-dojos',
133+
cmd: 'have_permissions',
134+
perm: 'ticketing-admin'
135+
}]
120136
},
121137
{ role: 'basic-user',
122138
customValidator: [{
@@ -137,7 +153,8 @@ module.exports = function(){
137153
},
138154
{
139155
role: 'basic-user',
140-
userType: 'champion'
156+
userType: 'champion',
157+
extendedUserTypes: true
141158
},
142159
{
143160
role: 'basic-user',
@@ -153,7 +170,8 @@ module.exports = function(){
153170
userType: 'parent-guardian'
154171
},
155172
{ role: 'basic-user',
156-
userType: 'champion'
173+
userType: 'champion',
174+
extendedUserTypes: true
157175
},
158176
{ role: 'basic-user',
159177
customValidator:[ {

config/perm/users.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ module.exports = function(){
1919
cmd: 'is_parent_of',
2020
}]
2121
}],
22-
'list': [{
23-
role: 'basic-user',
24-
userTypes: 'champion'
25-
}],
2622
'register': [{
2723
role: 'none',
2824
}],
@@ -35,10 +31,8 @@ module.exports = function(){
3531
//NOTE: isn't perm a customVal now ?
3632
customValidator: [{
3733
role: 'cd-dojos',
38-
cmd: 'is_having_perm',
39-
param: {
40-
perm: 'dojo-admin'
41-
}
34+
cmd: 'have_permissions',
35+
perm: 'dojo-admin'
4236
}]
4337
}],
4438

@@ -82,6 +76,7 @@ module.exports = function(){
8276
}, {
8377
role: 'basic-user',
8478
userType: 'champion',
79+
extendedUserTypes: true,
8580
customValidator: [{
8681
role: 'cd-dojos',
8782
cmd: 'belongs_to_dojo'
@@ -124,5 +119,8 @@ module.exports = function(){
124119
'kpi_number_of_youth_females_registered': [{
125120
role: 'cdf-admin',
126121
}],
122+
'get_lms_link': [{
123+
role: 'basic-user'
124+
}],
127125
};
128126
};

email-notifications.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module.exports = function (options) {
2323
var subject = args.subject;
2424
var subjectVariables = args.subjectVariables || [];
2525
var subjectTranslation;
26-
if (options.sendemail && options.email) {
26+
if (options.sendemail) {
2727
var emailCode = args.code + args.locality;
2828
if (!fs.existsSync(path.join(__dirname, '/email-templates/', emailCode))) emailCode = args.code + 'en_US';
2929
if (!args.to) return done(null, {ok: false, why: 'No recipient set.'});

lib/users/lms/award-badge.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
var async = require('async');
3+
var _ = require('lodash');
4+
5+
/**
6+
* Webhook handler to award badges based on courses
7+
* Courses "Code" must correspond to the badge slug
8+
* @param {Object} certificate contains all the info, header/user/course passed
9+
* ie: {"header":{"source":"LearnUpon","version":1,"webhookId":2213843,"attempt":2,"lastAttemptAt":"2016-09-01T10:33:32Z","webHookType":"course_completion","signature":"5736605a1627415455de6f7ca50e7e9d"},"user":{"userId":12345,"lastName":null,"firstName":null,"email":"qq@example.com","username":null,"customData":null},"enrollmentId":42,"courseId":42,"courseName":"CoderDojo Ethos: Implementation and Practice","courseReferenceCode":"coderdojo-ethos:-implementation-and-practice","courseMetaData":null,"modules":[{"id":42,"type":"scorm","name":"CoderDojo Ethos: Implementation and Practice","status":"passed","percentage":100.0,"dateCompleted":null,"attempts":null,"isKnowledgeCheck":false}],"credits":[],"courseAccessExpiresAt":null,"certification":false,"certificationName":null,"certExpiresAt":null,"wasRecertified":false,"dateEnrolled":"2016-08-29T12:02:38Z","dateStarted":"2016-09-01T10:30:50Z","dateCompleted":"2016-09-01T10:32:28Z","percentage":100,"enrollmentStatus":"passed","hasAttemptsRemaining":false}
10+
*/
11+
function awardLMSBadge (args, cb) {
12+
var seneca = this;
13+
var plugin = args.role;
14+
var certif = args;
15+
var user = certif.user;
16+
17+
function checkTestStatus (waterfallCb) {
18+
if (certif.header.webHookType !== 'course_completion') {
19+
return cb(null, {ok: false, why: 'Unhandled webhook'});
20+
}
21+
if (!_.every(certif.modules, {'status': 'passed'}) &&
22+
!_.every(certif.modules, {'status': 'completed'}) &&
23+
!_.includes(['completed', 'passed'], certif.enrollmentStatus) ) {
24+
return cb(null, {ok: false, why: 'Unhandled status'});
25+
}
26+
waterfallCb(null, certif.courseReferenceCode);
27+
}
28+
29+
function getBadge (badgeName, waterfallCb) {
30+
seneca.act({role: 'cd-badges', cmd: 'getBadge', slug: badgeName},
31+
function (err, badge) {
32+
if (err) return cb(err);
33+
waterfallCb(null, badge);
34+
});
35+
}
36+
37+
function getUser (badge, waterfallCb) {
38+
seneca.act({role: 'cd-users', cmd: 'list', query: {lmsId: user.userId}},
39+
function (err, sysUser) {
40+
if (err) return cb(err);
41+
if (_.isEmpty(sysUser)) return cb(null, {ok: false, why: 'LMSUser not found'});
42+
return waterfallCb(null, sysUser[0], badge);
43+
});
44+
}
45+
46+
function awardBadge (sysUser, badge, waterfallCb) {
47+
var applicationData = {
48+
user: sysUser,
49+
badge: badge.badge,
50+
emailSubject: 'You have been awarded a new CoderDojo digital badge!'
51+
};
52+
seneca.act({role: 'cd-badges', cmd: 'sendBadgeApplication',
53+
applicationData: applicationData,
54+
user: {id: null}
55+
},
56+
function (err, user) {
57+
if (err) return cb(err);
58+
waterfallCb();
59+
});
60+
}
61+
62+
63+
async.waterfall([
64+
checkTestStatus,
65+
getBadge,
66+
getUser,
67+
awardBadge
68+
], cb);
69+
70+
}
71+
72+
module.exports = awardLMSBadge;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
var _ = require('lodash');
3+
var crypto = require('crypto');
4+
5+
/**
6+
* Verify the validity of a LMS certificate by comparing the signature of this cert
7+
* to the value of its hash + our shared private key
8+
* @param {String} certificate the certificate as a string
9+
* @param {String} signature extracted expected hash
10+
*/
11+
function checkValidity (args, cb) {
12+
var seneca = this;
13+
var hash = crypto.createHash('md5');
14+
var webhookSecret = process.env.LMS_WEBHOOK_SECRET;
15+
var signature = args.signature;
16+
var certif = args.certif;
17+
var stringCertif = certif + ':' + webhookSecret;
18+
hash.update(stringCertif);
19+
var digest = hash.digest('hex');
20+
if ( !_.isEmpty(webhookSecret)){
21+
if ( digest !== signature){
22+
cb(null, {ok: false, why: 'Invalid signature', http$:{status: 401}});
23+
} else {
24+
cb(null, {ok: true});
25+
}
26+
} else {
27+
cb(new Error('Missing Webhook Secret'));
28+
}
29+
}
30+
31+
32+
module.exports = checkValidity;

0 commit comments

Comments
 (0)