Refactoring (#1201)
* Some initial refactoring * Make it one application * Got it working * Fix problem on Windows * Move WebWolf * Move first lesson * Moved all lessons * Fix pom.xml * Fix tests * Add option to initialize a lesson This way we can create content for each user inside a lesson. The initialize method will be called when a new user is created or when a lesson reset happens * Clean up pom.xml files * Remove fetching labels based on language. We only support English at the moment, all the lesson explanations are written in English which makes it very difficult to translate. If we only had labels it would make sense to support multiple languages * Fix SonarLint issues * And move it all to the main project * Fix for documentation paths * Fix pom warnings * Remove PMD as it does not work * Update release notes about refactoring Update release notes about refactoring Update release notes about refactoring * Fix lesson template * Update release notes * Keep it in the same repo in Dockerhub * Update documentation to show how the connection is obtained. Resolves: #1180 * Rename all integration tests * Remove command from Dockerfile * Simplify GitHub actions Currently, we use a separate actions for pull-requests and branch build. This is now consolidated in one action. The PR action triggers always, it now only trigger when the PR is opened and not in draft. Running all platforms on a branch build is a bit too much, it is better to only run all platforms when someone opens a PR. * Remove duplicate entry from release notes * Add explicit registry for base image * Lesson scanner not working when fat jar When running the fat jar we have to take into account we are reading from the jar file and not the filesystem. In this case you cannot use `getFile` for example. * added info in README and fixed release docker * changed base image and added ignore file Co-authored-by: Zubcevic.com <rene@zubcevic.com>
This commit is contained in:
@ -0,0 +1,35 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone'],
|
||||
function($,
|
||||
_,
|
||||
Backbone) {
|
||||
return Backbone.View.extend({
|
||||
el:'#error-notification-container',
|
||||
initialize: function() {
|
||||
Backbone.on("error:unhandled", this.showNotification.bind(this));
|
||||
this.hideNotification();
|
||||
this.currentTimeout = null;
|
||||
},
|
||||
|
||||
showNotification: function() {
|
||||
var self = this;
|
||||
if (!this.$el.is(':visible')) {
|
||||
this.$el.show(350);
|
||||
}
|
||||
if (this.currentTimeout != null) {
|
||||
window.clearTimeout(this.currentTimeout);
|
||||
}
|
||||
this.currentTimeout = window.setTimeout(function() {
|
||||
self.hideNotification();
|
||||
self.currentTimeout = null;
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
hideNotification: function() {
|
||||
if (this.$el.is(':visible')) {
|
||||
this.$el.hide(350);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
139
src/main/resources/webgoat/static/js/goatApp/view/GoatRouter.js
Normal file
139
src/main/resources/webgoat/static/js/goatApp/view/GoatRouter.js
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Define the libraries that are used by the GoatRouter script. All define '' names should refer to the
|
||||
* names in the main.js require.config paths name parts.
|
||||
* The names of the function arguments is used as the object returned from loading the specified framework.
|
||||
*/
|
||||
|
||||
define(['jquery',
|
||||
'libs/jquery-vuln',
|
||||
'jqueryuivuln',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'goatApp/controller/LessonController',
|
||||
'goatApp/controller/MenuController',
|
||||
'goatApp/view/LessonContentView',
|
||||
'goatApp/view/MenuView',
|
||||
'goatApp/view/TitleView'
|
||||
], function ($,
|
||||
$vuln,
|
||||
jqueryui,
|
||||
_,
|
||||
Backbone,
|
||||
LessonController,
|
||||
MenuController,
|
||||
LessonContentView,
|
||||
MenuView,
|
||||
TitleView) {
|
||||
|
||||
function getContentElement() {
|
||||
return $('#main-content');
|
||||
};
|
||||
|
||||
function render(view) {
|
||||
$('div.pages').hide();
|
||||
//TODO this works for now because we only have one page we should rewrite this a bit
|
||||
if (view != null) {
|
||||
$('#report-card-page').show();
|
||||
} else {
|
||||
$('#lesson-title').show();
|
||||
$('#lesson-page').show();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Definition of Goat App Router.
|
||||
*/
|
||||
var GoatAppRouter = Backbone.Router.extend({
|
||||
|
||||
routes: {
|
||||
'welcome': 'welcomeRoute',
|
||||
'lesson/:name': 'lessonRoute',
|
||||
'lesson/:name/:pageNum': 'lessonPageRoute',
|
||||
'test/:param': 'testRoute',
|
||||
'reportCard': 'reportCard'
|
||||
},
|
||||
|
||||
lessonController: null,
|
||||
menuController : null,
|
||||
titleView: null,
|
||||
|
||||
setUpCustomJS: function () {
|
||||
webgoat.customjs.jquery = $; //passing jquery into custom js scope ... still klunky, but works for now
|
||||
webgoat.customjs.jqueryVuln = $vuln;
|
||||
|
||||
// shim to support xss lesson
|
||||
webgoat.customjs.phoneHome = function (e) {
|
||||
console.log('phoneHome invoked');
|
||||
webgoat.customjs.jquery.ajax({
|
||||
method: "POST",
|
||||
url: "/WebGoat/CrossSiteScripting/phone-home-xss",
|
||||
data: {param1: 42, param2: 24},
|
||||
headers: {
|
||||
"webgoat-requested-by": "dom-xss-vuln"
|
||||
},
|
||||
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
success: function (data) {
|
||||
//devs leave stuff like this in all the time
|
||||
console.log('phone home said ' + JSON.stringify(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/*
|
||||
* Constructor of Goat App Router invoked by goatApp.js new Router().
|
||||
*/
|
||||
initialize: function () {
|
||||
console.log('initialize goat app router');
|
||||
this.menuController = new MenuController({menuView: new MenuView()});
|
||||
this.titleView = new TitleView();
|
||||
this.lessonController = new LessonController({lessonContentView: new LessonContentView(), titleView: this.titleView}),
|
||||
this.lessonController.start();
|
||||
webgoat = {};
|
||||
webgoat.customjs = {};
|
||||
|
||||
this.setUpCustomJS();
|
||||
Backbone.history.start();
|
||||
this.listenTo(this.lessonController, 'menu:reload', this.reloadMenu)
|
||||
},
|
||||
|
||||
lessonRoute: function(name) {
|
||||
render();
|
||||
this.lessonController.loadLesson(name, 0);
|
||||
this.menuController.updateMenu(name);
|
||||
},
|
||||
|
||||
lessonPageRoute: function (name, pageNum) {
|
||||
render();
|
||||
pageNum = (_.isNumber(parseInt(pageNum))) ? parseInt(pageNum) : 0;
|
||||
this.lessonController.loadLesson(name, pageNum);
|
||||
this.menuController.updateMenu(name);
|
||||
},
|
||||
|
||||
testRoute: function (param) {
|
||||
this.lessonController.testHandler(param);
|
||||
//this.menuController.updateMenu(name);
|
||||
},
|
||||
|
||||
welcomeRoute: function () {
|
||||
render();
|
||||
this.lessonController.loadWelcome();
|
||||
},
|
||||
|
||||
reloadMenu: function (curLesson) {
|
||||
this.menuController.updateMenu();
|
||||
},
|
||||
|
||||
reportCard : function () {
|
||||
var self = this;
|
||||
require(['goatApp/view/ReportCardView'], function (ReportCardView) {
|
||||
self.titleView.render('Report card');
|
||||
render(new ReportCardView());
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return GoatAppRouter;
|
||||
|
||||
});
|
@ -0,0 +1,37 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone'],
|
||||
function($,_,Backbone) {
|
||||
return Backbone.View.extend({
|
||||
el:'#help-controls', //Check this
|
||||
|
||||
initialize: function (options) {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
this.hasPlan = options.hasPlan;
|
||||
this.hasSolution = options.hasSolution;
|
||||
this.hasSource = options.hasSource;
|
||||
},
|
||||
|
||||
showHintsButton: function(nav) {
|
||||
this.$el.find('#show-hints-button').unbind().on('click',this.showHints.bind(this)).show();
|
||||
},
|
||||
|
||||
hideHintsButton: function(){
|
||||
$('#show-hints-button').hide();
|
||||
},
|
||||
|
||||
render:function() {
|
||||
this.$el.find('#restart-lesson-button').unbind().on('click',_.bind(this.restartLesson,this)).show();
|
||||
},
|
||||
|
||||
showHints: function() {
|
||||
this.trigger('hints:show','hint');
|
||||
},
|
||||
|
||||
restartLesson: function() {
|
||||
this.trigger('lesson:restart');
|
||||
}
|
||||
});
|
||||
});
|
139
src/main/resources/webgoat/static/js/goatApp/view/HintView.js
Normal file
139
src/main/resources/webgoat/static/js/goatApp/view/HintView.js
Normal file
@ -0,0 +1,139 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'goatApp/model/HintCollection'],
|
||||
function($,
|
||||
_,
|
||||
Backbone,
|
||||
HintCollection) {
|
||||
return Backbone.View.extend({
|
||||
el:'#lesson-hint-container',
|
||||
events: {
|
||||
"click #show-next-hint": "showNextHint",
|
||||
"click #show-prev-hint": "showPrevHint"
|
||||
},
|
||||
initialize: function() {
|
||||
this.curHint=0;
|
||||
this.collection = new HintCollection();
|
||||
this.hintsToShow = new Array();
|
||||
this.listenTo(this.collection,'loaded',this.onModelLoaded);
|
||||
this.hideHints();
|
||||
var self = this;
|
||||
// different way to do this?
|
||||
Backbone.on('navigatedToPage', function(nav){
|
||||
self.selectHints(nav);
|
||||
// end event delegation??
|
||||
});
|
||||
},
|
||||
|
||||
isVisible: function() {
|
||||
return this.$el.is(':visible');
|
||||
},
|
||||
|
||||
toggleLabel: function() {
|
||||
if (this.isVisible()) {
|
||||
$('#show-hints-button').text('Hide hints');
|
||||
} else {
|
||||
$('#show-hints-button').text('Show hints');
|
||||
}
|
||||
},
|
||||
|
||||
render:function() {
|
||||
if (this.isVisible()) {
|
||||
this.$el.hide(350, this.toggleLabel.bind(this));
|
||||
} else if (this.hintsToShow.length > 0) {
|
||||
this.$el.show(350, this.toggleLabel.bind(this));
|
||||
}
|
||||
|
||||
if (this.hintsToShow.length > 0) {
|
||||
this.hideShowPrevNextButtons();
|
||||
}
|
||||
this.displayHint(this.curHint);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Select the hints, we get '/WebGoat/HttpBasics/attack1' in the json (nav) we need to select all the hints
|
||||
* from the model where the assignment name is contained in the assignmentPath. We do this not to mess
|
||||
* with contextRoots etc and try to select the name from the url.
|
||||
*
|
||||
* @todo we can of course try to add the assignment name to the html form as attribute.
|
||||
*
|
||||
* @param nav the json structure for navigating
|
||||
*/
|
||||
filterHints: function(endpoints) {
|
||||
this.hintsToShow = [];
|
||||
_.each(endpoints, this.filterHint, this);
|
||||
|
||||
if (this.hintsToShow.length > 0) {
|
||||
this.trigger('hints:showButton');
|
||||
} else {
|
||||
this.trigger('hints:hideButton');
|
||||
}
|
||||
},
|
||||
|
||||
filterHint: function(endpoint) {
|
||||
var self = this;
|
||||
_.each(this.collection.models, function(hintModel) {
|
||||
if (endpoint.indexOf(hintModel.get('assignmentPath')) > -1 || decodeURIComponent(endpoint).indexOf(hintModel.get('assignmentPath')) > -1) {
|
||||
self.hintsToShow.push(hintModel.get('hint'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onModelLoaded: function() {
|
||||
this.trigger('hints:loaded',{'helpElement':'hints','value':true})
|
||||
},
|
||||
|
||||
hideHints: function() {
|
||||
if (this.$el.is(':visible')) {
|
||||
this.$el.hide(350, this.toggleLabel.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
showNextHint: function() {
|
||||
this.curHint = (this.curHint < this.hintsToShow.length -1) ? this.curHint+1 : this.curHint;
|
||||
this.hideShowPrevNextButtons();
|
||||
this.displayHint(this.curHint);
|
||||
},
|
||||
|
||||
showPrevHint: function() {
|
||||
this.curHint = (this.curHint > 0) ? this.curHint-1 : this.curHint;
|
||||
this.hideShowPrevNextButtons();
|
||||
this.displayHint(this.curHint);
|
||||
},
|
||||
|
||||
showFirstHint: function() {
|
||||
this.curHint = 0;
|
||||
this.hideShowPrevNextButtons();
|
||||
this.displayHint(this.curHint);
|
||||
},
|
||||
|
||||
displayHint: function(curHint) {
|
||||
if(this.hintsToShow.length == 0) {
|
||||
// this.hideHints();
|
||||
} else {
|
||||
this.$el.find('#lesson-hint-content').html(polyglot.t(this.hintsToShow[curHint]));
|
||||
}
|
||||
},
|
||||
|
||||
hideShowPrevNextButtons: function() {
|
||||
if (this.curHint === this.hintsToShow.length -1) {
|
||||
this.$el.find('#show-next-hint').css('visibility','hidden');
|
||||
} else {
|
||||
this.$el.find('#show-next-hint').css('visibility','visible');
|
||||
}
|
||||
|
||||
if (this.curHint === 0) {
|
||||
this.$el.find('#show-prev-hint').css('visibility','hidden');
|
||||
} else {
|
||||
this.$el.find('#show-prev-hint').css('visibility','visible');
|
||||
}
|
||||
},
|
||||
|
||||
getHintsCount: function () {
|
||||
return this.collection.length;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
@ -0,0 +1,233 @@
|
||||
//LessonContentView
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'libs/jquery.form',
|
||||
'goatApp/view/ErrorNotificationView',
|
||||
'goatApp/view/PaginationControlView'],
|
||||
function (
|
||||
$,
|
||||
_,
|
||||
Backbone,
|
||||
JQueryForm,
|
||||
ErrorNotificationView,
|
||||
PaginationControlView) {
|
||||
return Backbone.View.extend({
|
||||
el: '#lesson-content-wrapper', //TODO << get this fixed up in DOM
|
||||
|
||||
initialize: function (options) {
|
||||
options = options || {};
|
||||
new ErrorNotificationView();
|
||||
var self = this;
|
||||
Backbone.on('assignment:navTo', function (assignment) {
|
||||
var page = self.findPage(assignment);
|
||||
if (page != -1) {
|
||||
self.navToPage(page);
|
||||
}
|
||||
});
|
||||
setInterval(function () {
|
||||
this.updatePagination();
|
||||
}.bind(this), 5000);
|
||||
},
|
||||
|
||||
findPage: function (assignment) {
|
||||
for (var i = 0; i < this.$contentPages.length; i++) {
|
||||
var contentPage = this.$contentPages[i];
|
||||
var form = $('form.attack-form', contentPage);
|
||||
var action = form.attr('action')
|
||||
if (action !== undefined && action.includes(assignment.assignment)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
/* initial rendering */
|
||||
render: function () {
|
||||
this.$el.find('.lesson-content').html(this.model.get('content'));
|
||||
this.$el.find('.attack-feedback').hide();
|
||||
this.$el.find('.attack-output').hide();
|
||||
this.makeFormsAjax();
|
||||
$(window).scrollTop(0); //work-around til we get the scroll down sorted out
|
||||
var startPageNum = this.model.get('pageNum');
|
||||
this.initPagination(startPageNum);
|
||||
},
|
||||
|
||||
initPagination: function (startPageNum) {
|
||||
//get basic pagination info
|
||||
this.$contentPages = this.$el.find('.lesson-page-wrapper');
|
||||
var currentPage = (!isNaN(startPageNum) && startPageNum && startPageNum < this.$contentPages) ? startPageNum : 0;
|
||||
//init views & pagination
|
||||
this.showCurContentPage(currentPage);
|
||||
this.paginationControlView = new PaginationControlView(this.$contentPages, this.model.get('lessonUrl'), startPageNum);
|
||||
},
|
||||
|
||||
updatePagination: function () {
|
||||
if (this.paginationControlView != undefined) {
|
||||
this.paginationControlView.updateCollection();
|
||||
}
|
||||
},
|
||||
|
||||
getCurrentPage: function () {
|
||||
return this.currentPage;
|
||||
},
|
||||
|
||||
makeFormsAjax: function () {
|
||||
this.$form = $('form.attack-form');
|
||||
// turn off standard submit
|
||||
var self = this;
|
||||
// each submit handled per form
|
||||
this.$form.each(function () {
|
||||
$(this).submit(self.onFormSubmit.bind(self));
|
||||
});
|
||||
},
|
||||
|
||||
/* form submission handling */
|
||||
onFormSubmit: function (e) {
|
||||
var curForm = e.currentTarget; // the form from which the
|
||||
var self = this;
|
||||
// TODO custom Data prep for submission
|
||||
var prepareDataFunctionName = $(curForm).attr('prepareData');
|
||||
var callbackFunctionName = $(curForm).attr('callback');
|
||||
var submitData = (typeof webgoat.customjs[prepareDataFunctionName] === 'function') ? webgoat.customjs[prepareDataFunctionName]() : $(curForm).serialize();
|
||||
var additionalHeadersFunctionName = $(curForm).attr('additionalHeaders');
|
||||
var additionalHeaders = (typeof webgoat.customjs[additionalHeadersFunctionName] === 'function') ? webgoat.customjs[additionalHeadersFunctionName]() : function () {
|
||||
};
|
||||
var successCallBackFunctionName = $(curForm).attr('successCallback');
|
||||
var failureCallbackFunctionName = $(curForm).attr('failureCallback');
|
||||
var informationalCallbackFunctionName = $(curForm).attr('informationalCallback');
|
||||
var callbackFunction = (typeof webgoat.customjs[callbackFunctionName] === 'function') ? webgoat.customjs[callbackFunctionName] : function () {
|
||||
};
|
||||
this.curForm = curForm;
|
||||
this.$curFeedback = $(curForm).closest('.attack-container').find('.attack-feedback');
|
||||
this.$curOutput = $(curForm).closest('.attack-container').find('.attack-output');
|
||||
|
||||
var formUrl = $(curForm).attr('action');
|
||||
var formMethod = $(curForm).attr('method');
|
||||
var contentType = ($(curForm).attr('contentType')) ? $(curForm).attr('contentType') : 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
var encType = $(curForm).attr('enctype')
|
||||
|
||||
$.ajax({
|
||||
url: formUrl,
|
||||
headers: additionalHeaders,
|
||||
method: formMethod,
|
||||
processData: 'multipart/form-data' !== encType,
|
||||
contentType: 'multipart/form-data' === encType ? false : contentType,
|
||||
data: submitData,
|
||||
}).then(function (data) {
|
||||
self.onSuccessResponse(data, failureCallbackFunctionName, successCallBackFunctionName, informationalCallbackFunctionName)
|
||||
}, self.onErrorResponse.bind(self));
|
||||
return false;
|
||||
},
|
||||
|
||||
onSuccessResponse: function (data, failureCallbackFunctionName, successCallBackFunctionName, informationalCallbackFunctionName) {
|
||||
this.renderFeedback(data.feedback);
|
||||
this.renderOutput(data.output || "");
|
||||
|
||||
//var submitData = (typeof webgoat.customjs[prepareDataFunctionName] === 'function') ? webgoat.customjs[prepareDataFunctionName]() : $(curForm).serialize();
|
||||
var successCallbackFunction = (typeof webgoat.customjs[successCallBackFunctionName] === 'function') ? webgoat.customjs[successCallBackFunctionName] : function () {
|
||||
};
|
||||
var failureCallbackFunction = (typeof webgoat.customjs[failureCallbackFunctionName] === 'function') ? webgoat.customjs[failureCallbackFunctionName] : function () {
|
||||
};
|
||||
var informationalCallbackFunction = (typeof webgoat.customjs[informationalCallbackFunctionName] === 'function') ? webgoat.customjs[informationalCallbackFunctionName] : function () {
|
||||
};
|
||||
if (data.attemptWasMade) {
|
||||
if (data.lessonCompleted || data.assignmentCompleted) {
|
||||
this.markAssignmentComplete();
|
||||
successCallbackFunction(data); //data is likely not useful, except maybe the output ...
|
||||
this.trigger('assignment:complete');
|
||||
} else {
|
||||
this.markAssignmentIncomplete(data); //again, data might be useful, especially the output
|
||||
failureCallbackFunction();
|
||||
}
|
||||
} else {
|
||||
informationalCallbackFunction();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
markAssignmentComplete: function () {
|
||||
this.curForm.reset();
|
||||
$(this.curForm).siblings('.assignment-success').find('i').removeClass('hidden');
|
||||
this.paginationControlView.updateCollection();
|
||||
},
|
||||
|
||||
markAssignmentIncomplete: function () {
|
||||
$(this.curForm).siblings('.assignment-success').find('i').addClass('hidden');
|
||||
},
|
||||
|
||||
onErrorResponse: function (data, b, c) {
|
||||
console.error(data);
|
||||
if (data.status == 403) {
|
||||
this.renderFeedback(data.responseText);
|
||||
}
|
||||
console.error(b);
|
||||
console.error(c);
|
||||
return false;
|
||||
},
|
||||
|
||||
removeSlashesFromJSON: function (str) {
|
||||
// slashes are leftover escapes from JSON serialization by server
|
||||
// for every two char sequence starting with backslash,
|
||||
// replace them in the text with second char only
|
||||
return str.replace(/\\(.)/g, "$1");
|
||||
},
|
||||
|
||||
renderFeedback: function (feedback) {
|
||||
var s = this.removeSlashesFromJSON(feedback);
|
||||
this.$curFeedback.html(polyglot.t(s) || "");
|
||||
this.$curFeedback.show(400)
|
||||
|
||||
},
|
||||
|
||||
renderOutput: function (output) {
|
||||
var s = this.removeSlashesFromJSON(output);
|
||||
this.$curOutput.html(polyglot.t(s) || "");
|
||||
this.$curOutput.show(400)
|
||||
},
|
||||
|
||||
showCurContentPage: function (pageNum) {
|
||||
this.$contentPages.hide();
|
||||
this.$el.find(this.$contentPages[pageNum]).show();
|
||||
},
|
||||
|
||||
findAssigmentEndpointsOnPage: function (pageNumber) {
|
||||
var contentPage = this.$contentPages[pageNumber];
|
||||
var endpoints = []; //going to assume uniqueness since these are assignments
|
||||
var pageForms = $(contentPage).find('form.attack-form');
|
||||
for (var i = 0; i < pageForms.length; i++) {
|
||||
endpoints.push(pageForms[i].action);
|
||||
}
|
||||
return endpoints;
|
||||
},
|
||||
|
||||
onNavToPage: function (pageNum) {
|
||||
var assignmentPaths = this.findAssigmentEndpointsOnPage(pageNum);
|
||||
this.trigger('endpoints:filtered', assignmentPaths);
|
||||
},
|
||||
|
||||
navToPage: function (pageNum) {
|
||||
this.paginationControlView.setCurrentPage(pageNum);//provides validation
|
||||
this.showCurContentPage(this.paginationControlView.currentPage);
|
||||
this.paginationControlView.render();
|
||||
this.paginationControlView.hideShowNavButtons();
|
||||
this.onNavToPage(pageNum);
|
||||
//var assignmentPaths = this.findAssigmentEndpointsOnPage(pageNum);
|
||||
//this.trigger('endpoints:filtered',assignmentPaths);
|
||||
},
|
||||
|
||||
/* for testing */
|
||||
showTestParam: function (param) {
|
||||
this.$el.find('.lesson-content').html('test:' + param);
|
||||
},
|
||||
|
||||
resetLesson: function () {
|
||||
this.$el.find('.attack-feedback').hide();
|
||||
this.$el.find('.attack-output').hide();
|
||||
this.markAssignmentIncomplete();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
@ -0,0 +1,27 @@
|
||||
//
|
||||
//UserAndInfoView
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone'],
|
||||
function($,
|
||||
_,
|
||||
Backbone) {
|
||||
return Backbone.View.extend({
|
||||
el:'#toggle-menu',
|
||||
|
||||
events: {
|
||||
"click": "toggleMenu"
|
||||
},
|
||||
|
||||
toggleMenu: function(e) {
|
||||
//left
|
||||
if (!$('.sidebarRight').hasClass('.sidebar-toggle-right')) {
|
||||
$('.sidebarRight').removeClass('sidebar-toggle-right');
|
||||
$('.main-content-wrapper').removeClass('main-content-toggle-right');
|
||||
}
|
||||
$('.sidebar').toggleClass('sidebar-toggle');
|
||||
$('.main-content-wrapper').toggleClass('main-content-toggle-left');
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,30 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'goatApp/support/GoatUtils',
|
||||
'goatApp/view/MenuItemView'],
|
||||
function(
|
||||
$,
|
||||
_,
|
||||
Backbone,
|
||||
GoatUtils,
|
||||
MenuItemView) {
|
||||
|
||||
return Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
options = options || {};
|
||||
this.items = options.items;
|
||||
},
|
||||
render: function() {
|
||||
var viewItems = [];
|
||||
for (var i=0;i<this.items.length;i++) {
|
||||
var listItem = $('<li>',{text:this.items[i].name});
|
||||
//viewItems
|
||||
viewItems.push(listItem);
|
||||
}
|
||||
return viewItems;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
138
src/main/resources/webgoat/static/js/goatApp/view/MenuView.js
Normal file
138
src/main/resources/webgoat/static/js/goatApp/view/MenuView.js
Normal file
@ -0,0 +1,138 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'goatApp/model/MenuCollection',
|
||||
'goatApp/view/MenuItemView',
|
||||
'goatApp/support/GoatUtils'],
|
||||
function(
|
||||
$,
|
||||
_,
|
||||
Backbone,
|
||||
MenuCollection,
|
||||
MenuItemView,
|
||||
GoatUtils) {
|
||||
return Backbone.View.extend({
|
||||
el:'#menu-container',
|
||||
//TODO: set template
|
||||
initialize: function() {
|
||||
this.collection = new MenuCollection();
|
||||
this.addSpinner();
|
||||
this.listenTo(this.collection,'menuData:loaded',this.render);
|
||||
// this.listenTo(this,'menu:click',this.accordionMenu);
|
||||
this.curLessonLinkId = '';
|
||||
},
|
||||
|
||||
addSpinner: function() {
|
||||
//<i class="fa fa-spinner fa-spin"></i>
|
||||
this.$el.append($('<i>',{class:'fa fa-3x fa-spinner fa-spin'}));
|
||||
},
|
||||
|
||||
removeSpinner: function() {
|
||||
this.$el.find('i.fa-spinner').remove();
|
||||
},
|
||||
|
||||
// rendering top level menu
|
||||
render: function (){
|
||||
//for now, just brute force
|
||||
//TODO: refactor into sub-views/components
|
||||
this.removeSpinner();
|
||||
var items, catItems, stages;
|
||||
items = this.collection.models; // top level items
|
||||
var menuMarkup = '';
|
||||
var menuUl = $('<ul>',{class:'nano-content'});
|
||||
for(var i=0;i<items.length;i++) { //CATEGORY LEVEL
|
||||
var catId, category, catLink, catArrow, catLinkText, lessonName, stageName;
|
||||
catId = GoatUtils.makeId(items[i].get('name'));
|
||||
category = $('<li>',{class:'sub-menu ng-scope'});
|
||||
catLink = $('<a>',{'category':catId});
|
||||
catArrow = $('<i>',{class:'fa fa-angle-right pull-right'});
|
||||
catLinkText = $('<span>',{text:items[i].get('name')});
|
||||
|
||||
catLink.append(catArrow);
|
||||
catLink.append(catLinkText);
|
||||
var self = this;
|
||||
catLink.click(_.bind(this.expandCategory,this,catId));
|
||||
category.append(catLink);
|
||||
// lesson level (first children level)
|
||||
//var lessons = new MenuItemView({items:items[i].get('children')}).render();
|
||||
var lessons=items[i].get('children');
|
||||
if (lessons) {
|
||||
var categoryLessonList = $('<ul>',{class:'slideDown lessonsAndStages',id:catId}); //keepOpen
|
||||
for (var j=0; j < lessons.length;j++) {
|
||||
var lessonItem = $('<li>',{class:'lesson'});
|
||||
var lessonName = polyglot.t(lessons[j].name);
|
||||
var lessonId = catId + '-' + GoatUtils.makeId(lessonName);
|
||||
if (this.curLessonLinkId === lessonId) {
|
||||
lessonItem.addClass('selected');
|
||||
}
|
||||
var lessonLink = $('<a>',{href:lessons[j].link,text:lessonName,id:lessonId});
|
||||
lessonLink.click(_.bind(this.onLessonClick,this,lessonId));
|
||||
lessonItem.append(lessonLink);
|
||||
//check for lab/stages
|
||||
categoryLessonList.append(lessonItem);
|
||||
if (lessons[j].complete) {
|
||||
lessonItem.append($('<span>',{class:'glyphicon glyphicon-check lesson-complete'}));
|
||||
}
|
||||
var stages = lessons[j].children;
|
||||
for (k=0; k < stages.length; k++) {
|
||||
var stageItem = $('<li>',{class:'stage'});
|
||||
var stageName = stages[k].name;
|
||||
var stageId = lessonId + '-stage' + k;
|
||||
if (this.curLessonLinkId === stageId) {
|
||||
stageItem.addClass('selected');
|
||||
}
|
||||
var stageLink = $('<a>',{href:stages[k].link,text:stageName,id:stageId});
|
||||
stageLink.click(_.bind(this.onLessonClick,this,stageId));
|
||||
stageItem.append(stageLink);
|
||||
categoryLessonList.append(stageItem);
|
||||
if (stages[k].complete) {
|
||||
stageItem.append($('<span>',{class:'glyphicon glyphicon-check lesson-complete'}));
|
||||
}
|
||||
}
|
||||
}
|
||||
category.append(categoryLessonList);
|
||||
}
|
||||
|
||||
menuUl.append(category);
|
||||
}
|
||||
this.$el.html(menuUl);
|
||||
//if we need to keep a menu open
|
||||
if (this.openMenu) {
|
||||
$('#'+this.openMenu).show();
|
||||
}
|
||||
},
|
||||
|
||||
updateMenu: function() {
|
||||
//for now ...
|
||||
this.collection.fetch();
|
||||
},
|
||||
|
||||
onLessonClick: function (elementId) {
|
||||
if (this.curLessonLinkId) {
|
||||
$('#'+this.curLessonLinkId).removeClass('selected').parent().removeClass('selected');
|
||||
}
|
||||
//update
|
||||
$('#'+elementId).addClass('selected').parent().addClass('selected');
|
||||
this.curLessonLinkId = elementId;
|
||||
},
|
||||
|
||||
expandCategory: function (id) {
|
||||
if (id) {
|
||||
//this.selectedCategory = id;
|
||||
this.accordionMenu(id);
|
||||
}
|
||||
},
|
||||
|
||||
accordionMenu: function(id) {
|
||||
if (this.openMenu !== id) {
|
||||
this.$el.find('#' + this.openMenu).slideUp(200);
|
||||
this.$el.find('#' + id).slideDown(300);
|
||||
this.openMenu = id;
|
||||
} else { //it's open
|
||||
this.$el.find('#' + id).slideUp(300).attr('isOpen', 0);
|
||||
this.openMenu = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,198 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'goatApp/model/LessonOverviewCollection',
|
||||
'text!templates/paging_controls.html'],
|
||||
function ($,
|
||||
_,
|
||||
Backbone,
|
||||
LessonOverviewCollection,
|
||||
PaginationTemplate) {
|
||||
return Backbone.View.extend({
|
||||
template: PaginationTemplate,
|
||||
el: '#lesson-page-controls',
|
||||
|
||||
initialize: function ($contentPages,baseLessonUrl,initPageNum) {
|
||||
this.$contentPages = $contentPages;
|
||||
this.collection = new LessonOverviewCollection();
|
||||
this.listenTo(this.collection, 'reset', this.render);
|
||||
this.numPages = this.$contentPages.length;
|
||||
this.baseUrl = baseLessonUrl;
|
||||
this.collection.fetch({reset:true});
|
||||
this.initPagination(initPageNum);
|
||||
//this.render();
|
||||
},
|
||||
|
||||
render: function (e) {
|
||||
this.parseLinks();
|
||||
var t = _.template(this.template);
|
||||
this.$el.html(t({'overview':this.lessonOverview}));
|
||||
this.bindNavButtons();
|
||||
this.hideShowNavButtons();
|
||||
},
|
||||
|
||||
updateCollection: function() {
|
||||
this.collection.fetch({reset:true});
|
||||
},
|
||||
|
||||
bindNavButtons: function() {
|
||||
this.$el.find('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').unbind().on('click',this.incrementPageView.bind(this));
|
||||
this.$el.find('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').unbind().on('click', this.decrementPageView.bind(this));
|
||||
this.navButtonsBound = true;
|
||||
},
|
||||
|
||||
parseLinks: function() {
|
||||
var assignmentCount = this.$contentPages.find('.attack-container');
|
||||
var solvedMap = {};
|
||||
var pages = [];
|
||||
|
||||
_.each(this.collection.models, function(model) {
|
||||
//alert (model.get('solved'));
|
||||
if (model.get('solved')) {
|
||||
var key = model.get('assignment').path.replace(/\//g,'');
|
||||
solvedMap[key] = model.get('assignment').name;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
isAttackSolved = function (path) {
|
||||
//strip
|
||||
var newPath = path.replace(/^\/WebGoat/,'');
|
||||
var newPath = newPath.replace(/\//g,'');
|
||||
if (typeof solvedMap[newPath] !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var self = this;
|
||||
var pages, pageClass, solved;
|
||||
_.each(this.$contentPages,function(page,index) {
|
||||
var curPageClass = (self.currentPage == index) ? ' cur-page' : '';
|
||||
|
||||
if ($(page).find('.attack-container').length < 1) { // no assignments [attacks]
|
||||
pageClass = 'page-link';
|
||||
pages.push({content:'content',pageClass:pageClass,curPageClass:curPageClass});
|
||||
} else {
|
||||
var $assignmentForms = $(page).find('.attack-container form.attack-form');
|
||||
// use for loop to avoid anonymous function scope hell
|
||||
//var pageAssignments = {content:'attack',attacks:[]}
|
||||
pageClass = 'attack-link'
|
||||
var solvedClass = 'solved-true'
|
||||
for (var i=0; i< $assignmentForms.length; i++) {
|
||||
//normalize path
|
||||
var action = $assignmentForms.attr('action');
|
||||
if (action.endsWith("/WebGoat/WebWolf/mail/")) {
|
||||
//fix for now. the find does not seem to work properly and gets confused with two /mail
|
||||
action = "/WebGoat/WebWolf/mail/send";
|
||||
}
|
||||
if (action.indexOf("?")>-1) {
|
||||
//used to also mark forms like JWT assignment 8 complete
|
||||
action = action.substring(0,action.indexOf("?"));
|
||||
}
|
||||
if (action && isAttackSolved(action)) {
|
||||
} else {
|
||||
solvedClass = 'solved-false';
|
||||
}
|
||||
}
|
||||
pages.push({solvedClass:solvedClass,content:'assignment',curPageClass:curPageClass,pageClass:pageClass});
|
||||
}
|
||||
});
|
||||
|
||||
//assign to the view
|
||||
this.lessonOverview = {
|
||||
baseUrl: this.baseUrl,
|
||||
pages: pages
|
||||
}
|
||||
},
|
||||
|
||||
showPrevPageButton: function() {
|
||||
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').show();
|
||||
},
|
||||
|
||||
hidePrevPageButton: function() {
|
||||
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-left.show-prev-page').hide();
|
||||
},
|
||||
|
||||
showNextPageButton: function() {
|
||||
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').show();
|
||||
},
|
||||
|
||||
hideNextPageButton: function() {
|
||||
$('span.glyphicon-class.glyphicon.glyphicon-circle-arrow-right.show-next-page').hide();
|
||||
},
|
||||
|
||||
initPagination: function(initPageNum) {
|
||||
//track pagination state in this view ... start at 0 .. unless a pageNum was provided
|
||||
this.currentPage = !initPageNum ? 0 : initPageNum;
|
||||
},
|
||||
|
||||
setCurrentPage: function (pageNum) {
|
||||
this.currentPage = (_.isNumber(pageNum) && pageNum < this.numPages) ? pageNum : 0;
|
||||
},
|
||||
|
||||
/* increment, decrement & display handlers */
|
||||
incrementPageView: function() {
|
||||
if (this.currentPage < this.numPages -1) {
|
||||
this.currentPage++;
|
||||
window.location.href = this.baseUrl + '/' + this.currentPage;
|
||||
}
|
||||
|
||||
if (this.currentPage > 0) {
|
||||
this.showPrevPageButton();
|
||||
}
|
||||
|
||||
if (this.currentPage >= this.numPages -1) {
|
||||
this.hideNextPageButton();
|
||||
this.showPrevPageButton;
|
||||
}
|
||||
this.collection.fetch({reset:true});
|
||||
},
|
||||
|
||||
decrementPageView: function() {
|
||||
if (this.currentPage > 0) {
|
||||
this.currentPage--;
|
||||
window.location.href = this.baseUrl + '/' + this.currentPage;
|
||||
}
|
||||
|
||||
if (this.currentPage < this.numPages -1) {
|
||||
this.showNextPageButton();
|
||||
}
|
||||
|
||||
if (this.currentPage == 0) {
|
||||
this.hidePrevPageButton();
|
||||
this.showNextPageButton()
|
||||
}
|
||||
this.collection.fetch({reset:true});
|
||||
},
|
||||
|
||||
hideShowNavButtons: function () {
|
||||
//one page only
|
||||
if (this.numPages === 1) {
|
||||
this.hidePrevPageButton();
|
||||
this.hideNextPageButton();
|
||||
}
|
||||
//first page
|
||||
if (this.currentPage === 0) {
|
||||
this.hidePrevPageButton();
|
||||
if (this.numPages > 1) {
|
||||
this.showNextPageButton();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// > first page, but not last
|
||||
if (this.currentPage > 0 && this.currentPage < this.numPages -1) {
|
||||
this.showNextPageButton();
|
||||
this.showPrevPageButton();
|
||||
return;
|
||||
}
|
||||
// last page and more than one page
|
||||
if (this.currentPage === this.numPages -1 && this.numPages > 1) {
|
||||
this.hideNextPageButton();
|
||||
this.showPrevPageButton();
|
||||
return;
|
||||
}
|
||||
|
||||
},
|
||||
});
|
||||
});
|
@ -0,0 +1,21 @@
|
||||
define(['jquery', 'backbone', 'underscore', 'goatApp/model/ReportCardModel', 'text!templates/report_card.html'],
|
||||
function ($, Backbone, _, ReportCardModel, ReportCardTemplate) {
|
||||
return Backbone.View.extend({
|
||||
el: '#report-card-page',
|
||||
template: ReportCardTemplate,
|
||||
|
||||
initialize: function () {
|
||||
var _this = this;
|
||||
this.model = new ReportCardModel();
|
||||
this.model.fetch().then(function() {
|
||||
_this.render();
|
||||
});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var t = _.template(this.template);
|
||||
this.$el.html(t(this.model.toJSON()));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone',
|
||||
'goatApp/model/FlagsCollection',
|
||||
'text!templates/scoreboard.html'],
|
||||
function($,
|
||||
_,
|
||||
Backbone,
|
||||
FlagsCollection,
|
||||
ScoreboardTemplate) {
|
||||
return Backbone.View.extend({
|
||||
el:'#scoreboard',
|
||||
|
||||
initialize: function() {
|
||||
this.template = ScoreboardTemplate,
|
||||
this.collection = new FlagsCollection();
|
||||
this.listenTo(this.collection,'reset',this.render)
|
||||
this.collection.fetch({reset:true});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
//this.$el.html('test');
|
||||
var t = _.template(this.template);
|
||||
this.$el.html(t({'rankings':this.collection.toJSON()}));
|
||||
setTimeout(this.pollData.bind(this), 5000);
|
||||
},
|
||||
|
||||
pollData: function() {
|
||||
this.collection.fetch({reset:true});
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone'],
|
||||
function($,_,Backbone) {
|
||||
return Backbone.View.extend({
|
||||
el:'#header #lesson-title-wrapper',
|
||||
|
||||
render:function(title) {
|
||||
var lessonTitleEl = $('<h1>',{id:'lesson-title',text:polyglot.t(title)});
|
||||
this.$el.html(lessonTitleEl);
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,45 @@
|
||||
//UserAndInfoView
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'backbone'],
|
||||
function($,
|
||||
_,
|
||||
Backbone) {
|
||||
return Backbone.View.extend({
|
||||
el:'#user-and-info-nav', //Check this,
|
||||
|
||||
events: {
|
||||
'click #about-button': 'showAboutModal',
|
||||
'click #user-menu': 'showUserMenu'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
},
|
||||
|
||||
render:function(title) {
|
||||
},
|
||||
|
||||
showUserMenu: function() {
|
||||
var menu = this.$el.find('.dropdown-menu');
|
||||
if (menu.is(':visible')) {
|
||||
menu.hide(200);
|
||||
} else {
|
||||
menu.show(400);
|
||||
/*menu.on('mouseout', function() {
|
||||
$('#user-and-info-nav .dropdown-menu').hide(200);
|
||||
});*/
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
showAboutModal: function() {
|
||||
$('#about-modal').show(400);
|
||||
$('#about-modal div.modal-header button.close, #about-modal div.modal-footer button').unbind('click').on('click', function() {
|
||||
$('#about-modal').hide(200);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user