Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Survey to Rate Challenges After Correct Flag Submission #224 #532

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// GO AFTER THE REQUIRES BELOW.
//
//= require jquery
//= require jquery_ujs
//= require_tree .
//= require jquery.nested-fields
// Loads all Bootstrap javascripts
Expand Down
56 changes: 56 additions & 0 deletions app/assets/stylesheets/surveys.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Place all the styles related to the Surveys controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: https://sass-lang.com/

.star-cb-group {
/* remove inline-block whitespace */
font-size: 0;
/* flip the order so we can use the + and ~ combinators */
unicode-bidi: bidi-override;
direction: rtl;
/* the hidden clearer */
}
.star-cb-group * {
font-size: 1rem;
}
.star-cb-group > input {
display: none;
}
.star-cb-group > input + label {
/* only enough room for the star */
display: inline-block;
overflow: hidden;
text-indent: 9999px;
width: 1em;
white-space: nowrap;
cursor: pointer;
}
.star-cb-group > input + label:before {
display: inline-block;
text-indent: -9999px;
content: "☆";
color: #888;
}
.star-cb-group > input:checked ~ label:before, .star-cb-group > input + label:hover ~ label:before, .star-cb-group > input + label:hover:before {
content: "★";
color: rgb(238, 206, 24);
text-shadow: 0 0 1px #333;
}
.star-cb-group > .star-cb-clear + label {
text-indent: -9999px;
width: .5em;
margin-left: -.5em;
}
.star-cb-group > .star-cb-clear + label:before {
width: .5em;
}
.star-cb-group:hover > input + label:before {
content: "☆";
color: #888;
text-shadow: none;
}
.star-cb-group:hover > input + label:hover ~ label:before, .star-cb-group:hover > input + label:hover:before {
content: "★";
color: rgb(238, 206, 24);
text-shadow: 0 0 1px #333;
}
7 changes: 5 additions & 2 deletions app/controllers/challenges_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ def update
@solved_challenge = @flag_found.save_solved_challenge(current_user)
@solved_video_url = @flag_found.video_url
flash.now[:notice] = I18n.t('flag.accepted')
@survey = Survey.new
@survey.submitted_flag_id = @submitted_flag.id
else
flash.now[:alert] = wrong_flag_messages.sample
end
@solvable = @challenge.can_be_solved_by(current_user.team)

render :show
end
Expand All @@ -51,7 +52,9 @@ def find_and_log_flag
flag = params[:challenge]&.[](:submitted_flag) # Safe navigation on a hash
return if flag.nil?

SubmittedFlag.create(user: current_user, challenge: @challenge, text: flag) unless current_user.admin?
unless current_user.admin?
@submitted_flag = SubmittedFlag.create(user: current_user, challenge: @challenge, text: flag)
end
@flag_found = @challenge.find_flag(flag)
end

Expand Down
15 changes: 15 additions & 0 deletions app/controllers/surveys_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

class SurveysController < ApplicationController
def create
@survey = Survey.new(survey_params)

redirect_to '/game', notice: I18n.t('surveys.submitted') if @survey.save
end

private

def survey_params
params.require(:survey).permit(:difficulty, :realism, :interest, :comment, :submitted_flag_id)
end
end
1 change: 1 addition & 0 deletions app/models/submitted_flag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
class SubmittedFlag < ApplicationRecord
belongs_to :user
belongs_to :challenge
has_one :survey, dependent: :destroy

validates :text, presence: true

Expand Down
5 changes: 5 additions & 0 deletions app/models/survey.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class Survey < ApplicationRecord
belongs_to :submitted_flag, optional: true
end
9 changes: 7 additions & 2 deletions app/views/challenges/show.html.haml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-# For PentestGames, @challenge here can actually be a flag object since Pentest challenges belong to teams + challenges and are linked by a flag object.
-#
-#
- content_for :admin_menu do
%li= link_to t('challenges.admin_edit_challenge', challengename: @challenge.name), admin_edit_url(@challenge)

Expand Down Expand Up @@ -28,7 +28,7 @@
= sanitize(Kramdown::Document.new(@challenge.sponsor_description).to_html)

