Skip to content

Commit c5773f7

Browse files
authored
Merge pull request #164 from OWASP/recreate-badges-on-module-changes
Recreate badges on module changes
2 parents 298bf2e + 48ce4a2 commit c5773f7

File tree

13 files changed

+411
-5326
lines changed

13 files changed

+411
-5326
lines changed

trainingportal/challenges.js

Lines changed: 90 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ const aescrypto = require(path.join(__dirname, 'aescrypto'));
2626
const https = require('https');
2727

2828
var modules = {};
29+
var moduleVer = 0;
2930
var challengeDefinitions = [];
3031
var challengeNames = [];
3132
var solutions = [];
3233
var descriptions = [];
3334
var masterSalt = "";
3435

35-
loadModules = function(){
36+
loadModules = () => {
3637
let modsPath;
3738
if(!util.isNullOrUndefined(process.env.DATA_DIR)){
3839
modsPath = path.join(process.env.DATA_DIR, "modules.json");
@@ -44,13 +45,18 @@ loadModules = function(){
4445
let localModules = {};
4546
let moduleIds = Object.keys(moduleDefs);
4647
for(let moduleId of moduleIds){
48+
if(moduleId === "version"){
49+
moduleVer = moduleDefs[moduleId];
50+
continue;
51+
}
4752
let disabled = config.disabledModules;
4853
if(util.isNullOrUndefined(disabled) || !disabled.includes(moduleId)){
4954
localModules[moduleId] = moduleDefs[moduleId];
5055
}
5156
}
52-
return localModules;
53-
}
57+
return Object.freeze(localModules);
58+
}
59+
5460

5561
function getModulePath(moduleId){
5662
return path.join('static/lessons/', moduleId);
@@ -71,8 +77,8 @@ function getDefinitionsForModule(moduleId){
7177
/**
7278
* Initializes challenges when this module is loaded
7379
*/
74-
function init(){
75-
modules = Object.freeze(loadModules());
80+
let init = async () => {
81+
modules = loadModules();
7682
for(let moduleId in modules){
7783
let moduleDefinitions = getDefinitionsForModule(moduleId);
7884
var modulePath = getModulePath(moduleId);
@@ -97,15 +103,22 @@ function init(){
97103
else{
98104
masterSalt=process.env.CHALLENGE_MASTER_SALT;
99105
}
106+
107+
let dbModuleVersion = await db.getModuleVersion();
108+
if(dbModuleVersion < moduleVer){
109+
util.log("New training modules version, updating module completion for all users.")
110+
recreateBadgesOnModulesUpdate();
111+
db.updateModuleVersion(moduleVer);
112+
}
100113
}
101114

102115
init();
103116

104-
exports.getModules = function(){ return modules; }
105-
exports.getChallengeNames = function(){ return challengeNames; }
106-
exports.getChallengeDefinitions = function(){ return challengeDefinitions; }
117+
getModules = function(){ return modules; }
118+
getChallengeNames = function(){ return challengeNames; }
119+
getChallengeDefinitions = function(){ return challengeDefinitions; }
107120

108-
exports.isPermittedModule = async (user, moduleId) => {
121+
isPermittedModule = async (user, moduleId) => {
109122
let badges = await db.fetchBadges(user.id);
110123
if(util.isNullOrUndefined(modules[moduleId])){
111124
return false;
@@ -131,9 +144,9 @@ exports.isPermittedModule = async (user, moduleId) => {
131144
/**
132145
* Get the user level based on the amount of passed challenges
133146
*/
134-
exports.getUserLevelForModule = async (user,moduleId) => {
147+
getUserLevelForModule = async (user,moduleId) => {
135148
let moduleDefinitions = getDefinitionsForModule(moduleId);
136-
let passedChallenges = await db.getPromise(db.fetchChallengeEntriesForUser,user);
149+
let passedChallenges = await db.fetchChallengeEntriesForUser(user);
137150
let userLevel=-1;
138151
for(let level of moduleDefinitions){
139152
let passCount = 0;
@@ -157,11 +170,11 @@ exports.getUserLevelForModule = async (user,moduleId) => {
157170
/**
158171
* Get permitted challenges for module
159172
*/
160-
exports.getPermittedChallengesForUser = async (user, moduleId) => {
173+
getPermittedChallengesForUser = async (user, moduleId) => {
161174
if(util.isNullOrUndefined(moduleId)) return [];
162175
if(util.isNullOrUndefined(modules[moduleId])) return [];
163176

164-
var permittedLevel = await exports.getUserLevelForModule(user, moduleId) + 1;
177+
var permittedLevel = await getUserLevelForModule(user, moduleId) + 1;
165178

166179
var moduleDefinitions = getDefinitionsForModule(moduleId);
167180

@@ -179,7 +192,7 @@ exports.getPermittedChallengesForUser = async (user, moduleId) => {
179192
* @param {Object} user The session user object
180193
* @param {Array} moduleIds The lesson module ids
181194
*/
182-
exports.getChallengeDefinitionsForUser = async (user, moduleId) => {
195+
getChallengeDefinitionsForUser = async (user, moduleId) => {
183196
var returnChallenges = [];
184197

185198
if(util.isNullOrUndefined(moduleId)) return [];
@@ -214,7 +227,7 @@ exports.getChallengeDefinitionsForUser = async (user, moduleId) => {
214227
* Returns the solution html (converted from markdown)
215228
* @param {The challenge id} challengeId
216229
*/
217-
exports.getSolution = function (challengeId) {
230+
getSolution = function (challengeId) {
218231
var solution = solutions[challengeId];
219232
var solutionHtml = "";
220233
if(!util.isNullOrUndefined(solution)){
@@ -229,7 +242,7 @@ exports.getSolution = function (challengeId) {
229242
* Returns the description html (converted from markdown if applicable)
230243
* @param {The challenge id} challengeId
231244
*/
232-
exports.getDescription = function (challengeId) {
245+
getDescription = function (challengeId) {
233246
var description = descriptions[challengeId];
234247
var descriptionHtml = "";
235248
if(util.isNullOrUndefined(description)) return "";
@@ -252,8 +265,8 @@ exports.getDescription = function (challengeId) {
252265
/**
253266
* Checks if the user has completed the module and issue a badge
254267
*/
255-
exports.verifyModuleCompletion = async (user, moduleId) => {
256-
var userLevel = await exports.getUserLevelForModule(user, moduleId);
268+
verifyModuleCompletion = async (user, moduleId) => {
269+
var userLevel = await getUserLevelForModule(user, moduleId);
257270
let moduleDefinitions = getDefinitionsForModule(moduleId);
258271
var lastLevel = moduleDefinitions[moduleDefinitions.length-1];
259272

@@ -268,7 +281,7 @@ exports.verifyModuleCompletion = async (user, moduleId) => {
268281
}
269282
}
270283
if(!found){
271-
util.log("WARN: Fixed badge for user.", user);
284+
util.log(`WARN: Fixed badge for module ${moduleId} for user.`, user);
272285
await db.insertBadge(user.id, moduleId);
273286
}
274287
return true;
@@ -277,11 +290,37 @@ exports.verifyModuleCompletion = async (user, moduleId) => {
277290
return false;
278291
}
279292

293+
/**
294+
* Iterates through the entire list of users to insert badges where needed
295+
*/
296+
recreateBadgesOnModulesUpdate = async () => {
297+
298+
let users = await db.fetchUsersWithId();
299+
300+
for(let user of users){
301+
let entries = await db.fetchChallengeEntriesForUser(user);
302+
303+
var passedChallenges = [];
304+
for(let entry of entries){
305+
passedChallenges.push(entry.challengeId);
306+
}
307+
308+
user.passedChallenges = passedChallenges;
309+
for(let moduleId in modules){
310+
try {
311+
await verifyModuleCompletion(user, moduleId);
312+
} catch (error) {
313+
util.log("Error with badge verification.", user);
314+
}
315+
}
316+
}
317+
}
318+
280319
/**
281320
* Retrieves a code to verify completion of the level
282321
* @param {Badge} badge
283322
*/
284-
exports.getBadgeCode = (badge, user) => {
323+
getBadgeCode = (badge, user) => {
285324
let module = modules[badge.moduleId];
286325

287326
if(util.isNullOrUndefined(module) || util.isNullOrUndefined(module.badgeInfo)) return null;
@@ -308,7 +347,7 @@ exports.getBadgeCode = (badge, user) => {
308347
* Verifies a badge code and returns parsed info
309348
* @param {Base64} badgeCode
310349
*/
311-
exports.verifyBadgeCode = (badgeCode) => {
350+
verifyBadgeCode = (badgeCode) => {
312351
urlDecoded = decodeURIComponent(badgeCode);
313352
let parts = urlDecoded.split(".");
314353
if(parts.length !== 2) return null;
@@ -331,7 +370,7 @@ exports.verifyBadgeCode = (badgeCode) => {
331370
* @param {*} badgrInfo
332371
* @param {*} user
333372
*/
334-
module.exports.badgrCall = function(badgrInfo, user){
373+
badgrCall = function(badgrInfo, user){
335374
if(!util.isNullOrUndefined(badgrInfo) && !util.isNullOrUndefined(config.encBadgrToken)){
336375
if(user.email===null){
337376
util.log("Cannot issue badge for this user. E-mail is null.", user);
@@ -379,7 +418,7 @@ module.exports.badgrCall = function(badgrInfo, user){
379418
/**
380419
* Logic to for the api challenge code
381420
*/
382-
module.exports.apiChallengeCode = async (req) => {
421+
apiChallengeCode = async (req) => {
383422
if(util.isNullOrUndefined(req.body.challengeId) ||
384423
util.isNullOrUndefined(req.body.challengeCode) ||
385424
util.isNullOrUndefined(req.body.moduleId)){
@@ -408,7 +447,7 @@ module.exports.apiChallengeCode = async (req) => {
408447
var curChallengeObj = null;
409448

410449
//identify the current challenge object and also the available challenges for the current user level
411-
var availableChallenges = await module.exports.getPermittedChallengesForUser(req.user, moduleId);
450+
var availableChallenges = await getPermittedChallengesForUser(req.user, moduleId);
412451

413452
//search for the current challenge id
414453
for(let availableChallenge of availableChallenges){
@@ -436,24 +475,24 @@ module.exports.apiChallengeCode = async (req) => {
436475
}
437476
//success update challenge
438477
curChallengeObj.moduleId = moduleId;
439-
return module.exports.insertChallengeEntry(req.user, curChallengeObj, moduleId);
478+
return insertChallengeEntry(req.user, curChallengeObj, moduleId);
440479

441480
}
442481

443482
/**
444483
* Inserts a challenge entry
445484
*/
446-
module.exports.insertChallengeEntry = async (user,curChallengeObj, moduleId) => {
485+
insertChallengeEntry = async (user,curChallengeObj, moduleId) => {
447486
await db.getPromise(db.insertChallengeEntry, [user.id,curChallengeObj.id]);
448487
//issue badgr badge if enabled
449-
module.exports.badgrCall(curChallengeObj.badgrInfo,user);
450-
let isModuleComplete = await module.exports.verifyModuleCompletion(user,moduleId);
488+
badgrCall(curChallengeObj.badgrInfo,user);
489+
let isModuleComplete = await verifyModuleCompletion(user,moduleId);
451490
//check to see if the user levelled up
452491
curChallengeObj.moduleComplete = isModuleComplete;
453492
if(isModuleComplete){
454493
util.log(`User has solved the challenge ${curChallengeObj.name} and completed the module!`, user);
455494
//issue badgr badge if enabled for module
456-
module.exports.badgrCall(modules[moduleId].badgrInfo,user);
495+
badgrCall(modules[moduleId].badgrInfo,user);
457496
return {
458497
"message":"Congratulations you solved the challenge and completed the module! You can now get your badge of completion.",
459498
"data":curChallengeObj
@@ -466,4 +505,26 @@ module.exports.insertChallengeEntry = async (user,curChallengeObj, moduleId) =>
466505
"data": curChallengeObj
467506
}
468507
}
508+
}
509+
510+
511+
512+
513+
module.exports = {
514+
apiChallengeCode,
515+
badgrCall,
516+
getBadgeCode,
517+
getChallengeNames,
518+
getChallengeDefinitions,
519+
getChallengeDefinitionsForUser,
520+
getDescription,
521+
getModules,
522+
getPermittedChallengesForUser,
523+
getUserLevelForModule,
524+
getSolution,
525+
insertChallengeEntry,
526+
isPermittedModule,
527+
verifyBadgeCode,
528+
verifyModuleCompletion,
529+
recreateBadgesOnModulesUpdate
469530
}

0 commit comments

Comments
 (0)