Skip to content

Commit

Permalink
Merge pull request #164 from OWASP/recreate-badges-on-module-changes
Browse files Browse the repository at this point in the history
Recreate badges on module changes
  • Loading branch information
paul-ion authored Dec 24, 2024
2 parents 298bf2e + 48ce4a2 commit c5773f7
Show file tree
Hide file tree
Showing 13 changed files with 411 additions and 5,326 deletions.
119 changes: 90 additions & 29 deletions trainingportal/challenges.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ const aescrypto = require(path.join(__dirname, 'aescrypto'));
const https = require('https');

var modules = {};
var moduleVer = 0;
var challengeDefinitions = [];
var challengeNames = [];
var solutions = [];
var descriptions = [];
var masterSalt = "";

loadModules = function(){
loadModules = () => {
let modsPath;
if(!util.isNullOrUndefined(process.env.DATA_DIR)){
modsPath = path.join(process.env.DATA_DIR, "modules.json");
Expand All @@ -44,13 +45,18 @@ loadModules = function(){
let localModules = {};
let moduleIds = Object.keys(moduleDefs);
for(let moduleId of moduleIds){
if(moduleId === "version"){
moduleVer = moduleDefs[moduleId];
continue;
}
let disabled = config.disabledModules;
if(util.isNullOrUndefined(disabled) || !disabled.includes(moduleId)){
localModules[moduleId] = moduleDefs[moduleId];
}
}
return localModules;
}
return Object.freeze(localModules);
}


function getModulePath(moduleId){
return path.join('static/lessons/', moduleId);
Expand All @@ -71,8 +77,8 @@ function getDefinitionsForModule(moduleId){
/**
* Initializes challenges when this module is loaded
*/
function init(){
modules = Object.freeze(loadModules());
let init = async () => {
modules = loadModules();
for(let moduleId in modules){
let moduleDefinitions = getDefinitionsForModule(moduleId);
var modulePath = getModulePath(moduleId);
Expand All @@ -97,15 +103,22 @@ function init(){
else{
masterSalt=process.env.CHALLENGE_MASTER_SALT;
}

let dbModuleVersion = await db.getModuleVersion();
if(dbModuleVersion < moduleVer){
util.log("New training modules version, updating module completion for all users.")
recreateBadgesOnModulesUpdate();
db.updateModuleVersion(moduleVer);
}
}

init();

exports.getModules = function(){ return modules; }
exports.getChallengeNames = function(){ return challengeNames; }
exports.getChallengeDefinitions = function(){ return challengeDefinitions; }
getModules = function(){ return modules; }
getChallengeNames = function(){ return challengeNames; }
getChallengeDefinitions = function(){ return challengeDefinitions; }

exports.isPermittedModule = async (user, moduleId) => {
isPermittedModule = async (user, moduleId) => {
let badges = await db.fetchBadges(user.id);
if(util.isNullOrUndefined(modules[moduleId])){
return false;
Expand All @@ -131,9 +144,9 @@ exports.isPermittedModule = async (user, moduleId) => {
/**
* Get the user level based on the amount of passed challenges
*/
exports.getUserLevelForModule = async (user,moduleId) => {
getUserLevelForModule = async (user,moduleId) => {
let moduleDefinitions = getDefinitionsForModule(moduleId);
let passedChallenges = await db.getPromise(db.fetchChallengeEntriesForUser,user);
let passedChallenges = await db.fetchChallengeEntriesForUser(user);
let userLevel=-1;
for(let level of moduleDefinitions){
let passCount = 0;
Expand All @@ -157,11 +170,11 @@ exports.getUserLevelForModule = async (user,moduleId) => {
/**
* Get permitted challenges for module
*/
exports.getPermittedChallengesForUser = async (user, moduleId) => {
getPermittedChallengesForUser = async (user, moduleId) => {
if(util.isNullOrUndefined(moduleId)) return [];
if(util.isNullOrUndefined(modules[moduleId])) return [];

var permittedLevel = await exports.getUserLevelForModule(user, moduleId) + 1;
var permittedLevel = await getUserLevelForModule(user, moduleId) + 1;

var moduleDefinitions = getDefinitionsForModule(moduleId);

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

if(util.isNullOrUndefined(moduleId)) return [];
Expand Down Expand Up @@ -214,7 +227,7 @@ exports.getChallengeDefinitionsForUser = async (user, moduleId) => {
* Returns the solution html (converted from markdown)
* @param {The challenge id} challengeId
*/
exports.getSolution = function (challengeId) {
getSolution = function (challengeId) {
var solution = solutions[challengeId];
var solutionHtml = "";
if(!util.isNullOrUndefined(solution)){
Expand All @@ -229,7 +242,7 @@ exports.getSolution = function (challengeId) {
* Returns the description html (converted from markdown if applicable)
* @param {The challenge id} challengeId
*/
exports.getDescription = function (challengeId) {
getDescription = function (challengeId) {
var description = descriptions[challengeId];
var descriptionHtml = "";
if(util.isNullOrUndefined(description)) return "";
Expand All @@ -252,8 +265,8 @@ exports.getDescription = function (challengeId) {
/**
* Checks if the user has completed the module and issue a badge
*/
exports.verifyModuleCompletion = async (user, moduleId) => {
var userLevel = await exports.getUserLevelForModule(user, moduleId);
verifyModuleCompletion = async (user, moduleId) => {
var userLevel = await getUserLevelForModule(user, moduleId);
let moduleDefinitions = getDefinitionsForModule(moduleId);
var lastLevel = moduleDefinitions[moduleDefinitions.length-1];

Expand All @@ -268,7 +281,7 @@ exports.verifyModuleCompletion = async (user, moduleId) => {
}
}
if(!found){
util.log("WARN: Fixed badge for user.", user);
util.log(`WARN: Fixed badge for module ${moduleId} for user.`, user);
await db.insertBadge(user.id, moduleId);
}
return true;
Expand All @@ -277,11 +290,37 @@ exports.verifyModuleCompletion = async (user, moduleId) => {
return false;
}

/**
* Iterates through the entire list of users to insert badges where needed
*/
recreateBadgesOnModulesUpdate = async () => {

let users = await db.fetchUsersWithId();

for(let user of users){
let entries = await db.fetchChallengeEntriesForUser(user);

var passedChallenges = [];
for(let entry of entries){
passedChallenges.push(entry.challengeId);
}

user.passedChallenges = passedChallenges;
for(let moduleId in modules){
try {
await verifyModuleCompletion(user, moduleId);
} catch (error) {
util.log("Error with badge verification.", user);
}
}
}
}

/**
* Retrieves a code to verify completion of the level
* @param {Badge} badge
*/
exports.getBadgeCode = (badge, user) => {
getBadgeCode = (badge, user) => {
let module = modules[badge.moduleId];

if(util.isNullOrUndefined(module) || util.isNullOrUndefined(module.badgeInfo)) return null;
Expand All @@ -308,7 +347,7 @@ exports.getBadgeCode = (badge, user) => {
* Verifies a badge code and returns parsed info
* @param {Base64} badgeCode
*/
exports.verifyBadgeCode = (badgeCode) => {
verifyBadgeCode = (badgeCode) => {
urlDecoded = decodeURIComponent(badgeCode);
let parts = urlDecoded.split(".");
if(parts.length !== 2) return null;
Expand All @@ -331,7 +370,7 @@ exports.verifyBadgeCode = (badgeCode) => {
* @param {*} badgrInfo
* @param {*} user
*/
module.exports.badgrCall = function(badgrInfo, user){
badgrCall = function(badgrInfo, user){
if(!util.isNullOrUndefined(badgrInfo) && !util.isNullOrUndefined(config.encBadgrToken)){
if(user.email===null){
util.log("Cannot issue badge for this user. E-mail is null.", user);
Expand Down Expand Up @@ -379,7 +418,7 @@ module.exports.badgrCall = function(badgrInfo, user){
/**
* Logic to for the api challenge code
*/
module.exports.apiChallengeCode = async (req) => {
apiChallengeCode = async (req) => {
if(util.isNullOrUndefined(req.body.challengeId) ||
util.isNullOrUndefined(req.body.challengeCode) ||
util.isNullOrUndefined(req.body.moduleId)){
Expand Down Expand Up @@ -408,7 +447,7 @@ module.exports.apiChallengeCode = async (req) => {
var curChallengeObj = null;

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

//search for the current challenge id
for(let availableChallenge of availableChallenges){
Expand Down Expand Up @@ -436,24 +475,24 @@ module.exports.apiChallengeCode = async (req) => {
}
//success update challenge
curChallengeObj.moduleId = moduleId;
return module.exports.insertChallengeEntry(req.user, curChallengeObj, moduleId);
return insertChallengeEntry(req.user, curChallengeObj, moduleId);

}

/**
* Inserts a challenge entry
*/
module.exports.insertChallengeEntry = async (user,curChallengeObj, moduleId) => {
insertChallengeEntry = async (user,curChallengeObj, moduleId) => {
await db.getPromise(db.insertChallengeEntry, [user.id,curChallengeObj.id]);
//issue badgr badge if enabled
module.exports.badgrCall(curChallengeObj.badgrInfo,user);
let isModuleComplete = await module.exports.verifyModuleCompletion(user,moduleId);
badgrCall(curChallengeObj.badgrInfo,user);
let isModuleComplete = await verifyModuleCompletion(user,moduleId);
//check to see if the user levelled up
curChallengeObj.moduleComplete = isModuleComplete;
if(isModuleComplete){
util.log(`User has solved the challenge ${curChallengeObj.name} and completed the module!`, user);
//issue badgr badge if enabled for module
module.exports.badgrCall(modules[moduleId].badgrInfo,user);
badgrCall(modules[moduleId].badgrInfo,user);
return {
"message":"Congratulations you solved the challenge and completed the module! You can now get your badge of completion.",
"data":curChallengeObj
Expand All @@ -466,4 +505,26 @@ module.exports.insertChallengeEntry = async (user,curChallengeObj, moduleId) =>
"data": curChallengeObj
}
}
}




module.exports = {
apiChallengeCode,
badgrCall,
getBadgeCode,
getChallengeNames,
getChallengeDefinitions,
getChallengeDefinitionsForUser,
getDescription,
getModules,
getPermittedChallengesForUser,
getUserLevelForModule,
getSolution,
insertChallengeEntry,
isPermittedModule,
verifyBadgeCode,
verifyModuleCompletion,
recreateBadgesOnModulesUpdate
}
Loading

0 comments on commit c5773f7

Please sign in to comment.