- if @solvable
= form_for :challenge, url: submit_url(@defense_team, @challenge), method: "put", html: { class: "well", style: "margin-bottom:40px;" } do |f|
= form_for :challenge, url: submit_url(@defense_team, @challenge), method: "put", html: { class: "well form-inline", style: "margin-bottom:40px;" } do |f|
.control-group
%p
= t('challenges.submit_flag')
Expand All @@ -38,6 +38,11 @@
.control-group
= invisible_recaptcha_tags text: 'Submit', :class => "btn btn-primary"

- if @flag_found
%hr/
= render partial: "surveys/form"
%hr/

- if @solved_by.length > 0
%table.table.table-bordered.table-striped
%thead
Expand Down
26 changes: 26 additions & 0 deletions app/views/surveys/_form.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
= form_for @survey, html: { class: "well form-inline", style: "margin-bottom:40px;" } do |f|
- if @survey.errors.any?
#error_explanation
%h2= "#{pluralize(@survey.errors.count, "error")} prohibited this survey from being saved:"
%ul
- @survey.errors.full_messages.each do |message|
%li= message

%h3= t('surveys.new.header')
.rating
.field
= f.label :difficulty
= render partial: "surveys/star_rating", locals: { category: :difficulty, f: f }
.field
= f.label :realism
= render partial: "surveys/star_rating", locals: { category: :realism, f: f }
.field
= f.label :interest
= render partial: "surveys/star_rating", locals: { category: :interest, f: f }
.field
= f.label :comment
= f.text_area :comment
.field
= f.hidden_field :submitted_flag_id
.field
= f.submit t('surveys.new.submit_btn'), class: "btn btn-primary"
5 changes: 5 additions & 0 deletions app/views/surveys/_star_rating.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.rating
%span.star-cb-group
- [5, 4, 3, 2, 1].each do |rating|
= f.radio_button category, rating, { :name => "survey[#{category}]", :id => "#{category}_#{rating}", :value => rating }
%label{:for => "#{category}_#{rating}"}
2 changes: 1 addition & 1 deletion config/initializers/rails_admin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
config.actions do
# root actions
dashboard # mandatory
# collection actions
# collection actions
index # mandatory
new do
except ['Challenge', 'FeedItem', 'SolvedChallenge', 'Flag'] # Block users from adding items from their parent classes instead of their own classes
Expand Down
7 changes: 6 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -433,4 +433,9 @@ en:
Your account has been locked due to an excessive amount of unsuccessful
sign in attempts.
unlock_line_2: 'Click the link below to unlock your account:'
unlock_account_link: Unlock my account
unlock_account_link: 'Unlock my account'
surveys:
submitted: 'Survey Submitted!'
new:
header: 'Review Solved Challenge'
submit_btn: 'Submit Survey'
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@
end
end

resource :surveys, only: %i[create] do
post :create
end

get '/game/summary' => 'games#summary'
get '/game/teams' => 'games#teams'

Expand Down
13 changes: 13 additions & 0 deletions db/migrate/20200710145833_create_surveys.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class CreateSurveys < ActiveRecord::Migration[6.0]
def change
create_table :surveys do |t|
t.integer :difficulty, default: 0, null: false
t.integer :realism, default: 0, null: false
t.integer :interest, default: 0, null: false
t.text :comment, default: "", null: true
t.integer :submitted_flag_id, null: false
t.timestamps
end
add_foreign_key "surveys", "submitted_flags"
end
end
13 changes: 12 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2020_06_23_173255) do
ActiveRecord::Schema.define(version: 2020_07_10_145833) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -178,6 +178,16 @@
t.index ["flag_id"], name: "index_submitted_flags_on_flag_id"
end

create_table "surveys", force: :cascade do |t|
t.integer "difficulty", default: 0, null: false
t.integer "realism", default: 0, null: false
t.integer "interest", default: 0, null: false
t.text "comment", default: ""
t.integer "submitted_flag_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end

create_table "teams", id: :serial, force: :cascade do |t|
t.string "team_name"
t.datetime "created_at"
Expand Down Expand Up @@ -281,5 +291,6 @@
add_foreign_key "challenges", "games"
add_foreign_key "flags", "teams"
add_foreign_key "submitted_flags", "flags"
add_foreign_key "surveys", "submitted_flags"
add_foreign_key "teams", "divisions"
end
22 changes: 22 additions & 0 deletions test/controllers/surveys_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require 'test_helper'

