Merge pull request #228 from span/developer-controls
Developer controls
This commit is contained in:
commit
a8f8d4b4fa
@ -29,18 +29,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.owasp.webgoat.service;
|
package org.owasp.webgoat.service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.owasp.webgoat.session.LabelDebugger;
|
import org.owasp.webgoat.session.LabelDebugger;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>PluginReloadService class.</p>
|
* <p>LabelDebugService class.</p>
|
||||||
*
|
*
|
||||||
* @author nbaars
|
* @author nbaars
|
||||||
* @version $Id: $Id
|
* @version $Id: $Id
|
||||||
@ -48,21 +53,52 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||||||
@Controller
|
@Controller
|
||||||
public class LabelDebugService extends BaseService {
|
public class LabelDebugService extends BaseService {
|
||||||
|
|
||||||
|
private static final String URL_DEBUG_LABELS_MVC = "/debug/labels.mvc";
|
||||||
|
private static final String KEY_ENABLED = "enabled";
|
||||||
|
private static final String KEY_SUCCESS = "success";
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(LabelDebugService.class);
|
private static final Logger logger = LoggerFactory.getLogger(LabelDebugService.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private LabelDebugger labelDebugger;
|
private LabelDebugger labelDebugger;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reload all the plugins
|
* Checks if debugging of labels is enabled or disabled
|
||||||
*
|
*
|
||||||
* @return a {@link org.springframework.http.ResponseEntity} object.
|
* @return a {@link org.springframework.http.ResponseEntity} object.
|
||||||
*/
|
*/
|
||||||
@RequestMapping(value = "/debug/labels.mvc")
|
@RequestMapping(value = URL_DEBUG_LABELS_MVC, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public @ResponseBody
|
public @ResponseBody
|
||||||
//todo parse params to add enable / disable
|
ResponseEntity<Map<String, Object>> checkDebuggingStatus() {
|
||||||
ResponseEntity<String> reloadPlugins() {
|
logger.debug("Checking label debugging, it is " + labelDebugger.isEnabled()); // FIXME parameterize
|
||||||
labelDebugger.enable();
|
Map<String, Object> result = createResponse(labelDebugger.isEnabled());
|
||||||
return new ResponseEntity("Label debugger enabled refresh the WebGoat page!",HttpStatus.OK);
|
return new ResponseEntity<Map<String, Object>>(result, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the enabled flag on the label debugger to the given parameter
|
||||||
|
*
|
||||||
|
* @return a {@link org.springframework.http.ResponseEntity} object.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@RequestMapping(value = URL_DEBUG_LABELS_MVC, produces = MediaType.APPLICATION_JSON_VALUE, params = KEY_ENABLED)
|
||||||
|
public @ResponseBody
|
||||||
|
ResponseEntity<Map<String, Object>> setDebuggingStatus(@RequestParam("enabled") Boolean enabled) throws Exception {
|
||||||
|
logger.debug("Setting label debugging to " + labelDebugger.isEnabled()); // FIXME parameterize
|
||||||
|
Map<String, Object> result = createResponse(enabled);
|
||||||
|
labelDebugger.setEnabled(enabled);
|
||||||
|
return new ResponseEntity<Map<String, Object>>(result, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param enabled
|
||||||
|
* @return a {@link java.util.Map} object.
|
||||||
|
*/
|
||||||
|
private Map<String, Object> createResponse(Boolean enabled) {
|
||||||
|
Map<String, Object> result = new HashMap<String, Object>();
|
||||||
|
result.put(KEY_SUCCESS, Boolean.TRUE);
|
||||||
|
result.put(KEY_ENABLED, enabled);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,19 +29,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.owasp.webgoat.service;
|
package org.owasp.webgoat.service;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
import org.owasp.webgoat.plugins.PluginsLoader;
|
import org.owasp.webgoat.plugins.PluginsLoader;
|
||||||
import org.owasp.webgoat.session.WebSession;
|
import org.owasp.webgoat.session.WebSession;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>PluginReloadService class.</p>
|
* <p>PluginReloadService class.</p>
|
||||||
*
|
*
|
||||||
@ -59,16 +63,20 @@ public class PluginReloadService extends BaseService {
|
|||||||
* @param session a {@link javax.servlet.http.HttpSession} object.
|
* @param session a {@link javax.servlet.http.HttpSession} object.
|
||||||
* @return a {@link org.springframework.http.ResponseEntity} object.
|
* @return a {@link org.springframework.http.ResponseEntity} object.
|
||||||
*/
|
*/
|
||||||
@RequestMapping(value = "/reloadplugins.mvc")
|
@RequestMapping(value = "/reloadplugins.mvc", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public @ResponseBody
|
public @ResponseBody
|
||||||
ResponseEntity<String> reloadPlugins(HttpSession session) {
|
ResponseEntity<Map<String, Object>> reloadPlugins(HttpSession session) {
|
||||||
WebSession webSession = (WebSession) session.getAttribute(WebSession.SESSION);
|
WebSession webSession = (WebSession) session.getAttribute(WebSession.SESSION);
|
||||||
|
|
||||||
logger.debug("Loading plugins into cache");
|
logger.debug("Loading plugins into cache");
|
||||||
String pluginPath = session.getServletContext().getRealPath("plugin_lessons");
|
String pluginPath = session.getServletContext().getRealPath("plugin_lessons");
|
||||||
String targetPath = session.getServletContext().getRealPath("plugin_extracted");
|
String targetPath = session.getServletContext().getRealPath("plugin_extracted");
|
||||||
new PluginsLoader(Paths.get(pluginPath), Paths.get(targetPath)).copyJars();
|
new PluginsLoader(Paths.get(pluginPath), Paths.get(targetPath)).copyJars();
|
||||||
|
|
||||||
webSession.getCourse().loadLessonFromPlugin(session.getServletContext());
|
webSession.getCourse().loadLessonFromPlugin(session.getServletContext());
|
||||||
return new ResponseEntity("Plugins reload refresh the WebGoat page!",HttpStatus.OK);
|
|
||||||
|
Map<String, Object> result = new HashMap<String, Object>();
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("message", "Plugins reloaded");
|
||||||
|
return new ResponseEntity<Map<String, Object>>(result, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
public class LabelDebugger implements Serializable {
|
public class LabelDebugger implements Serializable {
|
||||||
|
|
||||||
private boolean isEnabled = false;
|
private boolean enabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>isEnabled.</p>
|
* <p>isEnabled.</p>
|
||||||
@ -18,14 +18,31 @@ public class LabelDebugger implements Serializable {
|
|||||||
* @return a boolean.
|
* @return a boolean.
|
||||||
*/
|
*/
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return isEnabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>enable.</p>
|
* <p>Enables label debugging</p>
|
||||||
*/
|
*/
|
||||||
public void enable() {
|
public void enable() {
|
||||||
this.isEnabled = true;
|
this.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Disables label debugging</p>
|
||||||
|
*/
|
||||||
|
public void disable() {
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets the status to enabled</p>
|
||||||
|
* @param enabled
|
||||||
|
* @throws Exception if enabled is null
|
||||||
|
*/
|
||||||
|
public void setEnabled(Boolean enabled) throws Exception {
|
||||||
|
if(enabled == null) throw new Exception("Cannot set enabled to null");
|
||||||
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -72,9 +72,9 @@
|
|||||||
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#">User: ${user}</a></li>
|
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#">User: ${user}</a></li>
|
||||||
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#">Role: ${role}</a></li>
|
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#">Role: ${role}</a></li>
|
||||||
<li role="presentation" class="divider"></li>
|
<li role="presentation" class="divider"></li>
|
||||||
|
<li role="presentation"><a role="menuitem" tabindex="-1" href="#developer-controls">Show developer controls</a></li>
|
||||||
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#">${version}</a></li>
|
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#">${version}</a></li>
|
||||||
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#">Build: ${build}</a></li>
|
<li role="presentation" class="disabled"><a role="menuitem" tabindex="-1" href="#">Build: ${build}</a></li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" id="about-button" class="btn btn-default right_nav_button" title="About WebGoat" data-toggle="modal" data-target="#about-modal">
|
<button type="button" id="about-button" class="btn btn-default right_nav_button" title="About WebGoat" data-toggle="modal" data-target="#about-modal">
|
||||||
@ -143,6 +143,15 @@
|
|||||||
<h4>Params</h4>
|
<h4>Params</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="developer-control-container">
|
||||||
|
<div align="left">
|
||||||
|
<h3>Developer controls</h3>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div id="developer-controls">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -765,6 +765,27 @@ cookie-container {
|
|||||||
padding-left:3px;
|
padding-left:3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.developer-controls-table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.developer-controls-table td {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.developer-controls-table a {
|
||||||
|
color: #e84c3d
|
||||||
|
}
|
||||||
|
|
||||||
|
#developer-control-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu-container a,
|
||||||
|
.developer-controls-table a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
MENU / Sidebar
|
MENU / Sidebar
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
@ -123,6 +123,7 @@ define(['jquery',
|
|||||||
this.sourceView = new SourceView();
|
this.sourceView = new SourceView();
|
||||||
this.lessonHintView = new HintView();
|
this.lessonHintView = new HintView();
|
||||||
this.cookieView = new CookieView();
|
this.cookieView = new CookieView();
|
||||||
|
|
||||||
//TODO: instantiate model with values (not sure why was not working before)
|
//TODO: instantiate model with values (not sure why was not working before)
|
||||||
var paramModel = new ParamModel({});
|
var paramModel = new ParamModel({});
|
||||||
paramModel.set('scrParam',this.lessonContent.get('scrParam'));
|
paramModel.set('scrParam',this.lessonContent.get('scrParam'));
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
define([
|
||||||
|
'backbone'],
|
||||||
|
function(
|
||||||
|
Backbone) {
|
||||||
|
return Backbone.Model.extend({
|
||||||
|
id: 'label-status',
|
||||||
|
url: 'service/debug/labels.mvc',
|
||||||
|
|
||||||
|
label: '',
|
||||||
|
labels: {
|
||||||
|
enable: 'Enable label debugging',
|
||||||
|
disable: 'Disable label debugging'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
|
||||||
|
fetch: function(options) {
|
||||||
|
options || (options = {});
|
||||||
|
var data = (options.data || {});
|
||||||
|
if(this.enabled != undefined) {
|
||||||
|
options.data = { enabled: !this.enabled };
|
||||||
|
}
|
||||||
|
return Backbone.Collection.prototype.fetch.call(this, options);
|
||||||
|
},
|
||||||
|
|
||||||
|
load: function () {
|
||||||
|
this.fetch().then(this.labelStatusLoaded.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
labelStatusLoaded: function(data) {
|
||||||
|
this.enabled = data.enabled;
|
||||||
|
this.label = this.enabled ? this.labels['disable'] : this.labels['enable'];
|
||||||
|
this.trigger('plugins:loaded', this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,19 @@
|
|||||||
|
define([
|
||||||
|
'backbone'],
|
||||||
|
function(
|
||||||
|
Backbone) {
|
||||||
|
return Backbone.Model.extend({
|
||||||
|
url: 'service/reloadplugins.mvc',
|
||||||
|
id: 'reload-plugins',
|
||||||
|
label: 'Reload plugins',
|
||||||
|
|
||||||
|
load: function () {
|
||||||
|
this.fetch().then(this.pluginsLoaded.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
pluginsLoaded: function(data) {
|
||||||
|
this.trigger('plugins:loaded', this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,77 @@
|
|||||||
|
define(['jquery',
|
||||||
|
'underscore',
|
||||||
|
'backbone',
|
||||||
|
'goatApp/model/PluginReloadModel',
|
||||||
|
'goatApp/model/LabelDebugModel'],
|
||||||
|
function(
|
||||||
|
$,
|
||||||
|
_,
|
||||||
|
Backbone,
|
||||||
|
PluginReloadModel,
|
||||||
|
LabelDebugModel) {
|
||||||
|
return Backbone.View.extend({
|
||||||
|
el: '#developer-controls',
|
||||||
|
|
||||||
|
onControlClick: function(model) {
|
||||||
|
$('#' + model.id).find('td').text('Loading...');
|
||||||
|
model.load();
|
||||||
|
},
|
||||||
|
|
||||||
|
onPluginsLoaded: function(model) {
|
||||||
|
window.location.href = 'welcome.mvc';
|
||||||
|
},
|
||||||
|
|
||||||
|
onLabelsLoaded: function(model) {
|
||||||
|
this.models[1] = model;
|
||||||
|
this.render();
|
||||||
|
Backbone.history.loadUrl(Backbone.history.getFragment());
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function(options) {
|
||||||
|
this.addMenuListener();
|
||||||
|
this.models = [new PluginReloadModel(), new LabelDebugModel()];
|
||||||
|
this.listenTo(this.models[0], 'plugins:loaded', this.onPluginsLoaded);
|
||||||
|
this.listenTo(this.models[1], 'plugins:loaded', this.onLabelsLoaded);
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
addMenuListener: function() {
|
||||||
|
var showHandler = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('#developer-control-container').show();
|
||||||
|
$(this).text('Hide developer controls').off().on('click', hideHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
var hideHandler = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('#developer-control-container').hide();
|
||||||
|
$(this).text('Show developer controls').off().on('click', showHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
$('a[href="#developer-controls"]').click(showHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
this.$el.html('');
|
||||||
|
var table = $('<table>',{'class':'developer-controls-table table-nonfluid'});
|
||||||
|
var self = this;
|
||||||
|
_.each(this.models, function(model) {
|
||||||
|
var newRow = $('<tr>', { id: model.id });
|
||||||
|
var headerCell = $('<th>')
|
||||||
|
var statusCell = $('<td>')
|
||||||
|
|
||||||
|
var link = $('<a>', {
|
||||||
|
'text': model.label,
|
||||||
|
'title': model.label
|
||||||
|
});
|
||||||
|
link.click(_.bind(self.onControlClick, self, model));
|
||||||
|
|
||||||
|
newRow.append(headerCell.append(link));
|
||||||
|
newRow.append(statusCell);
|
||||||
|
table.append(newRow);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$el.append(table);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -4,17 +4,20 @@ define(['jquery',
|
|||||||
'goatApp/controller/LessonController',
|
'goatApp/controller/LessonController',
|
||||||
'goatApp/controller/MenuController',
|
'goatApp/controller/MenuController',
|
||||||
'goatApp/view/LessonContentView',
|
'goatApp/view/LessonContentView',
|
||||||
'goatApp/view/MenuView'
|
'goatApp/view/MenuView',
|
||||||
|
'goatApp/view/DeveloperControlsView'
|
||||||
], function ($,
|
], function ($,
|
||||||
_,
|
_,
|
||||||
Backbone,
|
Backbone,
|
||||||
LessonController,
|
LessonController,
|
||||||
MenuController,
|
MenuController,
|
||||||
LessonContentView,
|
LessonContentView,
|
||||||
MenuView) {
|
MenuView,
|
||||||
|
DeveloperControlsView) {
|
||||||
|
|
||||||
var lessonView = new LessonContentView();
|
var lessonView = new LessonContentView();
|
||||||
var menuView = new MenuView();
|
var menuView = new MenuView();
|
||||||
|
var developerControlsView = new DeveloperControlsView();
|
||||||
|
|
||||||
var GoatAppRouter = Backbone.Router.extend({
|
var GoatAppRouter = Backbone.Router.extend({
|
||||||
routes: {
|
routes: {
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package org.owasp.webgoat.session;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
|
public class LabelDebuggerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetEnabledTrue() throws Exception {
|
||||||
|
LabelDebugger ld = new LabelDebugger();
|
||||||
|
ld.setEnabled(true);
|
||||||
|
Assert.assertTrue(ld.isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetEnabledFalse() throws Exception {
|
||||||
|
LabelDebugger ld = new LabelDebugger();
|
||||||
|
ld.setEnabled(false);
|
||||||
|
Assert.assertFalse(ld.isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetEnabledNullThrowsException() {
|
||||||
|
LabelDebugger ld = new LabelDebugger();
|
||||||
|
try {
|
||||||
|
ld.setEnabled(null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// We want to end up here
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEnableIsTrue() {
|
||||||
|
LabelDebugger ld = new LabelDebugger();
|
||||||
|
ld.enable();
|
||||||
|
Assert.assertTrue(ld.isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisableIsFalse() {
|
||||||
|
LabelDebugger ld = new LabelDebugger();
|
||||||
|
ld.disable();
|
||||||
|
Assert.assertFalse(ld.isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user