Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,11 @@ jobs:
exit 0

- name: Run tests
timeout-minutes: 4
timeout-minutes: 12
run: |
export MOZ_HEADLESS=1
export DOJO_SSH_PORT=2222
timeout 3m pytest -vrpP --order-dependencies --durations=0 ./test || (docker exec dojo-test dojo compose logs && false)
timeout 10m pytest -vrpP --order-dependencies --durations=0 ./test || (docker exec dojo-test dojo compose logs && false)

- name: Pack docker data cache
if: github.event_name == 'schedule'
Expand Down
7 changes: 7 additions & 0 deletions dojo_theme/static/js/dojo/challenges.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ function unlockChallenge(challenge_button) {

function startChallenge(event) {
event.preventDefault();

if (!localStorage.getItem('groundRulesAccepted')) {
window.pendingChallengeEvent = event;
$('#groundRulesModal').modal('show');
return;
}

const item = $(event.currentTarget).closest(".accordion-item");
const module = item.find("#module").val()
const challenge = item.find("#challenge").val()
Expand Down
1 change: 1 addition & 0 deletions dojo_theme/static/js/dojo/groundrules.dev.js
56 changes: 56 additions & 0 deletions dojo_theme/static/js/dojo/groundrules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
$(document).ready(function() {
const requiredText = "I have read the ground rules and commit to not publish writeups on the internet.";

function normalizeText(text) {
return text.toLowerCase().replace(/[^a-z]/g, '');
}

const normalizedRequired = normalizeText(requiredText);

$('#groundRulesInput').on('input', function() {
const inputValue = $(this).val().trim();
const normalizedInput = normalizeText(inputValue);
const isValid = normalizedInput === normalizedRequired;

if (isValid) {
$(this).removeClass('is-invalid').addClass('is-valid');
$('#acceptGroundRules').prop('disabled', false);
} else {
$(this).removeClass('is-valid');
if (inputValue.length > 0) {
$(this).addClass('is-invalid');
}
$('#acceptGroundRules').prop('disabled', true);
}
});

$('#acceptGroundRules').click(function() {
const inputValue = $('#groundRulesInput').val().trim();
const normalizedInput = normalizeText(inputValue);
if (normalizedInput === normalizedRequired) {
localStorage.setItem('groundRulesAccepted', 'true');

$('#groundRulesModal').modal('hide');

$('#groundRulesInput').val('').removeClass('is-valid is-invalid');
$('#acceptGroundRules').prop('disabled', true);

if (window.pendingChallengeEvent && typeof startChallenge === 'function') {
const event = window.pendingChallengeEvent;
delete window.pendingChallengeEvent;
startChallenge(event);
}

if (window.pendingNavbarChallengeEvent && typeof DropdownStartChallenge === 'function') {
const event = window.pendingNavbarChallengeEvent;
delete window.pendingNavbarChallengeEvent;
setTimeout(() => DropdownStartChallenge(event), 100);
}
}
});

$('#groundRulesModal').on('hidden.bs.modal', function() {
$('#groundRulesInput').val('').removeClass('is-valid is-invalid');
$('#acceptGroundRules').prop('disabled', true);
});
});
1 change: 1 addition & 0 deletions dojo_theme/static/js/dojo/groundrules.min.js
7 changes: 7 additions & 0 deletions dojo_theme/static/js/dojo/navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ async function updateNavbarDropdown() {

function DropdownStartChallenge(event) {
event.preventDefault();

if (!localStorage.getItem('groundRulesAccepted')) {
window.pendingNavbarChallengeEvent = event;
$('#groundRulesModal').modal('show');
return;
}

const item = $(event.currentTarget).closest(".overflow-hidden");
const module = item.find("#module").val()
const challenge = item.find("#challenge").val()
Expand Down
35 changes: 35 additions & 0 deletions dojo_theme/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,40 @@
</div>
</footer>

<!-- Ground Rules Modal -->
<div id="groundRulesModal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="groundRulesModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content bg-dark text-white rounded-lg shadow-lg">
<div class="modal-header border-bottom border-secondary">
<h5 class="modal-title brand-mono-bold" id="groundRulesModalLabel">pwn.college Ground Rules</h5>
</div>
<div class="modal-body p-4">
<p class="lead">Please read and acknowledge the ground rules before starting challenges:</p>

<div class="alert alert-warning mt-3 mb-4">
<h6 class="alert-heading brand-mono-bold">Important Notice</h6>
<p class="mb-0">The challenges created for pwn.college are, first and foremost, educational material, and are used to grade students at universities around the world. Because of this, we would appreciate that you <strong>do not post writeups, walkthrough videos, and livestreams of challenge solutions</strong> to the internet.</p>
</div>

<p>Obviously, we can't enforce this, but we worked hard to make all of this public, and we would appreciate your help in keeping pwn.college a viable educational platform.</p>

<div class="form-group mt-4">
<label for="groundRulesInput" class="font-weight-bold">To continue, please type the following exactly:</label>
<p class="text-info">I have read the ground rules and commit to not publish writeups on the internet.</p>
<input type="text" class="form-control bg-dark text-white border-secondary" id="groundRulesInput" placeholder="Type the confirmation text here..." autocomplete="off">
<div class="invalid-feedback">
Please type the exact confirmation text.
</div>
</div>
</div>
<div class="modal-footer border-top border-secondary">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="acceptGroundRules" disabled>Accept and Continue</button>
</div>
</div>
</div>
</div>

<script defer src="{{ url_for('views.themes', path='js/vendor.bundle.js') }}"></script>
<script defer src="{{ url_for('views.themes', path='js/core.js') }}"></script>
<script defer src="{{ url_for('views.themes', path='js/helpers.js') }}"></script>
Expand All @@ -94,6 +128,7 @@
<script defer src="{{ url_for('views.themes', path='js/dojo/util.js') }}"></script>
<script defer src="{{ url_for('views.themes', path='js/dojo/navbar.js') }}"></script>
<script defer src="{{ url_for('views.themes', path='js/dojo/popup.js') }}"></script>
<script defer src="{{ url_for('views.themes', path='js/dojo/groundrules.js') }}"></script>

{% block scripts %}
{% endblock %}
Expand Down
78 changes: 78 additions & 0 deletions test/test_welcome.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ def random_user_browser(random_user):
browser.find_element("id", "name").send_keys(random_id)
browser.find_element("id", "password").send_keys(random_id)
browser.find_element("id", "_submit").click()

browser.execute_script("localStorage.setItem('groundRulesAccepted', 'true');")

return random_id, random_session, browser


Expand Down Expand Up @@ -150,3 +153,78 @@ def test_welcome_practice(random_user_browser, welcome_dojo):
flag = workspace_run("tail -n1 /tmp/out", user=random_id).stdout.split()[-1]
challenge_submit(browser, idx, flag)
browser.close()


def test_ground_rules_modal(random_user, welcome_dojo):
"""Test that ground rules modal appears on first challenge start but not on second"""
random_id, random_session = random_user

options = FirefoxOptions()
options.add_argument("--headless")
browser = Firefox(options=options)

browser.get(f"{DOJO_URL}/login")
browser.find_element("id", "name").send_keys(random_id)
browser.find_element("id", "password").send_keys(random_id)
browser.find_element("id", "_submit").click()

browser.get(f"{DOJO_URL}/welcome/welcome")
time.sleep(2)
browser.execute_script("localStorage.removeItem('groundRulesAccepted');")

idx = challenge_idx(browser, "The Flag File")
challenge_expand(browser, idx)
body = browser.find_element("id", f"challenges-body-{idx}")
body.find_element("id", "challenge-start").click()

time.sleep(2)
modal_visible = False
challenge_started = False

try:
modal = browser.find_element("id", "groundRulesModal")
modal_visible = "show" in modal.get_attribute("class")
except:
pass

try:
message = body.find_element("id", "result-message").text
challenge_started = "started" in message.lower()
except:
pass

assert modal_visible or not challenge_started, "Ground rules modal should appear on first challenge start"

if modal_visible:
browser.execute_script("""
localStorage.setItem('groundRulesAccepted', 'true');
$('#groundRulesModal').modal('hide');
if (window.pendingChallengeEvent) {
window.startChallenge(window.pendingChallengeEvent);
}
""")

wait = WebDriverWait(browser, 10)
wait.until(lambda d: "started" in body.find_element("id", "result-message").text.lower())
else:
browser.execute_script("localStorage.setItem('groundRulesAccepted', 'true');")

browser.get(f"{DOJO_URL}/welcome/welcome")
time.sleep(1)
idx2 = challenge_idx(browser, "Challenge Programs")
challenge_expand(browser, idx2)
body2 = browser.find_element("id", f"challenges-body-{idx2}")
body2.find_element("id", "challenge-start").click()

time.sleep(2)

try:
modal = browser.find_element("id", "groundRulesModal")
assert "show" not in modal.get_attribute("class"), "Ground rules modal should NOT be displayed on second challenge start"
except:
pass

wait = WebDriverWait(browser, 10)
wait.until(lambda d: "started" in body2.find_element("id", "result-message").text.lower())

browser.close()