diff --git a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/AbstractLesson.java b/webgoat-container/src/main/java/org/owasp/webgoat/lessons/AbstractLesson.java index 5834a6c41..4effc1d76 100644 --- a/webgoat-container/src/main/java/org/owasp/webgoat/lessons/AbstractLesson.java +++ b/webgoat-container/src/main/java/org/owasp/webgoat/lessons/AbstractLesson.java @@ -626,18 +626,37 @@ public abstract class AbstractLesson extends Screen implements Comparable 0), - }); + this.onInfoLoaded = function() { + console.log("Lesson info loaded") + this.helpControlsView = new HelpControlsView({ + hasPlan:this.lessonInfoModel.get('hasPlan'), + hasSolution:this.lessonInfoModel.get('hasSolution'), + hasSource:this.lessonInfoModel.get('hasSource'), + hasHints:(this.lessonInfoModel.get('numberHints') > 0), + }); - this.listenTo(this.helpControlsView,'plan:show',this.hideShowHelps); - this.listenTo(this.helpControlsView,'solution:show',this.hideShowHelps); - this.listenTo(this.helpControlsView,'hints:show',this.onShowHints) - this.listenTo(this.helpControlsView,'source:show',this.hideShowHelps); - this.listenTo(this.helpControlsView,'lesson:restart',this.restartLesson); + this.listenTo(this.helpControlsView,'plan:show',this.hideShowHelps); + this.listenTo(this.helpControlsView,'solution:show',this.hideShowHelps); + this.listenTo(this.helpControlsView,'hints:show',this.onShowHints) + this.listenTo(this.helpControlsView,'source:show',this.hideShowHelps); + this.listenTo(this.helpControlsView,'lesson:restart',this.restartLesson); - this.helpControlsView.render(); + this.helpControlsView.render(); - this.titleView.render(this.lessonInfoModel.get('lessonTitle')); - }; + this.titleView.render(this.lessonInfoModel.get('lessonTitle')); + }; - this.onContentLoaded = function(loadHelps) { - this.lessonInfoModel = new LessonInfoModel(); - this.listenTo(this.lessonInfoModel,'info:loaded',this.onInfoLoaded); + this.onContentLoaded = function(loadHelps) { + console.log("Lesson content loaded") + this.lessonInfoModel = new LessonInfoModel(); + this.listenTo(this.lessonInfoModel,'info:loaded',this.onInfoLoaded); - if (loadHelps) { - this.helpControlsView = null; - this.lessonView.model = this.lessonContent; - this.lessonView.render(); - - this.planView = new PlanView(); - this.solutionView = new SolutionView(); - this.sourceView = new SourceView(); - this.lessonHintView = new HintView(); - this.cookieView = new CookieView(); - //TODO: instantiate model with values (not sure why was not working before) - var paramModel = new ParamModel({}); - paramModel.set('screenParam',this.lessonContent.get('screenParam')); - paramModel.set('menuParam',this.lessonContent.get('menuParam')); - paramModel.set('stageParam',this.lessonContent.get('stageParam')); - this.paramView = new ParamView({model:paramModel}); + if (loadHelps) { + this.helpControlsView = null; + this.lessonView.model = this.lessonContent; + this.lessonView.render(); + + this.planView = new PlanView(); + this.solutionView = new SolutionView(); + this.sourceView = new SourceView(); + this.lessonHintView = new HintView(); + this.cookieView = new CookieView(); + //TODO: instantiate model with values (not sure why was not working before) + var paramModel = new ParamModel({}); + paramModel.set('scrParam',this.lessonContent.get('scrParam')); + paramModel.set('menuParam',this.lessonContent.get('menuParam')); + paramModel.set('stageParam',this.lessonContent.get('stageParam')); + paramModel.set('numParam',this.lessonContent.get('numParam')); + this.paramView = new ParamView({model:paramModel}); - $('.lesson-help').hide(); - } - this.trigger('menu:reload'); - }; + $('.lesson-help').hide(); + } + this.trigger('menu:reload'); + }; - this.addCurHelpState = function (curHelp) { - this.helpsLoaded[curHelp.helpElement] = curHelp.value; - }; + this.addCurHelpState = function (curHelp) { + this.helpsLoaded[curHelp.helpElement] = curHelp.value; + }; - this.hideShowHelps = function(showHelp) { - var showId = '#lesson-' + showHelp + '-row'; - var contentId = '#lesson-' + showHelp + '-content'; - $('.lesson-help').not(showId).hide(); - if (!showId) { - return; - } + this.hideShowHelps = function(showHelp) { + var showId = '#lesson-' + showHelp + '-row'; + var contentId = '#lesson-' + showHelp + '-content'; + $('.lesson-help').not(showId).hide(); + if (!showId) { + return; + } - if ($(showId).is(':visible')) { - $(showId).hide(); - return; - } else { - //TODO: move individual .html operations into individual help views - switch(showHelp) { - case 'plan': - $(contentId).html(this.planView.model.get('content')); - break; - case 'solution': - $(showId).html(this.solutionView.model.get('content')); - break; - case 'source': - $(contentId).html('
' + this.sourceView.model.get('content') + '
'); - break; - } - $(showId).show(); - GoatUtils.scrollToHelp() - } - }; + if ($(showId).is(':visible')) { + $(showId).hide(); + return; + } else { + //TODO: move individual .html operations into individual help views + switch(showHelp) { + case 'plan': + $(contentId).html(this.planView.model.get('content')); + break; + case 'solution': + $(showId).html(this.solutionView.model.get('content')); + break; + case 'source': + $(contentId).html('
' + this.sourceView.model.get('content') + '
'); + break; + } + $(showId).show(); + GoatUtils.scrollToHelp() + } + }; - this.onShowHints = function() { - this.lessonHintView.render(); - }; + this.onShowHints = function() { + this.lessonHintView.render(); + }; - this.restartLesson = function() { - var self=this; - $.ajax({ - url:'service/restartlesson.mvc', - method:'GET' - }).then(function() { - self.loadLesson(self.screen,self.menu); - }); - }; + this.restartLesson = function() { + var self=this; + var fragment = "attack/" + self.scr + "/" + self.menu; + console.log("Navigating to " + fragment); + // Avoiding the trigger event - handle - navigate loop by + // loading the lesson explicitly (after executing the restart + // servlet). + goatRouter.navigate(fragment); + // Resetting the user's lesson state (assuming a single browser + // and session per user). + $.ajax({ + url:'service/restartlesson.mvc', + method:'GET' + }).done(function(text) { + console.log("Received a response from the restart servlet: '" + text + "'"); + // Explicitly loading the lesson instead of triggering an + // event in goatRouter.navigate(). + self.loadLesson(self.scr,self.menu); + }); + }; - }; - return Controller; -}); \ No newline at end of file + }; + return Controller; +}); diff --git a/webgoat-container/src/main/webapp/js/goatApp/model/LessonContentModel.js b/webgoat-container/src/main/webapp/js/goatApp/model/LessonContentModel.js index e588447b2..d4923640d 100644 --- a/webgoat-container/src/main/webapp/js/goatApp/model/LessonContentModel.js +++ b/webgoat-container/src/main/webapp/js/goatApp/model/LessonContentModel.js @@ -1,46 +1,54 @@ define(['jquery', - 'underscore', - 'backbone', - 'goatApp/model/HTMLContentModel'], - function($, - _, - Backbone, - HTMLContentModel){ + 'underscore', + 'backbone', + 'goatApp/model/HTMLContentModel'], + function($, + _, + Backbone, + HTMLContentModel){ - return HTMLContentModel.extend({ - urlRoot:null, - defaults: { - items:null, - selectedItem:null - }, - initialize: function (options) { - this.screenParam = null; - this.menuParam = null; - this.stageParam = null; - this.baseUrlRoot = 'attack?Screen='; - }, - loadData: function(options) { - this.urlRoot = this.baseUrlRoot +options.screen + '&menu=' + options.menu + '&stage=' + options.stage; - this.set('menuParam',options.menu); - this.set('screenParam',options.screen); - this.set('stageParam',options.stage) - var self=this; - this.fetch().then(function(data) { - self.setContent(data); - }); - }, + return HTMLContentModel.extend({ + urlRoot:null, + defaults: { + items:null, + selectedItem:null + }, + initialize: function (options) { + this.scrParam = null; + this.menuParam = null; + this.stageParam = null; + this.numParam = null; + this.baseUrlRoot = 'attack'; + }, + loadData: function(options) { + this.urlRoot = this.baseUrlRoot + "?Screen=" + options.scr + '&menu=' + options.menu; + if (options.stage != null) { + this.urlRoot += '&stage=' + options.stage; + } + if (options.num != null) { + this.urlRoot += '&Num=' + options.num; + } + this.set('menuParam', options.menu); + this.set('scrParam', options.scr); + this.set('stageParam', options.stage) + this.set('numParam', options.num) + var self = this; + this.fetch().done(function(data) { + self.setContent(data); + }); + }, - setContent: function(content, loadHelps) { - if (typeof loadHelps === 'undefined') { - loadHelps = true; - } - this.set('content',content); - this.trigger('content:loaded',this,loadHelps); - }, + setContent: function(content, loadHelps) { + if (typeof loadHelps === 'undefined') { + loadHelps = true; + } + this.set('content',content); + this.trigger('content:loaded',this,loadHelps); + }, - fetch: function (options) { - options = options || {}; - return Backbone.Model.prototype.fetch.call(this, _.extend({ dataType: "html"}, options)); - } - }); -}); \ No newline at end of file + fetch: function (options) { + options = options || {}; + return Backbone.Model.prototype.fetch.call(this, _.extend({ dataType: "html"}, options)); + } + }); +}); diff --git a/webgoat-container/src/main/webapp/js/goatApp/view/GoatRouter.js b/webgoat-container/src/main/webapp/js/goatApp/view/GoatRouter.js index 9b1b20717..b95167123 100644 --- a/webgoat-container/src/main/webapp/js/goatApp/view/GoatRouter.js +++ b/webgoat-container/src/main/webapp/js/goatApp/view/GoatRouter.js @@ -19,7 +19,9 @@ define(['jquery', var GoatAppRouter = Backbone.Router.extend({ routes: { 'welcome':'welcomeRoute', - 'attack/:scr/:menu(/:stage)':'attackRoute', + 'attack/:scr/:menu':'attackRoute', + 'attack/:scr/:menu/:stage':'attackRoute', + 'attack/:scr/:menu/*stage/:num':'attackRoute', }, lessonController: new LessonController({ @@ -35,14 +37,17 @@ define(['jquery', this.lessonController.start(); // this.menuController.initMenu(); - goatRouter.on('route:attackRoute', function(scr,menu,stage) { - this.lessonController.loadLesson(scr,menu,stage); + goatRouter.on('route:attackRoute', function(scr,menu,stage,num) { + this.lessonController.loadLesson(scr,menu,stage,num); this.menuController.updateMenu(scr,menu); //update menu }); goatRouter.on('route:welcomeRoute', function() { this.lessonController.loadWelcome(); }); + goatRouter.on("route", function(route, params) { + console.log("Got a route event: " + route + ", params: " + params); + }); Backbone.history.start(); this.listenTo(this.lessonController, 'menu:reload',this.reloadMenu) @@ -57,4 +62,4 @@ define(['jquery', return GoatAppRouter; -}); \ No newline at end of file +}); diff --git a/webgoat-container/src/main/webapp/js/goatApp/view/LessonContentView.js b/webgoat-container/src/main/webapp/js/goatApp/view/LessonContentView.js index 94d5176bc..d51bf5946 100644 --- a/webgoat-container/src/main/webapp/js/goatApp/view/LessonContentView.js +++ b/webgoat-container/src/main/webapp/js/goatApp/view/LessonContentView.js @@ -1,59 +1,64 @@ //LessonContentView define(['jquery', - 'underscore', - 'backbone', - 'libs/jquery.form'], - function( - $, - _, - Backbone, - JQueryForm) { - return Backbone.View.extend({ - el:'#lesson-content-wrapper', //TODO << get this fixed up in DOM + 'underscore', + 'backbone', + 'libs/jquery.form'], + function( + $, + _, + Backbone, + JQueryForm) { + return Backbone.View.extend({ + el:'#lesson-content-wrapper', //TODO << get this fixed up in DOM - initialize: function(options) { - options = options || {}; - }, + initialize: function(options) { + options = options || {}; + }, - render: function() { - this.$el.html(this.model.get('content')); - this.makeFormsAjax(); - this.ajaxifyAttackHref(); - $(window).scrollTop(0); //work-around til we get the scroll down sorted out - }, - - //TODO: reimplement this in custom fashion maybe? - makeFormsAjax: function () { - var options = { - success:this.reLoadView.bind(this), - url:'attack?Screen=' + this.model.get('screenParam') + '&menu=' + this.model.get('menuParam'), - type:'GET' - // $.ajax options can be used here too, for example: - //timeout: 3000 - }; - //hook forms //TODO: clarify form selectors later - $("form").ajaxForm(options); + render: function() { + this.$el.html(this.model.get('content')); + this.makeFormsAjax(); + this.ajaxifyAttackHref(); + $(window).scrollTop(0); //work-around til we get the scroll down sorted out + }, + + //TODO: reimplement this in custom fashion maybe? + makeFormsAjax: function () { + var options = { + success:this.reLoadView.bind(this), + url: this.model.urlRoot, + type:'GET' + // $.ajax options can be used here too, for example: + //timeout: 3000 + }; + //hook forms //TODO: clarify form selectors later + $("form").ajaxForm(options); }, ajaxifyAttackHref: function() { // rewrite any links with hrefs point to relative attack URLs - var self = this; - $.each($('a[href^="attack?"]'),function(i,el) { - var url = $(el).attr('href'); - $(el).unbind('click').attr('href','#').attr('link',url); - //TODO pull currentMenuId - $(el).click(function() { - event.preventDefault(); - var _url = $(el).attr('link'); - $.get(_url, {success:self.reloadView.bind(self)}); - }); - }); - }, + var self = this; + // The current LessonAdapter#getLink() generates a hash-mark link. It will not match the mask below. + // Besides, the new MVC code registers an event handler that will reload the lesson according to the route. + $.each($('a[href^="attack?"]'),function(i,el) { + var url = $(el).attr('href'); + $(el).unbind('click').attr('href','#').attr('link',url); + //TODO pull currentMenuId + $(el).click(function(event) { + event.preventDefault(); + var _url = $(el).attr('link'); + console.log("About to GET " + _url); + $.get(_url) + .done(self.reLoadView.bind(self)) + .fail(function() { alert("failed to GET " + _url); }); + }); + }); + }, reLoadView: function(content) { - this.model.setContent(content); - this.render(); + this.model.setContent(content); + this.render(); } - }); + }); - -}); \ No newline at end of file + +}); diff --git a/webgoat-container/src/main/webapp/main.jsp b/webgoat-container/src/main/webapp/main.jsp index eeb9c9e06..7d21b84ca 100644 --- a/webgoat-container/src/main/webapp/main.jsp +++ b/webgoat-container/src/main/webapp/main.jsp @@ -104,7 +104,7 @@ if (stages != null) for (int i = 0; i < stages.length; i++) { %> - <%=(rla.isStageComplete(webSession, stages[i]) ? lessonComplete : "")%>Stage <%=i + 1%>: <%=stages[i]%> + <%=(rla.isStageComplete(webSession, stages[i]) ? lessonComplete : "")%>">Stage <%=i + 1%>: <%=stages[i]%> <% } diff --git a/webgoat-container/src/test/java/org/owasp/webgoat/lessons/AbstractLessonTest.java b/webgoat-container/src/test/java/org/owasp/webgoat/lessons/AbstractLessonTest.java new file mode 100644 index 000000000..82b74e34b --- /dev/null +++ b/webgoat-container/src/test/java/org/owasp/webgoat/lessons/AbstractLessonTest.java @@ -0,0 +1,62 @@ +package org.owasp.webgoat.lessons; + +import org.apache.ecs.Element; +import org.apache.ecs.ElementContainer; +import org.hamcrest.CoreMatchers; +import org.junit.Test; +import org.owasp.webgoat.session.WebSession; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertThat; + +public class AbstractLessonTest { + + private AbstractLesson lesson = new AbstractLesson() { + protected Element createContent(WebSession s) { + return new ElementContainer(); + } + public Category getCategory() { + return Category.XSS; + } + protected Integer getDefaultRanking() { + return new Integer(5); + } + protected Category getDefaultCategory() { + return Category.INTRODUCTION; + } + protected boolean getDefaultHidden() { + return false; + } + protected List getHints(WebSession s) { + return Arrays.asList(); + } + public String getInstructions(WebSession s) { + return "Instructions"; + } + public String getTitle() { + return "title"; + } + public String getCurrentAction(WebSession s) { + return "an action"; + } + public void restartLesson() { + } + public void setCurrentAction(WebSession s, String lessonScreen) { + } + }; + + @Test + public void testLinks() { + String mvcLink = lesson.getLink(); + assertThat(mvcLink, CoreMatchers.startsWith("#attack/")); + assertThat(mvcLink, CoreMatchers.endsWith("/900")); + + String srvLink = lesson.getServletLink(); + assertThat(srvLink, CoreMatchers.startsWith("attack?Screen=")); + assertThat(srvLink, CoreMatchers.endsWith("&menu=900")); + } +} + +