class SurveysControllerTest < ActionController::TestCase

def setup
create(:active_game)
@team1 = create(:team)
@team2 = create(:team)
@standard_challenge = create(:standard_challenge, flag_count: 3)
@pentest_challenge = create(:pentest_challenge_with_flags)
end

test 'create new survey' do
team_user = create(:user_with_team)
standard_challenge = create(:standard_challenge)
sign_in team_user
submitted_flag = create(:submitted_flag, user: team_user, challenge: standard_challenge)
post :create, params: { survey: { difficulty: 5, realism: 5, interest: 5, comment: "MyText", submitted_flag_id: submitted_flag.id } }
assert_redirected_to game_path
assert_match I18n.t('surveys.submitted'), flash[:notice]
end
camdenmoors marked this conversation as resolved.
Show resolved Hide resolved
end
9 changes: 9 additions & 0 deletions test/factories/surveys.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FactoryBot.define do
factory :survey do
difficulty { 1 }
realism { 1 }
interest { 1 }
comment { "MyText" }
submitted_flag_id { 1 }
camdenmoors marked this conversation as resolved.
Show resolved Hide resolved
end
end
93 changes: 93 additions & 0 deletions test/integration/submitted_flags_survey_display_modes_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
require 'test_helper'

class SubmittedFlagsSurveyDisplayModesTest < ActionDispatch::IntegrationTest
include TeamsHelper
include Devise::Test::IntegrationHelpers

def setup
create(:active_game)
@team1 = create(:team)
@team2 = create(:team)
@standard_challenge = create(:standard_challenge, flag_count: 3)
@pentest_challenge = create(:pentest_challenge_with_flags)
end

test 'solved challenge survey shows for pentest challenge when submitted flag is accepted' do
create(:pentest_solved_challenge, team: @team1, challenge: @pentest_challenge, flag: @team2.defense_flags.first)
sign_in @team1.team_captain
get game_team_challenge_path(@team2, @pentest_challenge)
assert_response :success
if @flag_found
assert_select 'form.well.form-inline' do
assert_select 'h3', I18n.t('surveys.new.header')
assert_select 'div.field', {:count => 3} do
assert_select 'label'
assert_select 'span.star-cb-group'
end
assert_select 'div.field' do
assert_select 'label'
assert_select 'textarea#survey_comment'
end
assert_select 'div.field' do
assert_select 'input#survey_submitted_flag_id'
end
assert_select 'div.field' do
assert_select 'input.btn.btn-primary[type=submit]'
end
end
end
end

test 'solved challenge survey shows for standard challenge when submitted flag is accepted' do
create(:standard_solved_challenge, challenge: @standard_challenge, team: @team1)
sign_in @team1.team_captain
get game_challenge_path(@standard_challenge)
assert_response :success
if @flag_found
assert_select 'form.well.form-inline' do
assert_select 'h3', I18n.t('surveys.new.header')
assert_select 'div.field', {:count => 3} do
assert_select 'label'
assert_select 'span.star-cb-group'
end
assert_select 'div.field' do
assert_select 'label'
assert_select 'textarea#survey_comment'
end
assert_select 'div.field' do
assert_select 'input#survey_submitted_flag_id'
end
assert_select 'div.field' do
assert_select 'input.btn.btn-primary[type=submit]'
end
end
end
end

test 'solved challenge survey shows for share challenge when submitted flag is accepted' do
share_chal = create(:share_challenge)
create(:standard_solved_challenge, challenge: share_chal, team: @team1)
sign_in @team1.team_captain
get game_challenge_path(share_chal)
assert_response :success
if @flag_found
assert_select 'form.well.form-inline' do
assert_select 'h3', I18n.t('surveys.new.header')
assert_select 'div.field', {:count => 3} do
assert_select 'label'
assert_select 'span.star-cb-group'
end
assert_select 'div.field' do
assert_select 'label'
assert_select 'textarea#survey_comment'
end
assert_select 'div.field' do
assert_select 'input#survey_submitted_flag_id'
end
assert_select 'div.field' do
assert_select 'input.btn.btn-primary[type=submit]'
end
end
end
end
end
7 changes: 7 additions & 0 deletions test/models/survey_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require 'test_helper'

class SurveyTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
Loading