├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── .travis.yml ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ └── rails.png │ ├── javascripts │ │ ├── application.js │ │ ├── bootstrap-2.0.3.min.js │ │ ├── categories.js.coffee │ │ ├── challenges.js.coffee │ │ ├── d3.v3.min.js │ │ ├── games.js.coffee │ │ ├── messages.js.coffee │ │ └── timeline.js.coffee │ └── stylesheets │ │ ├── application.css │ │ ├── bootstrap-2.0.3.min.css │ │ ├── categories.css.scss │ │ ├── challenges.css.scss │ │ ├── games.css.sass │ │ ├── messages.css.scss │ │ ├── scaffolds.css.scss │ │ └── timeline.css.sass ├── controllers │ ├── achievements_controller.rb │ ├── application_controller.rb │ ├── challenges_controller.rb │ ├── games_controller.rb │ ├── messages_controller.rb │ ├── sessions_controller.rb │ ├── timeline_controller.rb │ └── users_controller.rb ├── helpers │ ├── application_helper.rb │ ├── categories_helper.rb │ ├── challenges_helper.rb │ ├── games_helper.rb │ ├── messages_helper.rb │ ├── sessions_helper.rb │ └── timeline_helper.rb ├── mailers │ └── .gitkeep ├── models │ ├── .gitkeep │ ├── achievement.rb │ ├── admin.rb │ ├── category.rb │ ├── challenge.rb │ ├── challenge_state.rb │ ├── division.rb │ ├── feed_item.rb │ ├── flag.rb │ ├── game.rb │ ├── message.rb │ ├── player.rb │ ├── score_adjustment.rb │ ├── solved_challenge.rb │ ├── submitted_flag.rb │ └── user.rb └── views │ ├── achievements │ └── index.html.haml │ ├── challenges │ ├── _gameboard.html.haml │ ├── index.html.haml │ └── show.html.haml │ ├── devise │ ├── confirmations │ │ └── new.html.erb │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ ├── reset_password_instructions.html.erb │ │ └── unlock_instructions.html.erb │ ├── passwords │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── registrations │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── sessions │ │ └── new.html.haml │ ├── shared │ │ └── _links.erb │ └── unlocks │ │ └── new.html.erb │ ├── events │ └── _timeline.haml │ ├── games │ ├── _countdown.html.haml │ ├── show.html.haml │ ├── show.json.jbuilder │ └── summary.html.haml │ ├── kaminari │ ├── _first_page.html.haml │ ├── _gap.html.haml │ ├── _last_page.html.haml │ ├── _next_page.html.haml │ ├── _page.html.haml │ ├── _paginator.html.haml │ └── _prev_page.html.haml │ ├── layouts │ ├── _flash.html.haml │ └── application.html.haml │ ├── messages │ └── index.html.haml │ └── users │ ├── _divisions.html.haml │ ├── _list.html.haml │ ├── _map.html.haml │ ├── index.html.haml │ ├── public.html.haml │ └── show.html.haml ├── apt-packages ├── bin ├── bundle ├── rails ├── rake └── setup ├── config.ru ├── config ├── application.rb ├── boot.rb ├── brakeman.ignore ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── devise.rb │ ├── geocoder.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── rails_admin.rb │ ├── rails_admin.rb.example │ ├── session_store.rb │ ├── wrap_parameters.rb │ └── wrong_flag_messages.rb ├── locales │ ├── devise.en.yml │ └── scoreboard.en.yml ├── routes.rb └── secrets.yml ├── db ├── migrate │ ├── 20110816191149_devise_create_users.rb │ ├── 20110816191206_create_rails_admin_histories_table.rb │ ├── 20110816192037_create_games.rb │ ├── 20110817154811_create_challenges.rb │ ├── 20110817154849_create_categories.rb │ ├── 20110817173940_add_category_id_to_challenge.rb │ ├── 20110817174121_add_game_id_to_category.rb │ ├── 20110817175559_add_type_to_users.rb │ ├── 20110817191639_add_game_id_to_users.rb │ ├── 20120315193309_add_achievement_name_to_challenge.rb │ ├── 20120424134356_create_messages.rb │ ├── 20120424140054_add_title_to_messages.rb │ ├── 20120424180652_create_submitted_flags.rb │ ├── 20120426145021_add_messages_stamp_to_user.rb │ ├── 20120501154332_create_feed_items.rb │ ├── 20120501165852_add_challenge_id_to_feed_item.rb │ ├── 20120501171043_add_text_to_feed_item.rb │ ├── 20120508213250_add_point_value_to_feed_item.rb │ ├── 20120518181822_add_tags_to_users.rb │ ├── 20120522161744_add_reset_password_sent_at_to_users.rb │ ├── 20120626161246_create_keys.rb │ ├── 20120627164138_add_display_name_to_users.rb │ ├── 20120706171912_add_tos_to_game.rb │ ├── 20130507195030_add_city_to_players.rb │ ├── 20130515183428_add_irc_to_game.rb │ ├── 20130628164632_change_affiliation_to_string.rb │ ├── 20150812172020_remove_flag_from_challenge.rb │ ├── 20150812172928_create_flags.rb │ ├── 20150812232430_add_flag_to_feed_items.rb │ ├── 20150818183333_add_eligibility_to_users.rb │ ├── 20160112192523_add_disable_vpn_to_game.rb │ ├── 20160112203043_add_disable_flag_an_hour_to_game.rb │ ├── 20160408200244_create_divisions.rb │ ├── 20160408204720_rename_gameid_divisionid.rb │ ├── 20160415192739_create_challenge_states.rb │ ├── 20160415194524_add_division_ref_to_solved_challenges.rb │ ├── 20160422151153_rename_state_to_current_state_on_challenge.rb │ ├── 20160422181517_change_status_to_int.rb │ ├── 20160624144411_drop_keys_table.rb │ └── 20160628171437_remove_tos_from_game.rb ├── schema.rb └── seeds.rb ├── docker-compose.yml ├── lib ├── assets │ └── .gitkeep └── tasks │ ├── .gitkeep │ ├── cucumber.rake │ └── scoreboard.rake ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png ├── robots.txt └── states.json ├── script └── rails └── test ├── fixtures ├── categories.yml ├── challenge_states.yml ├── challenges.yml ├── divisions.yml ├── feed_items.yml ├── flags.yml ├── games.yml ├── messages.yml ├── submitted_flags.yml └── users.yml ├── functional ├── achievements_controller_test.rb ├── application_controller_test.rb ├── challenges_controller_test.rb ├── games_controller_test.rb ├── keys_controller_test.rb ├── messages_controller_test.rb └── users_controller_test.rb ├── helpers ├── application_helper_test.rb ├── categories_helper_test.rb ├── challenges_helper_test.rb ├── games_helper_test.rb ├── messages_helper_test.rb ├── sessions_helper_test.rb └── timeline_helper_test.rb ├── mailers └── .gitkeep ├── models ├── achievement_test.rb ├── admin_test.rb ├── category_test.rb ├── challenge_state_test.rb ├── challenge_test.rb ├── division_test.rb ├── feed_item_test.rb ├── flag_test.rb ├── game_test.rb ├── message_test.rb ├── player_test.rb ├── score_adjustment_test.rb ├── solved_challenge_test.rb └── user_test.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | *.sassc 3 | .sass-cache 4 | capybara-*.html 5 | .rspec 6 | /.bundle 7 | /vendor/bundle 8 | /log/* 9 | /tmp/* 10 | /db/*.sqlite3 11 | /public/system/* 12 | /coverage/ 13 | /spec/tmp/* 14 | **.orig 15 | rerun.txt 16 | pickle-email-*.html 17 | 18 | *.gem 19 | *.rbc 20 | .bundle 21 | .config 22 | coverage 23 | InstalledFiles 24 | lib/bundler/man 25 | pkg 26 | rdoc 27 | spec/reports 28 | test/tmp 29 | test/version_tmp 30 | tmp 31 | vendor/ruby 32 | 33 | # YARD artifacts 34 | .yardoc 35 | _yardoc 36 | doc/ 37 | 38 | .DS_Store 39 | Icon? 40 | 41 | # Thumbnails 42 | ._* 43 | 44 | # Files that might appear on external disk 45 | .Spotlight-V100 46 | .Trashes 47 | 48 | *.tmproj 49 | *.tmproject 50 | tmtags 51 | 52 | .env_vars 53 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - 'spec/**/*' 4 | - 'tmp/**/*' 5 | - 'db/**/*' 6 | - 'config/initializers/*' 7 | - 'log/**/*' 8 | - 'test/**/*' 9 | - 'vendor/**/*' 10 | Rails: 11 | Enabled: true 12 | 13 | Metrics/LineLength: 14 | Max: 120 15 | 16 | Documentation: 17 | Enabled: false -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: ruby 3 | # Use travis CI Docker Infrastructure 4 | sudo: false 5 | cache: bundler 6 | notifications: 7 | email: false 8 | slack: 9 | rooms: 10 | secure: BU22IQqojSJwkLBpLF4y2+BgEPUaZ7cdxRExsdWqDCoXGKSVBFY1I0/E4qm+abPZzSUqfvx8CC2hRqvdtymAmHbew6L8K+yDfdmBKwug3WRXWBqy65xWdQBQlvnYhmn7WfstbVYb2xHQj3YE+ZtNPI0UmJRUYdAJLI2qlzOjes8= 11 | on_success: change 12 | on_failure: always 13 | services: 14 | - postgresql 15 | before_script: 16 | - psql -c 'create database scoreboard_test;' -U postgres 17 | script: 18 | - bundle exec brakeman -qAzw1 19 | - bundle exec bundle-audit check --update 20 | - bundle exec rake test 21 | - bundle exec rubocop -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.3.1 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y --no-install-recommends \ 5 | postgresql-client \ 6 | && rm -rf /var/lib/apt/lists/* 7 | 8 | WORKDIR /usr/src/app 9 | COPY Gemfile* ./ 10 | RUN bundle install --without test 11 | COPY . . 12 | 13 | RUN rake assets:precompile 14 | 15 | ENV RAILS_ENV production 16 | 17 | ENV RAILS_SERVE_STATIC_FILES 1 18 | 19 | EXPOSE 3000 20 | CMD ["passenger", "start", "--port", "3000"] -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | source 'https://rubygems.org' 3 | 4 | ruby '2.3.1' 5 | 6 | gem 'rails', '~> 4.2.6' 7 | gem 'bluecloth' 8 | gem 'aws-sdk', '<2.0' 9 | gem 'haml' 10 | gem 'rails_admin' 11 | gem 'devise' 12 | gem 'jbuilder' 13 | gem 'kaminari' 14 | gem 'geocoder' 15 | gem 'jquery-rails' 16 | gem 'pg' 17 | 18 | group :development do 19 | gem 'bullet' 20 | end 21 | 22 | group :test do 23 | gem 'rspec-rails' 24 | gem 'factory_girl_rails' 25 | gem 'coveralls', require: false 26 | gem 'brakeman', require: false 27 | gem 'rubocop', require: false 28 | gem 'bundler-audit', require: false 29 | end 30 | 31 | group :assets do 32 | gem 'sass-rails' 33 | gem 'coffee-rails' 34 | gem 'therubyracer', platform: 'ruby' 35 | gem 'uglifier' 36 | end 37 | 38 | group :production do 39 | gem 'passenger' 40 | end 41 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actionmailer (4.2.7.1) 5 | actionpack (= 4.2.7.1) 6 | actionview (= 4.2.7.1) 7 | activejob (= 4.2.7.1) 8 | mail (~> 2.5, >= 2.5.4) 9 | rails-dom-testing (~> 1.0, >= 1.0.5) 10 | actionpack (4.2.7.1) 11 | actionview (= 4.2.7.1) 12 | activesupport (= 4.2.7.1) 13 | rack (~> 1.6) 14 | rack-test (~> 0.6.2) 15 | rails-dom-testing (~> 1.0, >= 1.0.5) 16 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 17 | actionview (4.2.7.1) 18 | activesupport (= 4.2.7.1) 19 | builder (~> 3.1) 20 | erubis (~> 2.7.0) 21 | rails-dom-testing (~> 1.0, >= 1.0.5) 22 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 23 | activejob (4.2.7.1) 24 | activesupport (= 4.2.7.1) 25 | globalid (>= 0.3.0) 26 | activemodel (4.2.7.1) 27 | activesupport (= 4.2.7.1) 28 | builder (~> 3.1) 29 | activerecord (4.2.7.1) 30 | activemodel (= 4.2.7.1) 31 | activesupport (= 4.2.7.1) 32 | arel (~> 6.0) 33 | activesupport (4.2.7.1) 34 | i18n (~> 0.7) 35 | json (~> 1.7, >= 1.7.7) 36 | minitest (~> 5.1) 37 | thread_safe (~> 0.3, >= 0.3.4) 38 | tzinfo (~> 1.1) 39 | arel (6.0.3) 40 | ast (2.3.0) 41 | aws-sdk (1.66.0) 42 | aws-sdk-v1 (= 1.66.0) 43 | aws-sdk-v1 (1.66.0) 44 | json (~> 1.4) 45 | nokogiri (>= 1.4.4) 46 | bcrypt (3.1.11) 47 | bluecloth (2.2.0) 48 | brakeman (3.3.4) 49 | builder (3.2.2) 50 | bullet (5.2.1) 51 | activesupport (>= 3.0.0) 52 | uniform_notifier (~> 1.10.0) 53 | bundler-audit (0.5.0) 54 | bundler (~> 1.2) 55 | thor (~> 0.18) 56 | coffee-rails (4.2.1) 57 | coffee-script (>= 2.2.0) 58 | railties (>= 4.0.0, < 5.2.x) 59 | coffee-script (2.4.1) 60 | coffee-script-source 61 | execjs 62 | coffee-script-source (1.10.0) 63 | concurrent-ruby (1.0.2) 64 | coveralls (0.8.15) 65 | json (>= 1.8, < 3) 66 | simplecov (~> 0.12.0) 67 | term-ansicolor (~> 1.3) 68 | thor (~> 0.19.1) 69 | tins (>= 1.6.0, < 2) 70 | devise (4.2.0) 71 | bcrypt (~> 3.0) 72 | orm_adapter (~> 0.1) 73 | railties (>= 4.1.0, < 5.1) 74 | responders 75 | warden (~> 1.2.3) 76 | diff-lcs (1.2.5) 77 | docile (1.1.5) 78 | erubis (2.7.0) 79 | execjs (2.7.0) 80 | factory_girl (4.7.0) 81 | activesupport (>= 3.0.0) 82 | factory_girl_rails (4.7.0) 83 | factory_girl (~> 4.7.0) 84 | railties (>= 3.0.0) 85 | font-awesome-rails (4.6.3.1) 86 | railties (>= 3.2, < 5.1) 87 | geocoder (1.3.7) 88 | globalid (0.3.7) 89 | activesupport (>= 4.1.0) 90 | haml (4.0.7) 91 | tilt 92 | i18n (0.7.0) 93 | jbuilder (2.6.0) 94 | activesupport (>= 3.0.0, < 5.1) 95 | multi_json (~> 1.2) 96 | jquery-rails (4.1.1) 97 | rails-dom-testing (>= 1, < 3) 98 | railties (>= 4.2.0) 99 | thor (>= 0.14, < 2.0) 100 | jquery-ui-rails (5.0.5) 101 | railties (>= 3.2.16) 102 | json (1.8.3) 103 | kaminari (0.17.0) 104 | actionpack (>= 3.0.0) 105 | activesupport (>= 3.0.0) 106 | libv8 (3.16.14.15) 107 | loofah (2.0.3) 108 | nokogiri (>= 1.5.9) 109 | mail (2.6.4) 110 | mime-types (>= 1.16, < 4) 111 | mime-types (3.1) 112 | mime-types-data (~> 3.2015) 113 | mime-types-data (3.2016.0521) 114 | mini_portile2 (2.1.0) 115 | minitest (5.9.0) 116 | multi_json (1.12.1) 117 | nested_form (0.3.2) 118 | nokogiri (1.6.8) 119 | mini_portile2 (~> 2.1.0) 120 | pkg-config (~> 1.1.7) 121 | orm_adapter (0.5.0) 122 | parser (2.3.1.2) 123 | ast (~> 2.2) 124 | passenger (5.0.30) 125 | rack 126 | rake (>= 0.8.1) 127 | pg (0.18.4) 128 | pkg-config (1.1.7) 129 | powerpack (0.1.1) 130 | rack (1.6.4) 131 | rack-pjax (0.8.0) 132 | nokogiri (~> 1.5) 133 | rack (~> 1.1) 134 | rack-test (0.6.3) 135 | rack (>= 1.0) 136 | rails (4.2.7.1) 137 | actionmailer (= 4.2.7.1) 138 | actionpack (= 4.2.7.1) 139 | actionview (= 4.2.7.1) 140 | activejob (= 4.2.7.1) 141 | activemodel (= 4.2.7.1) 142 | activerecord (= 4.2.7.1) 143 | activesupport (= 4.2.7.1) 144 | bundler (>= 1.3.0, < 2.0) 145 | railties (= 4.2.7.1) 146 | sprockets-rails 147 | rails-deprecated_sanitizer (1.0.3) 148 | activesupport (>= 4.2.0.alpha) 149 | rails-dom-testing (1.0.7) 150 | activesupport (>= 4.2.0.beta, < 5.0) 151 | nokogiri (~> 1.6.0) 152 | rails-deprecated_sanitizer (>= 1.0.1) 153 | rails-html-sanitizer (1.0.3) 154 | loofah (~> 2.0) 155 | rails_admin (0.8.1) 156 | builder (~> 3.1) 157 | coffee-rails (~> 4.0) 158 | font-awesome-rails (>= 3.0, < 5) 159 | haml (~> 4.0) 160 | jquery-rails (>= 3.0, < 5) 161 | jquery-ui-rails (~> 5.0) 162 | kaminari (~> 0.14) 163 | nested_form (~> 0.3) 164 | rack-pjax (~> 0.7) 165 | rails (~> 4.0) 166 | remotipart (~> 1.0) 167 | safe_yaml (~> 1.0) 168 | sass-rails (>= 4.0, < 6) 169 | railties (4.2.7.1) 170 | actionpack (= 4.2.7.1) 171 | activesupport (= 4.2.7.1) 172 | rake (>= 0.8.7) 173 | thor (>= 0.18.1, < 2.0) 174 | rainbow (2.1.0) 175 | rake (11.2.2) 176 | ref (2.0.0) 177 | remotipart (1.2.1) 178 | responders (2.2.0) 179 | railties (>= 4.2.0, < 5.1) 180 | rspec-core (3.5.2) 181 | rspec-support (~> 3.5.0) 182 | rspec-expectations (3.5.0) 183 | diff-lcs (>= 1.2.0, < 2.0) 184 | rspec-support (~> 3.5.0) 185 | rspec-mocks (3.5.0) 186 | diff-lcs (>= 1.2.0, < 2.0) 187 | rspec-support (~> 3.5.0) 188 | rspec-rails (3.5.1) 189 | actionpack (>= 3.0) 190 | activesupport (>= 3.0) 191 | railties (>= 3.0) 192 | rspec-core (~> 3.5.0) 193 | rspec-expectations (~> 3.5.0) 194 | rspec-mocks (~> 3.5.0) 195 | rspec-support (~> 3.5.0) 196 | rspec-support (3.5.0) 197 | rubocop (0.42.0) 198 | parser (>= 2.3.1.1, < 3.0) 199 | powerpack (~> 0.1) 200 | rainbow (>= 1.99.1, < 3.0) 201 | ruby-progressbar (~> 1.7) 202 | unicode-display_width (~> 1.0, >= 1.0.1) 203 | ruby-progressbar (1.8.1) 204 | safe_yaml (1.0.4) 205 | sass (3.4.22) 206 | sass-rails (5.0.6) 207 | railties (>= 4.0.0, < 6) 208 | sass (~> 3.1) 209 | sprockets (>= 2.8, < 4.0) 210 | sprockets-rails (>= 2.0, < 4.0) 211 | tilt (>= 1.1, < 3) 212 | simplecov (0.12.0) 213 | docile (~> 1.1.0) 214 | json (>= 1.8, < 3) 215 | simplecov-html (~> 0.10.0) 216 | simplecov-html (0.10.0) 217 | sprockets (3.7.0) 218 | concurrent-ruby (~> 1.0) 219 | rack (> 1, < 3) 220 | sprockets-rails (3.1.1) 221 | actionpack (>= 4.0) 222 | activesupport (>= 4.0) 223 | sprockets (>= 3.0.0) 224 | term-ansicolor (1.3.2) 225 | tins (~> 1.0) 226 | therubyracer (0.12.2) 227 | libv8 (~> 3.16.14.0) 228 | ref 229 | thor (0.19.1) 230 | thread_safe (0.3.5) 231 | tilt (2.0.5) 232 | tins (1.12.0) 233 | tzinfo (1.2.2) 234 | thread_safe (~> 0.1) 235 | uglifier (3.0.1) 236 | execjs (>= 0.3.0, < 3) 237 | unicode-display_width (1.1.0) 238 | uniform_notifier (1.10.0) 239 | warden (1.2.6) 240 | rack (>= 1.0) 241 | 242 | PLATFORMS 243 | ruby 244 | 245 | DEPENDENCIES 246 | aws-sdk (< 2.0) 247 | bluecloth 248 | brakeman 249 | bullet 250 | bundler-audit 251 | coffee-rails 252 | coveralls 253 | devise 254 | factory_girl_rails 255 | geocoder 256 | haml 257 | jbuilder 258 | jquery-rails 259 | kaminari 260 | passenger 261 | pg 262 | rails (~> 4.2.6) 263 | rails_admin 264 | rspec-rails 265 | rubocop 266 | sass-rails 267 | therubyracer 268 | uglifier 269 | 270 | BUNDLED WITH 271 | 1.12.5 272 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015 MITRE Corporation 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec passenger start -p $PORT -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This Scoreboard is Deprecated 2 | 3 | This scoreboard is no longer maintained and any Pull Requests will be ignored. Please checkout [ctf-scoreboard](http://github.com/mitre-cyber-academy/ctf-scoreboard) for our maintained version of this scoreboard. 4 | 5 | # CTF-Scoreboard 6 | 7 | [![Build Status](https://travis-ci.org/mitre-cyber-academy/ctf-scoreboard.svg?branch=master)](https://travis-ci.org/mitre-cyber-academy/ctf-scoreboard) 8 | [![Coverage Status](https://coveralls.io/repos/github/mitre-cyber-academy/ctf-scoreboard/badge.svg?branch=master)](https://coveralls.io/github/mitre-cyber-academy/ctf-scoreboard?branch=master) 9 | 10 | ### What is this? 11 | 12 | This repository is for the scoreboard used to run MITRE capture the flag competitions. For the registratration app see [this repository](https://github.com/mitre-cyber-academy/registration-app). 13 | 14 | ### How to Run Locally 15 | 16 | 1. Install ruby. 17 | 2. In your terminal run `gem install bundle` 18 | 3. Run `bundle install` 19 | 4. Install postgres to your system (and create a role with your system username) 20 | 5. Run `bundle exec rake db:create` 21 | 6. Run `bundle exec rake db:schema:load` 22 | 6. Run `bundle exec rake db:seed` 23 | 7. Run `bundle exec rails s` 24 | 8. Open the webpage shown in your terminal from the last command in your browser. 25 | 9. Login to the scoreboard as email: `root`, password: `ChangePa$$w0rd` and change the password. 26 | 27 | ### How to Run with Docker 28 | 29 | 1. Install docker and docker-compose for your platform 30 | 2. Clone this project to your local system 31 | 3. Create a .env_vars file in the root containing the contents `SECRET_KEY_BASE=` 32 | 4. From this directory on your system run `docker-compose up -d` 33 | 5. Run `docker-compose run web rake db:create` 34 | 6. Run `docker-compose run web rake db:schema:load` 35 | 7. Run `docker-compose run web rake db:seed` 36 | 8. The scoreboard should now be running at `http://:3000` 37 | 9. Login to the scoreboard as email: `root`, password: `ChangePa$$w0rd` and change the password. 38 | 39 | 40 | ### How do I contribute? 41 | 42 | 1. Fork the repository on github 43 | 2. Run `git clone [address]` 44 | 3. Make your edits 45 | 4. View your edits 46 | 5. Run the git add and commit commands. Please make sure your commit messages are descriptive. 47 | 6. Run `git push origin master` 48 | 7. Submit a pull request 49 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # frozen_string_literal: true 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require File.expand_path('../config/application', __FILE__) 7 | 8 | Scoreboard::Application.load_tasks 9 | -------------------------------------------------------------------------------- /app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-cyber-academy/ctf-scoreboard-archive/56bda947589b60284e6d61817af8d7535ad74b8a/app/assets/images/rails.png -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into including all the files listed below. 2 | // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically 3 | // be included in the compiled file accessible from http://example.com/assets/application.js 4 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 5 | // the compiled file. 6 | // 7 | //= require jquery 8 | //= require jquery_ujs 9 | //= require_directory . 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/categories.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/challenges.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/games.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/messages.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/timeline.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ 4 | 5 | @Timeline = 6 | load: (game) -> 7 | ajax = $.getJSON(game+"/timeline/feed.json", (data) -> 8 | console.log("Updating") 9 | $('#timeline').html("

Recently Solved Challenges

") 10 | for i in data 11 | $('#timeline').append('
  • '+i.email+' solved ' + i.name + '
  • ') 12 | ) 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll automatically include all the stylesheets available in this directory 3 | * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at 4 | * the top of the compiled file, but it's generally better to create a new file per style scope. 5 | *= require_self 6 | *= require_tree . 7 | */ 8 | 9 | 10 | .offset 11 | { 12 | padding-left: 1em; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/categories.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the categories controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/challenges.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the challenges controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | .embed-container { 6 | position: relative; 7 | padding-bottom: 56.25%; 8 | padding-top: 30px; 9 | height: 0; 10 | overflow: hidden; 11 | width: 100%; 12 | height: auto; 13 | margin-bottom: 12px; 14 | } 15 | 16 | .embed-container iframe, 17 | .embed-container object, 18 | .embed-container embed { 19 | position: absolute; 20 | top: 0; left: 0; 21 | width: 100%; 22 | height: 100%; 23 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/games.css.sass: -------------------------------------------------------------------------------- 1 | .state 2 | stroke: desaturate(#0e90d2, 20%) 3 | fill: rgba(desaturate(#0e90d2, 20%),.5) 4 | 5 | .team_location 6 | fill: saturate(#F7901E, 20%) 7 | stroke: darken(#F7901E, 20%) 8 | 9 | .high_school 10 | fill: #C6401D 11 | stroke: darken(#C6401D, 20%) 12 | 13 | .highlighted 14 | fill: #BFD228 15 | stroke: darken(#BFD228, 20%) 16 | 17 | .legend text 18 | fill: #000 19 | stroke: none 20 | shape-rendering: crisp 21 | font: 11px sans-serif 22 | font-weight: 200 23 | text-anchor: left 24 | 25 | 26 | 27 | #clock 28 | font-family: 'Droid Sans', sans-serif 29 | padding-top: 11pt 30 | .hours 31 | 32 | .minutes 33 | color: rgba(21, 122, 181, .8) 34 | 35 | .seconds 36 | color: rgba(21, 122, 181, .8) 37 | 38 | -------------------------------------------------------------------------------- /app/assets/stylesheets/messages.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the messages controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | .message { 6 | margin-bottom:4em; 7 | } 8 | .message:last-child { 9 | margin:0; 10 | } 11 | -------------------------------------------------------------------------------- /app/assets/stylesheets/scaffolds.css.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top:75px; 3 | } 4 | 5 | h1 { 6 | margin:0 0 40px 0; 7 | border-bottom:1px solid #ddd; 8 | } 9 | 10 | .zero-items-text { 11 | text-align:center; 12 | font-weight:bold; 13 | -webkit-border-radius:4px; 14 | -moz-border-radius:4px; 15 | border-radius:4px; 16 | border:1px solid #DDD; 17 | line-height:18px; 18 | padding:8px; 19 | margin-bottom:18px; 20 | } 21 | 22 | .alert-container *:last-child { 23 | margin-bottom:40px; 24 | } 25 | 26 | .table { 27 | margin-bottom:40px; 28 | } 29 | 30 | .table:last-child { 31 | margin:0; 32 | } 33 | 34 | .pagination { 35 | margin:0; 36 | } 37 | -------------------------------------------------------------------------------- /app/assets/stylesheets/timeline.css.sass: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Timeline controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | .timeline path 6 | stroke: desaturate(#0e90d2, 20%) 7 | fill: rgba(desaturate(#0e90d2, 20%),.5) 8 | .challenges path 9 | stroke: desaturate(#0ed290, 20%) 10 | fill: rgba(desaturate(#0ed290, 20%),.5) 11 | 12 | .x.axis path 13 | display none 14 | 15 | 16 | .axis path, 17 | .axis line 18 | fill: none 19 | stroke: #000 20 | shape-rendering: crisp 21 | 22 | .axis text 23 | font: 9px sans-serif 24 | font-weight: 200 25 | 26 | 27 | 28 | #solved_challenges 29 | stroke: desaturate(#BFD228, 20%) 30 | fill: rgba(desaturate(#BFD228, 20%),.25) 31 | -------------------------------------------------------------------------------- /app/controllers/achievements_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class AchievementsController < ApplicationController 3 | def index 4 | @achievements = @game.achievements.order(:text) 5 | @title = 'Achievements' 6 | @subtitle = pluralize(@achievements.size, 'achievement') 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class ApplicationController < ActionController::Base 3 | protect_from_forgery with: :exception 4 | 5 | include SessionsHelper 6 | include ActionView::Helpers::TextHelper 7 | 8 | before_action :load_game, :load_messages_count 9 | 10 | def to_timeline(data, style = nil) 11 | output = [] 12 | data.keys.each do |key| 13 | output << { date: key, style: style, count: data[key].length } 14 | end 15 | output 16 | end 17 | 18 | def enable_auto_reload 19 | @auto_reload = false 20 | end 21 | 22 | # rubocop:disable MethodLength 23 | # rubocop:disable Metrics/AbcSize 24 | # rubocop:disable Metrics/CyclomaticComplexity 25 | # rubocop:disable Metrics/PerceivedComplexity 26 | # rubocop:disable Rails/OutputSafety 27 | def load_game 28 | @game = Game.instance 29 | raise ActiveRecord::RecordNotFound unless @game 30 | unless current_user.nil? 31 | now = Time.zone.now 32 | if now < @game.start 33 | flash.now[:info] = "The game will open on 34 | #{@game.start.strftime('%b %e %y, %R %Z')}.".html_safe 35 | elsif now < @game.stop && now > @game.stop - 1.hour 36 | flash.now[:info] = "The game will stop accepting submissions 37 | on #{@game.stop.strftime('%b %e %y, %R %Z')} 38 | ." 39 | else 40 | flash.now[:info] = I18n.t('game.closed') unless @game.open? 41 | end 42 | end 43 | end 44 | # rubocop:enable MethodLength 45 | # rubocop:enable Metrics/AbcSize 46 | # rubocop:enable Metrics/CyclomaticComplexity 47 | # rubocop:enable Metrics/PerceivedComplexity 48 | # rubocop:enable Rails/OutputSafety 49 | 50 | def load_messages_count 51 | unless current_user.nil? 52 | time = current_user.messages_stamp 53 | @messages_count = if time.nil? 54 | @game.messages.size 55 | else 56 | @game.messages.where('updated_at >= :time', time: time.utc).size 57 | end 58 | end 59 | end 60 | 61 | def enforce_access 62 | deny_access unless current_user 63 | end 64 | 65 | def after_sign_in_path_for(_user) 66 | root_path 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /app/controllers/challenges_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class ChallengesController < ApplicationController 3 | before_action :enforce_access 4 | before_action :find_challenge, except: :index 5 | def index 6 | @categories = @game.categories.includes(:challenges).order(:name) 7 | @challenges = @game.challenges 8 | @divisions = @game.divisions 9 | # Only exists for the purpose of providing an active tab for admins. 10 | @active_division = @divisions.first 11 | @title = 'Challenges' 12 | @subtitle = %(#{pluralize(@challenges.size, 'challenge')} in 13 | #{pluralize(@categories.size, 'category')}) 14 | end 15 | 16 | # rubocop:disable Metrics/AbcSize 17 | def show 18 | is_admin = current_user.is_a?(Admin) 19 | # Accept the flag via GET request if the current user is an admin. 20 | @admin_flag = @challenge.flags.find(params[:flag]) if is_admin && params[:flag] 21 | @solved = @challenge.solved_by_user?(current_user) 22 | @solved_video_url = @challenge.get_video_url_for_flag(current_user) 23 | # Get video URL for admins 24 | @solved_video_url = @admin_flag.video_url if @admin_flag 25 | @solved_by = @challenge.solved_challenges.order(:created_at).reverse_order 26 | flash.now[:success] = I18n.t('flag.accepted') if @solved || @admin_flag 27 | @title = @challenge.name 28 | @subtitle = pluralize(@challenge.point_value, 'point') 29 | @submitted_flags = to_timeline @challenge.submitted_flags.group_by { |sf| sf.updated_at.change(sec: 0) } 30 | end 31 | 32 | # rubocop:disable MethodLength 33 | def submit_flag 34 | # get variables 35 | flag = params[:challenge][:submitted_flag] 36 | is_player = current_user.is_a?(Player) 37 | 38 | # audit log 39 | SubmittedFlag.create(player: current_user, challenge: @challenge, text: flag) if is_player 40 | 41 | # handle flag 42 | flag_found = @challenge.flags.find { |flag_obj| flag_obj.flag.casecmp(flag).zero? } 43 | 44 | if flag_found 45 | 46 | flag_found.invoke_api_request 47 | 48 | if is_player 49 | SolvedChallenge.create(player: current_user, challenge: @challenge, flag: flag_found, 50 | division: current_user.division) 51 | redirect_to @challenge 52 | else 53 | redirect_to challenge_url(@challenge, flag: flag_found) 54 | end 55 | else 56 | redirect_to @challenge, flash: { error: Rails.configuration.wrong_flag_messages.sample } 57 | end 58 | end 59 | # rubocop:enable MethodLength 60 | # rubocop:enable Metrics/AbcSize 61 | 62 | private 63 | 64 | def find_challenge 65 | @challenge = @game.challenges.find(params[:id]) 66 | raise ActiveRecord::RecordNotFound if !current_user.admin? && !@challenge.open?(current_user.division) 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /app/controllers/games_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class GamesController < ApplicationController 3 | helper :all 4 | 5 | def index 6 | redirect_to game_path 7 | end 8 | 9 | # rubocop:disable MethodLength 10 | # rubocop:disable Metrics/AbcSize 11 | def show 12 | respond_to do |format| 13 | format.html do 14 | enable_auto_reload if @game.open? 15 | @divisions = @game.divisions 16 | signed_in_not_admin = current_user && !current_user.admin? 17 | @active_division = signed_in_not_admin ? current_user.division : @divisions.first 18 | @events = @game.feed_items 19 | .includes(player: :division) 20 | .includes(challenge: :category) 21 | .order(:created_at) 22 | .reverse_order 23 | .page(params[:page]).per(25) 24 | @title = @game.name 25 | @html_title = @title 26 | @subtitle = %(#{pluralize(@game.players.size, 'team')} and 27 | #{pluralize(@game.challenges.size, 'challenge')}) 28 | @submitted_flags = to_timeline(SubmittedFlag.all.group_by do |sf| 29 | sf.updated_at.change(sec: 0) 30 | end) 31 | end 32 | format.json 33 | end 34 | end 35 | 36 | def summary 37 | @title = 'Game Summary' 38 | @navbar_override = 'summary' 39 | @submitted_flags = to_timeline SubmittedFlag.all.group_by { |sf| sf.updated_at.change(min: 0) } 40 | @solved_challenges = SolvedChallenge.includes(:challenge).all 41 | @solved_challenges.each do |sc| 42 | sc[:point_value] = sc.challenge.point_value 43 | sc[:user_id] = sc[:id] = sc[:challenge_id] = nil 44 | end 45 | @time_slices = ((@game.stop - @game.start) / 1.hour).round 46 | @divisions = @game.divisions 47 | signed_in_not_admin = current_user && !current_user.admin? 48 | @active_division = signed_in_not_admin ? current_user.division : @divisions.first 49 | 50 | @signed_in_players = Player.where('current_sign_in_ip is not null') 51 | @players = Player.all 52 | end 53 | # rubocop:enable MethodLength 54 | # rubocop:enable Metrics/AbcSize 55 | end 56 | -------------------------------------------------------------------------------- /app/controllers/messages_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class MessagesController < ApplicationController 3 | def index 4 | @messages = @game.messages.order(:updated_at).reverse_order.page(params[:page]).per(10) 5 | @messages_count = 0 6 | current_user.update_attribute(:messages_stamp, Time.now.utc) 7 | @title = 'Messages' 8 | @subtitle = pluralize(@messages.size, 'message') 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class SessionsController < Devise::SessionsController 3 | skip_before_action :verify_authenticity_token, only: [:new, :create], if: :json_request? 4 | respond_to :json 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/timeline_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class TimelineController < ApplicationController 3 | def index 4 | # Grab a set of flags submitted and group them by minutes 5 | @submitted_flags = SubmittedFlag.all.group_by { |sf| sf.updated_at.change(sec: 0) } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class UsersController < ApplicationController 3 | before_action :enforce_access, only: [:download] 4 | 5 | def index 6 | @divisions = @game.divisions 7 | @active_division = current_user && !current_user.admin? ? current_user.division : @divisions.first 8 | @title = 'Teams' 9 | @subtitle = pluralize(@game.players.size, 'team') 10 | end 11 | 12 | # rubocop:disable Metrics/AbcSize 13 | # rubocop:disable Metrics/MethodLength 14 | def show 15 | @player = Player.find(params[:id]) 16 | @players = [@player] 17 | 18 | @solved_challenges = @player.solved_challenges.order('created_at DESC') 19 | @submitted_flags = to_timeline SubmittedFlag.where('user_id=?', 20 | params[:id]).group_by { |sf| sf.updated_at.change(sec: 0) } 21 | @achievements = @player.achievements.order('created_at DESC') 22 | @adjustments = @player.score_adjustments.order('created_at DESC') 23 | @score = @player.score 24 | @title = @player.display_name 25 | # This line is long because we need to NOT create these types of things in the controller. 26 | @subtitle = %(#{pluralize(@score, 'point')} and #{pluralize(@achievements.size, 'achievement')} 27 | in #{@player.division.name} division) 28 | render :show 29 | end 30 | # rubocop:enable Metrics/AbcSize 31 | # rubocop:enable Metrics/MethodLength 32 | 33 | def download 34 | if File.exist? "/opt/keys/#{current_user.key_file_name}.zip" 35 | send_file "/opt/keys/#{current_user.key_file_name}.zip", type: 'application/zip' 36 | else 37 | redirect_to current_user, flash: { error: "Your VPN cert hasn't been 38 | generated yet. Check back in 2 minutes." } 39 | end 40 | end 41 | 42 | private 43 | 44 | def user_params 45 | params.require(:user).permit(:email, :password, :password_confirmation) 46 | end 47 | 48 | def player_params 49 | params.require(:player).permit(:game_id, :tags, :display_name, :city, :affiliation) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module ApplicationHelper 3 | end 4 | -------------------------------------------------------------------------------- /app/helpers/categories_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module CategoriesHelper 3 | end 4 | -------------------------------------------------------------------------------- /app/helpers/challenges_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module ChallengesHelper 3 | def embed(youtube_url) 4 | youtube_id = youtube_url.split('v=').last 5 | content_tag(:iframe, nil, src: "//www.youtube.com/embed/#{youtube_id}", frameborder: 0) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/helpers/games_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module GamesHelper 3 | end 4 | -------------------------------------------------------------------------------- /app/helpers/messages_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module MessagesHelper 3 | end 4 | -------------------------------------------------------------------------------- /app/helpers/sessions_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module SessionsHelper 3 | def deny_access 4 | store_location 5 | redirect_to new_user_session_path 6 | end 7 | 8 | private 9 | 10 | def store_location 11 | session[:return_to] = request.fullpath 12 | end 13 | 14 | def clear_stored_location 15 | session[:return_to] = nil 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/helpers/timeline_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module TimelineHelper 3 | end 4 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-cyber-academy/ctf-scoreboard-archive/56bda947589b60284e6d61817af8d7535ad74b8a/app/mailers/.gitkeep -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-cyber-academy/ctf-scoreboard-archive/56bda947589b60284e6d61817af8d7535ad74b8a/app/models/.gitkeep -------------------------------------------------------------------------------- /app/models/achievement.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class Achievement < FeedItem 3 | validates :text, presence: true, uniqueness: true 4 | 5 | def description 6 | %(Unlocked achievement "#{text}") 7 | end 8 | 9 | def icon 10 | 'certificate' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/admin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class Admin < User 3 | end 4 | -------------------------------------------------------------------------------- /app/models/category.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class Category < ActiveRecord::Base 3 | belongs_to :game 4 | 5 | has_many :challenges 6 | 7 | validates :name, :game_id, presence: true 8 | 9 | # The next challenge can have a greater than or equal to point value of the current one 10 | # and has a name that comes after the current one. The order in which elements are returned 11 | # to self.challenges in is set in the challenges model. 12 | def next_challenge(challenge) 13 | # Order of challenges is handled by default scope in challenge.rb 14 | index = challenges.find_index(challenge) 15 | challenges.at(index + 1) unless index.nil? 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/models/challenge.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class Challenge < ActiveRecord::Base 3 | after_create :add_states_to_challenges 4 | 5 | belongs_to :category 6 | 7 | has_many :challenge_states, dependent: :destroy 8 | 9 | has_many :solved_challenges, through: :flags 10 | 11 | has_many :submitted_flags 12 | 13 | has_many :flags, dependent: :destroy, inverse_of: :challenge 14 | 15 | validates :name, :point_value, :flags, :category_id, presence: true 16 | 17 | enum starting_state: ChallengeState.states 18 | 19 | accepts_nested_attributes_for :flags, allow_destroy: true 20 | 21 | # Handles the ordering of all returned challenge objects. 22 | default_scope -> { order(:point_value, :name) } 23 | 24 | attr_accessor :submitted_flag 25 | 26 | # This bypasses game open check and only looks at the challenge state 27 | def challenge_open?(division) 28 | get_state(division).eql? 'open' 29 | end 30 | 31 | def open?(division) 32 | (challenge_open?(division) && category.game.open?) 33 | end 34 | 35 | def solved? 36 | SolvedChallenge.where('challenge_id = :challenge', challenge: self).count.positive? 37 | end 38 | 39 | # Returns whether or not challenge is available to be opened. 40 | def available?(division) 41 | get_state(division).eql? 'closed' 42 | end 43 | 44 | def force_closed?(division) 45 | get_state(division).eql? 'force_closed' 46 | end 47 | 48 | def get_current_solved_challenge(user) 49 | solved_challenge = SolvedChallenge.where('challenge_id = :challenge and user_id = :user', 50 | challenge: self, user: user) 51 | solved_challenge.first unless solved_challenge.nil? 52 | end 53 | 54 | def solved_by_user?(user) 55 | !get_current_solved_challenge(user).nil? 56 | end 57 | 58 | def get_video_url_for_flag(user) 59 | current_challenge = get_current_solved_challenge(user) 60 | current_challenge.flag.video_url unless current_challenge.nil? || current_challenge.flag.nil? 61 | end 62 | 63 | def get_api_request_for_flag(user) 64 | current_challenge = get_current_solved_challenge(user) 65 | current_challenge.flag.api_request unless current_challenge.nil? || current_challenge.flag.nil? 66 | end 67 | 68 | def set_state(division, new_state) 69 | challenge_state = challenge_states.find_by(division: division) 70 | challenge_state.state = new_state 71 | challenge_state.save 72 | end 73 | 74 | private 75 | 76 | # Gets the state using a division context 77 | def get_state(division) 78 | current_state = challenge_states.find_by(division: division) 79 | # If we somehow manage to not have a current state for this division then make one with the 80 | # default starting state 81 | current_state = challenge_states.create!(division: division, state: starting_state) unless current_state 82 | current_state.state 83 | end 84 | 85 | def add_states_to_challenges 86 | Division.all.find_each do |d| 87 | ChallengeState.create!(challenge: self, division: d, state: starting_state) 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /app/models/challenge_state.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class ChallengeState < ActiveRecord::Base 3 | before_validation :default_values 4 | belongs_to :challenge 5 | belongs_to :division 6 | 7 | validates :challenge_id, uniqueness: { scope: :division_id } 8 | 9 | validates :state, :challenge, presence: true 10 | 11 | enum state: [:open, :closed, :force_closed] 12 | 13 | def default_values 14 | self.state ||= ChallengeState.states[:closed] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/models/division.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class Division < ActiveRecord::Base 3 | after_create :add_states_to_challenges 4 | 5 | belongs_to :game 6 | 7 | has_many :players, dependent: :destroy 8 | 9 | has_many :challenge_states, dependent: :destroy 10 | 11 | has_many :achievements, through: :players 12 | has_many :feed_items, through: :players 13 | has_many :solved_challenges 14 | 15 | validates :name, presence: true 16 | 17 | def ordered_players(only_top_five = false) 18 | # They are eligible if the boolean is true 19 | players = filter_and_sort_players(eligible: true) 20 | # They are ineligible if the boolean is false 21 | ineligible_players = filter_and_sort_players(eligible: false) 22 | # Take the eligible players [in whole competition] and appends 23 | # the ineligible players to the end of the array of eligible players 24 | players.concat(ineligible_players) 25 | # if true return the first five in array 26 | if only_top_five 27 | # Then take the first 5 elements in array 28 | players[0..4] 29 | else 30 | players 31 | end 32 | end 33 | 34 | private 35 | 36 | def add_states_to_challenges 37 | Challenge.all.find_each do |c| 38 | ChallengeState.create!(challenge: c, division: self, state: c.starting_state) 39 | end 40 | end 41 | 42 | # rubocop:disable MethodLength 43 | # Sorts the provided list of players. This sorts directly in the database instead of getting the 44 | # data out of the database and sorting in rails. It gets all feed items of type ScoreAdjustment 45 | # and SolvedChallenge and sums up their values or the value of the challenge in the case of a 46 | # SolvedChallenge. 47 | def filter_and_sort_players(filters) 48 | players.includes(:achievements).where(filters) 49 | .joins( 50 | "LEFT JOIN feed_items 51 | ON feed_items.user_id = users.id 52 | AND feed_items.type IN ('SolvedChallenge', 'ScoreAdjustment') 53 | LEFT JOIN challenges ON challenges.id = feed_items.challenge_id" 54 | ) 55 | .group('users.id') 56 | .select( 57 | 'COALESCE(sum(challenges.point_value), 0) + COALESCE(sum(feed_items.point_value), 0) 58 | as current_score, 59 | MAX(feed_items.created_at) as last_solved_date, users.*' 60 | ) 61 | .order('current_score desc', 'last_solved_date asc', 'display_name asc') 62 | end 63 | # rubocop:enable MethodLength 64 | end 65 | -------------------------------------------------------------------------------- /app/models/feed_item.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class FeedItem < ActiveRecord::Base 3 | belongs_to :player, foreign_key: :user_id 4 | belongs_to :challenge 5 | validates :user_id, :type, presence: true 6 | validates :type, inclusion: %w(SolvedChallenge Achievement ScoreAdjustment) 7 | 8 | def description 9 | self.class 10 | end 11 | 12 | def icon 13 | '' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/models/flag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class Flag < ActiveRecord::Base 3 | belongs_to :challenge, inverse_of: :flags 4 | 5 | has_many :solved_challenges 6 | 7 | validates :flag, presence: true 8 | 9 | def invoke_api_request 10 | # Make API call if one is specified. This throws away any failed requests 11 | # since we really don't care that much if the actions really happen. The 12 | # player properly being credited their points is more important. 13 | Thread.new do 14 | begin 15 | Net::HTTP.get(URI.parse(api_request)) if api_request 16 | # Could list all the execeptions, most advice advocates to move 17 | # to a 3rd party HTTP library that has errors derived from a common 18 | # superclass 19 | rescue StandardError 20 | logger.info "Error invoking api request for flag submission #{self}" 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/game.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class Game < ActiveRecord::Base 3 | # players 4 | has_many :players, through: :divisions 5 | 6 | # feed items 7 | has_many :feed_items, through: :divisions 8 | has_many :achievements, through: :divisions 9 | 10 | # divisions 11 | has_many :divisions 12 | 13 | # challenges 14 | has_many :categories 15 | has_many :challenges, through: :categories 16 | has_many :solved_challenges, through: :divisions 17 | 18 | # messages 19 | has_many :messages 20 | 21 | # validation 22 | validates :name, :start, :stop, presence: true 23 | validate :instance_is_singleton, :order_of_start_and_stop_date 24 | 25 | # class methods 26 | 27 | def self.instance 28 | all.first 29 | end 30 | 31 | # validators 32 | 33 | def instance_is_singleton 34 | singleton = Game.instance 35 | errors.add(:base, I18n.t('game.too_many')) if self != singleton && !singleton.nil? 36 | end 37 | 38 | def order_of_start_and_stop_date 39 | errors.add(:base, I18n.t('game.date_mismatch')) unless start < stop 40 | end 41 | 42 | # helper methods 43 | 44 | def open? 45 | time = Time.zone.now 46 | (start < time && time < stop) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /app/models/message.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class Message < ActiveRecord::Base 3 | belongs_to :game 4 | validates :text, :game_id, :title, presence: true 5 | end 6 | -------------------------------------------------------------------------------- /app/models/player.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class Player < User 3 | belongs_to :division 4 | 5 | has_many :feed_items, foreign_key: :user_id, dependent: :delete_all 6 | has_many :solved_challenges, foreign_key: :user_id, dependent: :delete_all 7 | has_many :score_adjustments, foreign_key: :user_id, dependent: :delete_all 8 | has_many :achievements, foreign_key: :user_id, dependent: :delete_all 9 | 10 | validates :division_id, :display_name, presence: true 11 | 12 | geocoded_by :city 13 | after_validation :geocode, if: :city_changed? 14 | 15 | # touch file in /opt/keys 16 | after_create :touch_file 17 | 18 | def score 19 | feed_items.where(type: [SolvedChallenge, ScoreAdjustment]) 20 | .joins( 21 | 'LEFT JOIN challenges ON challenges.id = feed_items.challenge_id' 22 | ) 23 | .pluck(:point_value, :'challenges.point_value').flatten.compact.sum 24 | end 25 | 26 | def display_name 27 | return self[:display_name] if eligible 28 | return self[:display_name] + ' (ineligible)' unless eligible 29 | end 30 | 31 | # Grab the first 10 characters from the email and append the users ID. 32 | # Since we have 2 different scoreboards the ID is not sufficient, therefore 33 | # if we seed it with some additional data it should solve the issue. 34 | def key_file_name 35 | email.tr('^A-Za-z', '')[0..10] + id.to_s 36 | end 37 | 38 | private 39 | 40 | def touch_file 41 | system("touch /opt/keys/#{key_file_name}") 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /app/models/score_adjustment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class ScoreAdjustment < FeedItem 3 | include ActionView::Helpers::TextHelper 4 | 5 | validates :point_value, :text, presence: true 6 | validate :point_value_is_not_zero 7 | 8 | # rubocop:disable MethodLength 9 | # TODO: Rewrite this. 10 | def description 11 | color = '' 12 | verb = '' 13 | points = point_value 14 | if points.negative? 15 | color = 'red' 16 | verb = 'decreased' 17 | else 18 | color = 'green' 19 | verb = 'increased' 20 | end 21 | %(Score was #{verb} by #{pluralize(points.abs, 'point')}) 22 | end 23 | # rubocop:enable MethodLength 24 | 25 | def icon 26 | point_value.negative? ? 'chevron-down' : 'chevron-up' 27 | end 28 | 29 | def point_value_is_not_zero 30 | errors.add(:point_value, 'must not be zero.') if point_value.zero? 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/models/solved_challenge.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class SolvedChallenge < FeedItem 3 | validates :challenge_id, :flag, :division_id, :player, presence: true 4 | validate :user_has_not_solved_challenge, :challenge_is_open, :game_is_open 5 | 6 | after_save :award_achievement, :open_next_challenge 7 | 8 | belongs_to :flag 9 | belongs_to :division 10 | 11 | def description 12 | %(Solved challenge "#{challenge.category.name} #{challenge.point_value}") 13 | end 14 | 15 | def icon 16 | 'ok' 17 | end 18 | 19 | def challenge_is_open 20 | errors.add(:challenge, I18n.t('challenge.not_open')) unless challenge.open?(player.division) 21 | end 22 | 23 | def game_is_open 24 | errors.add(:base, I18n.t('challenge.game_not_open')) unless challenge.category.game.open? 25 | end 26 | 27 | def user_has_not_solved_challenge 28 | if player.solved_challenges.where('challenge_id = ?', challenge.id).count.positive? 29 | errors.add(:base, I18n.t('challenge.already_solved')) 30 | end 31 | end 32 | 33 | def award_achievement 34 | if Game.instance.solved_challenges.all.count == 1 # if this is the first solved challenge 35 | Achievement.create(player: player, text: 'First Blood!') 36 | end 37 | name = challenge.achievement_name 38 | Achievement.create(player: player, text: name) unless name.blank? 39 | end 40 | 41 | def open_next_challenge 42 | challenge = self.challenge 43 | category = challenge.category 44 | challenge = category.next_challenge(challenge) 45 | challenge.set_state(player.division, 'open') if challenge && challenge.available?(player.division) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /app/models/submitted_flag.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class SubmittedFlag < ActiveRecord::Base 3 | belongs_to :player, foreign_key: :user_id 4 | belongs_to :challenge 5 | validates :text, presence: true 6 | end 7 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class User < ActiveRecord::Base 3 | # Include default devise modules. Others available are: 4 | # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable 5 | # :registerable, :recoverable, :rememberable, 6 | devise :database_authenticatable, :trackable, :lockable, :validatable 7 | 8 | # validators 9 | validates :email, :type, presence: true 10 | validates :type, inclusion: %w(Admin Player) 11 | 12 | # is admin 13 | def admin? 14 | is_a?(Admin) 15 | end 16 | 17 | def change_password 18 | nil 19 | end 20 | 21 | def change_password=(value) 22 | return nil if value.blank? 23 | self.password = value 24 | self.password_confirmation = value 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/views/achievements/index.html.haml: -------------------------------------------------------------------------------- 1 | - if @achievements.size == 0 2 | .zero-items-text No Achievements 3 | - else 4 | %table.table.table-bordered.table-striped 5 | %thead 6 | %tr 7 | %th Name 8 | %th Unlocked By 9 | %th When 10 | %tbody 11 | - @achievements.each do |a| 12 | %tr 13 | %td= a.text 14 | %td= link_to_unless current_user.nil?, a.player.display_name, a.player 15 | %td= a.created_at.strftime("%b %e %y, %R %Z") -------------------------------------------------------------------------------- /app/views/challenges/_gameboard.html.haml: -------------------------------------------------------------------------------- 1 | - if challenges.size == 0 2 | .zero-items-text No Challenges 3 | - else 4 | - max = 0 5 | %table.table.table-bordered 6 | %thead 7 | %tr 8 | - categories.each do |c| 9 | %th{ :style => "text-align:center;width:#{1.0 / @categories.size.to_f * 100.0}%;" }= c.name 10 | - max = [max, c.challenges.size].max 11 | %tbody 12 | - is_admin = current_user.admin? 13 | - max.times do |row| 14 | %tr 15 | - categories.size.times do |column| 16 | - challenges = categories[column].challenges 17 | - if row < challenges.size 18 | - challenge = challenges[row] 19 | - is_solved = false 20 | - style = "text-align:center;" 21 | - if is_admin 22 | - is_solved = challenge.solved? 23 | - else 24 | - is_solved = challenge.solved_by_user?(current_user) 25 | - if challenge.force_closed?(division) && challenge.solved? 26 | - style << "color:DarkOrange;" 27 | - elsif challenge.force_closed?(division) 28 | - style << "color:red;" 29 | - elsif is_solved 30 | - style << "color:#08C;" 31 | - elsif challenge.open?(division) || (is_admin && challenge.challenge_open?(division)) 32 | - style << "color:green;" 33 | - else 34 | - style << "color:gray;" 35 | %td{ style: style }= link_to_if (is_admin || challenge.open?(division)), challenge.point_value, challenge, style: "color:inherit;" 36 | - else 37 | %td   38 | -------------------------------------------------------------------------------- /app/views/challenges/index.html.haml: -------------------------------------------------------------------------------- 1 | - if current_user.admin? 2 | .panel.panel-default 3 | -# Generate tabs for each division with a name 4 | %ul.nav.nav-tabs 5 | -@divisions.each do |d| 6 | %li{ :class => ('active' if @active_division.eql? d) } 7 | = link_to "#{d.name}", "#division-#{d.id}-tab", "data-toggle"=>"tab" 8 | .tab-content 9 | -@divisions.each do |d| 10 | .tab-pane{ :class => ('active' if @active_division.eql? d), :id => "division-#{d.id}-tab" } 11 | = render partial: "challenges/gameboard", locals: { challenges: @challenges, categories: @categories, division: d } 12 | - else 13 | .container-fluid 14 | = render partial: "challenges/gameboard", locals: { challenges: @challenges, categories: @categories, division: current_user.division } -------------------------------------------------------------------------------- /app/views/challenges/show.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :admin_menu do 2 | %li= link_to %[Edit "#{@challenge.name}"], rails_admin.edit_path("challenge", @challenge.id) 3 | 4 | %div{ :style => "margin-bottom:40px;" } 5 | - if @challenge.description.blank? 6 | No description 7 | - else 8 | = raw(BlueCloth.new(@challenge.description).to_html) 9 | - if !@solved_video_url.blank? 10 | .embed-container 11 | = embed(@solved_video_url) 12 | 13 | %h2 Solved By 14 | = render partial: "events/timeline", locals: {id: "timeline", data: @submitted_flags} 15 | 16 | - if !@solved 17 | = form_for @challenge, url: submit_flag_challenge_path(@challenge), method: "post", html: { class: "well", style: "margin-bottom:40px;" } do |f| 18 | .control-group 19 | %p Submit Flag 20 | .control-group 21 | .controls 22 | = f.text_field :submitted_flag, :class => "span5" 23 | .control-group 24 | = button_tag "Submit", :class => "btn btn-primary" 25 | 26 | - if @solved_by.size > 0 27 | %table.table.table-bordered.table-striped 28 | %thead 29 | %tr 30 | %th # 31 | %th Team 32 | %th Division 33 | %th When 34 | %tbody 35 | - @solved_by.each_with_index do |e, i| 36 | %tr 37 | %td= i + 1 38 | %td= link_to e.player.display_name, e.player 39 | %td= e.player.division.name 40 | %td= e.created_at.strftime("%b %e %y, %R %Z") 41 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

    Resend confirmation instructions

    2 | 3 | <%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

    <%= f.label :email %>
    7 | <%= f.email_field :email %>

    8 | 9 |

    <%= f.submit "Resend confirmation instructions" %>

    10 | <% end %> 11 | 12 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

    Welcome <%= @resource.email %>!

    2 | 3 |

    You can confirm your account through the link below:

    4 | 5 |

    <%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %>

    6 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

    Hello <%= @resource.email %>!

    2 | 3 |

    Someone has requested a link to change your password, and you can do this through the link below.

    4 | 5 |

    <%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %>

    6 | 7 |

    If you didn't request this, please ignore this email.

    8 |

    Your password won't change until you access the link above and create a new one.

    9 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

    Hello <%= @resource.email %>!

    2 | 3 |

    Your account has been locked due to an excessive amount of unsuccessful sign in attempts.

    4 | 5 |

    Click the link below to unlock your account:

    6 | 7 |

    <%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %>

    8 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

    Change your password

    2 | 3 | <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | <%= f.hidden_field :reset_password_token %> 6 | 7 |

    <%= f.label :password, "New password" %>
    8 | <%= f.password_field :password %>

    9 | 10 |

    <%= f.label :password_confirmation, "Confirm new password" %>
    11 | <%= f.password_field :password_confirmation %>

    12 | 13 |

    <%= f.submit "Change my password" %>

    14 | <% end %> 15 | 16 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

    Forgot your password?

    2 | 3 | <%= form_for(resource, :as => resource_name, :url => password_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

    <%= f.label :email %>
    7 | <%= f.email_field :email %>

    8 | 9 |

    <%= f.submit "Send me reset password instructions" %>

    10 | <% end %> 11 | 12 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

    Edit <%= resource_name.to_s.humanize %>

    2 | 3 | <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

    <%= f.label :email %>
    7 | <%= f.email_field :email %>

    8 | 9 |

    <%= f.label :password %> (leave blank if you don't want to change it)
    10 | <%= f.password_field :password %>

    11 | 12 |

    <%= f.label :password_confirmation %>
    13 | <%= f.password_field :password_confirmation %>

    14 | 15 |

    <%= f.label :current_password %> (we need your current password to confirm your changes)
    16 | <%= f.password_field :current_password %>

    17 | 18 |

    <%= f.submit "Update" %>

    19 | <% end %> 20 | 21 |

    Cancel my account

    22 | 23 |

    Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.

    24 | 25 | <%= link_to "Back", :back %> 26 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |

    Sign up

    2 | 3 | <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

    <%= f.label :email %>
    7 | <%= f.email_field :email %>

    8 | 9 |

    <%= f.label :password %>
    10 | <%= f.password_field :password %>

    11 | 12 |

    <%= f.label :password_confirmation %>
    13 | <%= f.password_field :password_confirmation %>

    14 | 15 |

    <%= f.submit "Sign up" %>

    16 | <% end %> 17 | 18 | <%= render :partial => "devise/shared/links" %> 19 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 2 | Sign In 3 | %small   4 | 5 | = render :partial => 'layouts/flash', :locals => { :flash => flash } 6 | 7 | = form_for resource, :as => resource_name, :url => session_path(resource_name), :html => { :class => "well" } do |f| 8 | 9 | .control-group 10 | = f.label :email, "Login", :class => "control-label" 11 | .controls 12 | = f.text_field :email, :class => "span5" 13 | 14 | .control-group 15 | = f.label :password, :class => "control-label" 16 | .controls 17 | = f.password_field :password, :class => "span5" 18 | 19 | - if devise_mapping.rememberable? 20 | .control-group 21 | .controls 22 | = f.check_box :remember_me 23 | = f.label :remember_me 24 | 25 | .control-group 26 | .controls 27 | = button_tag "Sign in", :class => "btn btn-primary" 28 |   29 | By logging in, you agree to our 30 | = link_to("terms of service", 'http://mitrecyberacademy.org/terms-of-service') + "." -------------------------------------------------------------------------------- /app/views/devise/shared/_links.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Sign in", new_session_path(resource_name) %>
    3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
    7 | <% end -%> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
    11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
    15 | <% end -%> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
    19 | <% end -%> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider) %>
    24 | <% end -%> 25 | <% end -%> -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

    Resend unlock instructions

    2 | 3 | <%= form_for(resource, :as => resource_name, :url => unlock_path(resource_name), :html => { :method => :post }) do |f| %> 4 | <%= devise_error_messages! %> 5 | 6 |

    <%= f.label :email %>
    7 | <%= f.email_field :email %>

    8 | 9 |

    <%= f.submit "Resend unlock instructions" %>

    10 | <% end %> 11 | 12 | <%= render :partial => "devise/shared/links" %> -------------------------------------------------------------------------------- /app/views/events/_timeline.haml: -------------------------------------------------------------------------------- 1 | / This is an ugly hack because of https://github.com/paulnicholson/coffee-filter#caveats 2 | :javascript 3 | if(typeof(data) == "undefined"){ 4 | data = {} 5 | style = {} 6 | chartHeight = {} 7 | chartWidth = {} 8 | interpolate = {} 9 | events = {} 10 | } 11 | id = "#{id}" 12 | data[id] = #{data.to_json} 13 | chartHeight[id] = #{height||=17} 14 | chartWidth[id] = #{width||=940} 15 | style[id] = "#{style||="timeline"}" 16 | interpolate[id] = "#{interpolate||="cardinal-open"}" 17 | 18 | margin = 17 19 | 20 | 21 | %div{:id=>"#{id}"} 22 | 23 | :coffeescript 24 | 25 | axis = true 26 | axis = false if chartHeight[id] < 100 27 | 28 | chartWidth[id] = document.getElementById(id).parentElement.clientWidth 29 | chart = d3.select("#"+id).append("svg") 30 | .attr("class", style[id]) 31 | .attr("width", ()-> chartWidth[id]+"px") 32 | .attr("height", ()-> chartHeight[id]+"px"); 33 | 34 | # Make some scales here 35 | heightScale = d3.scale.linear() 36 | .range([chartHeight[id], 0]) 37 | .domain([d3.max(data[id], (d) -> (d["count"])),0]) 38 | 39 | timeScale = d3.time.scale() 40 | .range([0,chartWidth[id]]) 41 | .domain(d3.extent(data[id], (d) -> new Date(d["date"]))) 42 | 43 | area = d3.svg.area() 44 | .interpolate(interpolate[id]) 45 | .x((d) -> (timeScale(new Date(d["date"])))) 46 | .y0(chartHeight[id]) 47 | .y1((d) -> (heightScale(d["count"]))) 48 | 49 | xAxis = d3.svg.axis() 50 | .scale(timeScale) 51 | .orient("top") 52 | 53 | 54 | chart.append("path") 55 | .datum(data[id].sort((a,b) -> (new Date(a["date"]) - new Date(b["date"])))) 56 | .attr("class", "data") 57 | .attr("d", area) 58 | if axis 59 | chart.append("g") 60 | .attr("class", "x axis") 61 | .attr("transform", "translate(0," + chartHeight[id] + ")") 62 | .call(xAxis); 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/views/games/_countdown.html.haml: -------------------------------------------------------------------------------- 1 | %div.muted{id:"clock",class:"clock pull-right"} 2 | %h4.muted 3 | Time Remaining: 4 | 5 | %span{id:"days", class:"days"} 6 | %span{id:"hours", class:"hours"} 7 | %span{id:"minutes", class:"minutes"} 8 | %small{id:"seconds", class:"seconds"} 9 | 10 | 11 | :coffeescript 12 | countdown = (endTime) -> 13 | diff = endTime - new Date() 14 | diff = 0 if diff < 0 15 | days = Math.floor(diff/1000/60/60/24) 16 | diff -= days * 1000*60*60*24 17 | hours = Math.floor(diff/1000/60/60) 18 | diff -= hours * 1000*60*60 19 | minutes = Math.floor(diff/1000/60) 20 | diff -= minutes * 1000*60 21 | seconds = Math.floor(diff/1000) 22 | return {"days": days, "hours": hours, "minutes":minutes, "seconds":seconds} 23 | 24 | String.prototype.paddingLeft = (paddingValue) -> 25 | String(paddingValue + this).slice(-paddingValue.length) 26 | 27 | updateClock = () -> 28 | difference = countdown(new Date(#{endTime.to_i*1000})) 29 | if difference.days == 0 30 | d3.selectAll("#days").text("").style("visibility", "hidden").style("") 31 | else if difference.days < 100 32 | d3.selectAll("#days").text(difference.days.toString().paddingLeft("00")+ "d") 33 | else 34 | d3.selectAll("#days").text(difference.days.toString().paddingLeft("000")+ "d") 35 | d3.selectAll("#hours").text(difference.hours.toString().paddingLeft("00")+ "h") 36 | d3.selectAll("#minutes").text(difference.minutes.toString().paddingLeft("00") + "m") 37 | d3.selectAll("#seconds").text(difference.seconds.toString().paddingLeft("00")+ "s") 38 | updateClock() 39 | setInterval(updateClock, 1000) 40 | -------------------------------------------------------------------------------- /app/views/games/show.html.haml: -------------------------------------------------------------------------------- 1 | %h2 Top 5 Teams 2 | %br 3 | = render partial: "users/divisions", locals: {divisions: @divisions, active_division: @active_division, only_top_five: true} 4 | %br 5 | %h2 Game Feed 6 | - if @events.count == 0 7 | .zero-items-text No Events 8 | - else 9 | %h4{:class=>"small muted"} 10 | Flags/Min: 11 | = render partial: "events/timeline", locals: {id: 'timeline', data: @submitted_flags} 12 | %table.table.table-bordered.table-striped 13 | %thead 14 | %tr 15 | %th 16 | %th Team 17 | %th Event 18 | %th Division 19 | %th When 20 | %tbody 21 | - @events.each do |e| 22 | %tr 23 | %td{ :style => "text-align:center;width:20px;" } 24 | %i{ :class => "icon-#{e.icon}" } 25 | %td= link_to_unless current_user.nil?, e.player.display_name, e.player 26 | %td= raw(e.description) 27 | %td= e.player.division.name 28 | %td= e.created_at.strftime("%b %e %y, %R %Z") 29 | 30 | = paginate @events -------------------------------------------------------------------------------- /app/views/games/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | json.array!(@game.players) do |json, player| 3 | json.name player.name 4 | json.tags player.tags 5 | 6 | # feed items 7 | json.feed player.feed_items.order('created_at') do |jayson, item| 8 | jayson.created_at item.created_at 9 | jayson.type item.type 10 | jayson.text item.text 11 | jayson.points item.point_value 12 | unless item.challenge.nil? 13 | jayson.text item.challenge.name 14 | jayson.points item.challenge.point_value 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/games/summary.html.haml: -------------------------------------------------------------------------------- 1 | :javascript 2 | var solved_challenges = #{@solved_challenges.to_json} 3 | var hours = #{@time_slices} 4 | var start = new Date(#{@game.start.to_i*1000}) 5 | :coffeescript 6 | window.highlightOnMap = (mapSelector, elementSelector) -> 7 | d3.selectAll(elementSelector) 8 | .transition() 9 | .attr("class", "highlighted") 10 | 11 | window.unhighlightOnMap = (mapSelector, elementSelector) -> 12 | d3.selectAll(elementSelector) 13 | .transition() 14 | .delay(250) 15 | .attr("class", "team_location " + d3.selectAll(elementSelector).attr("tags")) 16 | %h3{:class=>"muted"} 17 | Top 5 Teams 18 | %br/ 19 | = render partial: "users/divisions", locals: {divisions: @divisions, active_division: @active_division, only_top_five: true} 20 | %hr 21 | 22 | - if not @game.disable_flags_an_hour_graph 23 | %div{class:"row"} 24 | %div{class: "span12"} 25 | %h3{:class=>"muted"} 26 | Flags/Hour: 27 | %h4{id:"scoreToggle" , :class=>"muted offset", onclick:"Javascript:window.toggleScoreDisplay()"} 28 | Show Score over Time 29 | = render partial: "events/timeline", locals: {id: 'timeline', data: @submitted_flags, height:200, width: 940, interpolate: "basis", style:"timeline"} 30 | :coffeescript 31 | filterByDate = (date) -> ((el)->(new Date(el.updated_at) < date)) 32 | timeSlices = [] 33 | for hour in [1..hours] 34 | timeSlice = new Date(start.getTime()+hour*3600000) 35 | challenges = solved_challenges.filter(filterByDate(timeSlice)) 36 | timeSlices.push {"time":timeSlice, "challenges": challenges, "points":d3.sum(challenges, (d) -> (d.point_value))} 37 | # Make some scales here 38 | window.heightScale = d3.scale.linear() 39 | .range([chartHeight["timeline"], 0]) 40 | .domain([0,d3.max(timeSlices, (d) -> (d.points))*1.1]) 41 | 42 | window.timeSlices = timeSlices 43 | window.timeScale = d3.time.scale() 44 | .range([0,chartWidth["timeline"]]) 45 | .domain([start,new Date(start.getTime()+hours*3600000)]) 46 | 47 | 48 | 49 | area = d3.svg.area() 50 | .interpolate("monotone") 51 | .x((d) -> (timeScale(new Date(d["time"])))) 52 | .y0(chartHeight[id]) 53 | .y1(chartHeight[id]) 54 | d3.select("#timeline").select("svg").append("path") 55 | .datum(timeSlices) 56 | .attr("d", area) 57 | .attr("id", "solved_challenges") 58 | .attr("shown", 0) 59 | window.toggleScoreDisplay = () -> 60 | sd = d3.select("#solved_challenges") 61 | if sd.attr("shown") == "0" 62 | area.y1((d) -> (heightScale(d.points))) 63 | d3.select("#solved_challenges") 64 | .datum(timeSlices) 65 | .transition() 66 | .duration(500) 67 | .attr("d", area) 68 | .attr("id", "solved_challenges") 69 | .attr("shown", "1") 70 | d3.select("#scoreToggle").text("Hide Score over Time") 71 | else 72 | area.y1((d)->chartHeight[id]) 73 | d3.select("#solved_challenges") 74 | .datum(timeSlices) 75 | .transition() 76 | .duration(500) 77 | .attr("d", area) 78 | .attr("id", "solved_challenges") 79 | .attr("shown", "0") 80 | d3.select("#scoreToggle").text("Show Score over Time") 81 | %hr 82 | %div{class: "row"} 83 | %div.span4 84 | %h3{:class=>"h4 muted"} 85 | Currently Logged In Users: 86 | %span{:class=>"lead"} 87 | = @signed_in_players.size 88 | - @players.each do |p| 89 | %div{style:"padding-left:15px;padding-top:10px;"} 90 | %span{onmouseover:"Javascript:highlightOnMap('#map','##{p.email}')",onmouseout:"Javascript:unhighlightOnMap('#map','##{p.email}')"} 91 | %strong{class: "text-info"} 92 | 93 | = p.email 94 | - if p.current_sign_in_ip.nil? 95 | are not currently playing. 96 | - else 97 | are currently playing. 98 | %div.span8 99 | = render partial: "users/map", locals: {data: @players} 100 | 101 | -------------------------------------------------------------------------------- /app/views/kaminari/_first_page.html.haml: -------------------------------------------------------------------------------- 1 | -# Link to the "First" page 2 | -# available local variables 3 | -# url: url to the first page 4 | -# current_page: a page object for the currently displayed page 5 | -# num_pages: total number of pages 6 | -# per_page: number of items to fetch per page 7 | -# remote: data-remote 8 | %li= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, :remote => remote 9 | -------------------------------------------------------------------------------- /app/views/kaminari/_gap.html.haml: -------------------------------------------------------------------------------- 1 | -# Non-link tag that stands for skipped pages... 2 | -# available local variables 3 | -# current_page: a page object for the currently displayed page 4 | -# num_pages: total number of pages 5 | -# per_page: number of items to fetch per page 6 | -# remote: data-remote 7 | %li.disabled= link_to "…", "#" # raw(t 'views.pagination.truncate') 8 | -------------------------------------------------------------------------------- /app/views/kaminari/_last_page.html.haml: -------------------------------------------------------------------------------- 1 | -# Link to the "Last" page 2 | -# available local variables 3 | -# url: url to the last page 4 | -# current_page: a page object for the currently displayed page 5 | -# num_pages: total number of pages 6 | -# per_page: number of items to fetch per page 7 | -# remote: data-remote 8 | %li= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {:remote => remote} 9 | -------------------------------------------------------------------------------- /app/views/kaminari/_next_page.html.haml: -------------------------------------------------------------------------------- 1 | -# Link to the "Next" page 2 | -# available local variables 3 | -# url: url to the next page 4 | -# current_page: a page object for the currently displayed page 5 | -# num_pages: total number of pages 6 | -# per_page: number of items to fetch per page 7 | -# remote: data-remote 8 | %li= link_to_unless current_page.last?, "»", url, :rel => 'next', :remote => remote 9 | -------------------------------------------------------------------------------- /app/views/kaminari/_page.html.haml: -------------------------------------------------------------------------------- 1 | -# Link showing page number 2 | -# available local variables 3 | -# page: a page object for "this" page 4 | -# url: url to this page 5 | -# current_page: a page object for the currently displayed page 6 | -# num_pages: total number of pages 7 | -# per_page: number of items to fetch per page 8 | -# remote: data-remote 9 | - if page.current? 10 | %li.active= link_to page, url 11 | - else 12 | %li= link_to page, url 13 | -------------------------------------------------------------------------------- /app/views/kaminari/_paginator.html.haml: -------------------------------------------------------------------------------- 1 | -# The container tag 2 | -# available local variables 3 | -# current_page: a page object for the currently displayed page 4 | -# num_pages: total number of pages 5 | -# per_page: number of items to fetch per page 6 | -# remote: data-remote 7 | -# paginator: the paginator that renders the pagination tags inside 8 | = paginator.render do 9 | .pagination.pagination-centered 10 | %ul 11 | /= first_page_tag unless current_page.first? 12 | = prev_page_tag unless current_page.first? 13 | - each_page do |page| 14 | - if page.left_outer? || page.right_outer? || page.inside_window? 15 | = page_tag page 16 | - elsif !page.was_truncated? 17 | = gap_tag 18 | = next_page_tag unless current_page.last? 19 | /= last_page_tag unless current_page.last? 20 | -------------------------------------------------------------------------------- /app/views/kaminari/_prev_page.html.haml: -------------------------------------------------------------------------------- 1 | -# Link to the "Previous" page 2 | -# available local variables 3 | -# url: url to the previous page 4 | -# current_page: a page object for the currently displayed page 5 | -# num_pages: total number of pages 6 | -# per_page: number of items to fetch per page 7 | -# remote: data-remote 8 | %li= link_to_unless current_page.first?, "«", url, :rel => 'prev', :remote => remote 9 | -------------------------------------------------------------------------------- /app/views/layouts/_flash.html.haml: -------------------------------------------------------------------------------- 1 | .alert-container 2 | - flash.each do |key, value| 3 | - type = key.to_s 4 | - if ["error", "info", "success"].include?(type) 5 | %div{ :class => "alert alert-#{type}" }= raw(value) 6 | - else 7 | %div{ :class => "alert" }= raw(value) 8 | - if @errors 9 | - @errors.full_messages.each do |m| 10 | .alert.alert-error= "#{m}." 11 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | / 2 | :plain 3 | __ __ ___ _____ ___ ___ 4 | | \/ |_ _|_ _| _ \ __| 5 | | |\/| || | | | | / _| 6 | |_| |_|___| |_| |_|_\___| 7 | 8 | !!! 9 | %html 10 | %head 11 | 12 | - if @html_title 13 | %title= @html_title 14 | - elsif @title 15 | %title= %[#{@game.name} : #{@title}] 16 | - else 17 | %title Scoreboard 18 | 19 | = stylesheet_link_tag "application" 20 | = javascript_include_tag "application" 21 | = csrf_meta_tags 22 | 23 | = yield :head 24 | 25 | -# mark tabs as active 26 | - if @title 27 | :javascript 28 | $(document).ready(function() { 29 | $("#nav-#{@navbar_override || @title.downcase}").addClass("active"); 30 | }); 31 | 32 | -# auto reload 33 | - if @auto_reload 34 | :javascript 35 | $(document).ready(function() { 36 | window.setInterval(function() { 37 | window.location.reload(); 38 | }, 30000); 39 | }); 40 | 41 | %body 42 | .navbar.navbar-fixed-top 43 | .navbar-inner 44 | .container 45 | %ul.nav.pull-left 46 | = link_to @game.name, root_path, :class => "brand" 47 | - if current_user 48 | %li#nav-challenges= link_to "Challenges", challenges_path 49 | %li#nav-messages 50 | %a{ href: messages_path } 51 | Messages 52 | - if @messages_count > 0 53 | %span.badge.badge-important= @messages_count 54 | %li#nav-teams= link_to "Teams", players_path 55 | %li#nav-achievements= link_to "Achievements", achievements_path 56 | %li#nav-summary= link_to "Summary", "/summary" 57 | 58 | %ul.nav.pull-right 59 | - if current_user 60 | - if current_user.admin? 61 | %li.dropdown 62 | %a.dropdown-toggle{ :href => "#", "data-toggle".to_sym => "dropdown" } 63 | Admin 64 | %b.caret 65 | %ul.dropdown-menu 66 | %li= link_to "Dashboard", "/admin" 67 | %li.divider 68 | %li= link_to "Post a new Message", rails_admin.new_path("message") 69 | %li= link_to "View Submitted Flags", rails_admin.index_path("submitted_flag") 70 | %li= link_to "Award an Achievement", rails_admin.new_path("achievement") 71 | %li= link_to "New score adjustment", rails_admin.new_path("score_adjustment") 72 | - if content_for?(:admin_menu) 73 | %li.divider 74 | = yield :admin_menu 75 | - else 76 | %li= link_to "My Team", current_user 77 | %li.divider-vertical 78 | %li= link_to "Log Out", "/signout" 79 | - else 80 | %li= link_to "Log In", "/signin" 81 | 82 | .container 83 | - unless @title.blank? 84 | %h1 85 | = @title 86 | %small= @subtitle 87 | - if Time.now > @game.start && Time.now < @game.stop 88 | = render partial: "games/countdown", locals: {endTime: @game.stop} 89 | 90 | = render :partial => 'layouts/flash', :locals => { :flash => flash } unless current_user.nil? 91 | = yield 92 | 93 | .container 94 | %p{ :style => "text-align:center;margin-top:40px;" }< 95 | MITRE Cyber Academy™ | Terms of Service 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /app/views/messages/index.html.haml: -------------------------------------------------------------------------------- 1 | - if @messages.count == 0 2 | .zero-items-text No Messages 3 | - else 4 | - @messages.each do |m| 5 | %div.message 6 | %h2 7 | = m.title 8 | %small= m.updated_at.strftime("%b %e, %R %Z") 9 | = raw(BlueCloth.new(m.text).to_html) 10 | = paginate @messages -------------------------------------------------------------------------------- /app/views/users/_divisions.html.haml: -------------------------------------------------------------------------------- 1 | - if divisions.size > 1 2 | .panel.panel-default 3 | -# Generate tabs for each division with a name 4 | %ul.nav.nav-tabs 5 | -divisions.each do |d| 6 | %li{ :class => ('active' if active_division.eql? d) } 7 | = link_to "#{d.name}", "#division-#{d.id}-tab", "data-toggle"=>"tab" 8 | .tab-content 9 | -divisions.each do |d| 10 | .tab-pane{ :class => ('active' if active_division.eql? d), :id => "division-#{d.id}-tab" } 11 | = render partial: "users/list", locals: { players: d.ordered_players(only_top_five) } 12 | - else 13 | .container-fluid 14 | = render partial: "users/list", locals: { players: divisions.first.ordered_players(only_top_five) } -------------------------------------------------------------------------------- /app/views/users/_list.html.haml: -------------------------------------------------------------------------------- 1 | - if players.size.eql? 0 2 | .zero-items-text No Teams 3 | - else 4 | - max = 0 5 | %table.table.table-bordered.table-striped 6 | %thead 7 | %tr 8 | %th{ :style => "width:50px;" } # 9 | %th Name 10 | %th{ :style => "width:100px;" } Achievements 11 | %th{ :style => "width:100px;" } Points 12 | %th{ :style => "width:50%;" } 13 | %tbody 14 | - players.each_with_index do |p, i| 15 | / Current score only exists due to the division.rb model creating it inside of 16 | / filter_and_sort_players, it is used here since it saves us from doing a costly 17 | / score recalculation for each user. 18 | - score = p.current_score 19 | - if score > max 20 | - max = score 21 | - percent = score.to_f / max.to_f * 100.0 22 | %tr 23 | %td= i + 1 24 | %td{:class => "#{p.tags}"} 25 | = link_to p.display_name, p 26 | %td= p.achievements.size 27 | %td= score 28 | %td 29 | .progress{ :style => "margin:0;" } 30 | .bar{ :style => "width:#{percent}%;" } 31 | -------------------------------------------------------------------------------- /app/views/users/_map.html.haml: -------------------------------------------------------------------------------- 1 | 2 | %div{id: "map"} 3 | 4 | :javascript 5 | var players = #{data.to_json} 6 | 7 | :coffeescript 8 | width = document.getElementById("map").clientWidth 9 | height = width/2 10 | 11 | projection = d3.geo.albersUsa() 12 | .scale(width) 13 | .translate([width / 2, height / 2]); 14 | 15 | path = d3.geo.path() 16 | .projection(projection); 17 | svg = d3.select("#map").append("svg") 18 | .attr("width", width) 19 | .attr("height", height) 20 | 21 | g = svg.append("g") 22 | .attr("id", "states") 23 | 24 | d3.json("/states.json", (json)-> 25 | g.selectAll("path") 26 | .data(json.features) 27 | .enter().append("path") 28 | .attr("d",path) 29 | .attr("class","state") 30 | # Why is this in here? Because otherwise it can render the team locations prior to map and it looks funky. 31 | svg.selectAll(".team") 32 | .data(players) 33 | .enter().append("path") 34 | .attr("d", d3.svg.symbol().type((d)-> 35 | if d.tags?.indexOf("mitre") > 0 36 | return "triangle-up" 37 | else 38 | return "circle" 39 | ) 40 | .size(64)) 41 | .attr("id", (d)->(d.email)) 42 | .attr("transform", (d)-> 43 | translate = projection([d.longitude,d.latitude]) 44 | "translate(" +translate[0] + "," + translate[1] + ")" 45 | ) 46 | .attr("tags", (d)->(d.tags)) 47 | .attr("class", (d) -> 48 | ("team_location " + d.tags) 49 | ) 50 | 51 | ) 52 | -------------------------------------------------------------------------------- /app/views/users/index.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: "users/divisions", locals: {divisions: @divisions, active_division: @active_division, only_top_five: false} -------------------------------------------------------------------------------- /app/views/users/public.html.haml: -------------------------------------------------------------------------------- 1 | %div.row 2 | %div.span5 3 | %h3.muted Flags/Min: 4 | = render partial: "events/timeline", locals: {id: "timeline", data: @submitted_flags} 5 | %div.span5.offset1 6 | %h3.muted 7 | Affiliation: 8 | %small 9 | -if not @player.affiliation == '' 10 | = @player.affiliation 11 | -else 12 | Unknown 13 | 14 | %div.row 15 | %div.span5 16 | - if @solved_challenges.size == 0 17 | .zero-items-text No Solved Challenges 18 | - else 19 | %table.table.table-bordered.table-striped 20 | %thead 21 | %tr 22 | %th # 23 | %th Category 24 | %th Points 25 | %th When 26 | %tbody 27 | - @solved_challenges.each_with_index do |s, i| 28 | %tr 29 | %td= i + 1 30 | %td= s.challenge.category.name 31 | %td= s.challenge.point_value 32 | %td= s.created_at.strftime("%b %e %y, %R %Z") 33 | 34 | 35 | %div.span5.offset1 36 | = render partial: "users/map", locals: {data: @players} 37 | -------------------------------------------------------------------------------- /app/views/users/show.html.haml: -------------------------------------------------------------------------------- 1 | %div.row 2 | %div.span5 3 | %h3.muted Flags/Min: 4 | = render partial: "events/timeline", locals: {id: "timeline", data: @submitted_flags} 5 | %div.span5.offset1 6 | %h3.muted 7 | -if current_user == @player and not @game.disable_vpn 8 | Connection Information 9 | -else 10 | Affiliation: 11 | %small 12 | =@player.affiliation 13 | %div.row 14 | %div.span5 15 | - if @solved_challenges.size == 0 16 | .zero-items-text No Solved Challenges 17 | - else 18 | %table.table.table-bordered.table-striped 19 | %thead 20 | %tr 21 | %th # 22 | %th Category 23 | %th Points 24 | %th When 25 | %tbody 26 | - @solved_challenges.each_with_index do |s, i| 27 | %tr 28 | %td= i + 1 29 | %td= s.challenge.category.name 30 | %td= s.challenge.point_value 31 | %td= s.created_at.strftime("%b %e %y, %R %Z") 32 | 33 | 34 | -if(current_user == @player and not @game.disable_vpn) 35 | %div.span5.offset1 36 | :markdown 37 | To connect to the jumpbox from Kali download your key using the link below. Unzip the folder, open a terminal in that folder and run 38 | 39 | `openvpn (ovpn file)` 40 | -if @game.irc? 41 | :markdown 42 | If you need help please connect to our IRC channel at [#{@game.irc}](http://webchat.freenode.net/?channels=#{@game.irc[1..-1]}) 43 | -else 44 | %div.span5.offset1 45 | = render partial: "users/map", locals: {data: @players} 46 | 47 | 48 | - if (not current_user.nil? and current_user.admin?) || current_user == @player and not @game.disable_vpn 49 | %h3 VPN Cert 50 | %table.table.table-bordered.table-striped 51 | %tbody 52 | %tr 53 | %td{ colspan: "4" } 54 | %i.icon-plus 55 | = link_to "Download Cert", download_player_path(@player) 56 | 57 | %h3 Achievements 58 | 59 | - if @achievements.size == 0 60 | .zero-items-text No Achievements 61 | - else 62 | %table.table.table-bordered.table-striped 63 | %thead 64 | %tr 65 | %th Name 66 | %th When 67 | %tbody 68 | - @achievements.each do |a| 69 | %tr 70 | %td= a.text 71 | %td= a.updated_at.strftime("%b %e %y, %R %Z") 72 | 73 | - unless current_user.nil? 74 | %h3 Score Adjustments 75 | - if @adjustments.size == 0 76 | .zero-items-text No Adjustments 77 | - else 78 | %table.table.table-bordered.table-striped 79 | %thead 80 | %tr 81 | %th Points 82 | %th When 83 | %th Comments 84 | %tbody 85 | - @adjustments.each do |a| 86 | %tr 87 | - style = "" 88 | - if a.point_value > 0 89 | - style = "color:green;" 90 | - elsif a.point_value < 0 91 | - style = "color:red;" 92 | %td{ style: style } 93 | - if a.point_value > 0 94 | = %[+#{a.point_value}] 95 | - else 96 | = a.point_value 97 | %td= a.created_at.strftime("%b %e %y, %R %Z") 98 | %td= a.text 99 | -------------------------------------------------------------------------------- /apt-packages: -------------------------------------------------------------------------------- 1 | unison -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 4 | load Gem.bin_path('bundler', 'bundle') 5 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | APP_PATH = File.expand_path('../../config/application', __FILE__) 4 | require_relative '../config/boot' 5 | require 'rails/commands' 6 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | require_relative '../config/boot' 4 | require 'rake' 5 | Rake.application.run 6 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | require 'pathname' 4 | 5 | # path to your application root. 6 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 7 | 8 | Dir.chdir APP_ROOT do 9 | # This script is a starting point to setup your application. 10 | # Add necessary setup steps to this file: 11 | 12 | puts '== Installing dependencies ==' 13 | system 'gem install bundler --conservative' 14 | system 'bundle check || bundle install' 15 | 16 | # puts "\n== Copying sample files ==" 17 | # unless File.exist?("config/database.yml") 18 | # system "cp config/database.yml.sample config/database.yml" 19 | # end 20 | 21 | puts "\n== Preparing database ==" 22 | system 'bin/rake db:setup' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system 'rm -f log/*' 26 | system 'rm -rf tmp/cache' 27 | 28 | puts "\n== Restarting application server ==" 29 | system 'touch tmp/restart.txt' 30 | end 31 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # This file is used by Rack-based servers to start the application. 3 | 4 | require ::File.expand_path('../config/environment', __FILE__) 5 | run Scoreboard::Application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require File.expand_path('../boot', __FILE__) 3 | 4 | require 'rails/all' 5 | 6 | # If you have a Gemfile, require the default gems, the ones in the 7 | # current environment and also include :assets gems if in development 8 | # or test environments. 9 | Bundler.require(*Rails.groups) if defined?(Bundler) 10 | 11 | module Scoreboard 12 | class Application < Rails::Application 13 | # Settings in config/environments/* take precedence over those specified here. 14 | # Application configuration should go into files in config/initializers 15 | # -- all .rb files in that directory are automatically loaded. 16 | 17 | # Custom directories with classes and modules you want to be autoloadable. 18 | # config.autoload_paths += %W(#{config.root}/extras) 19 | config.autoload_paths += %W(#{config.root}/lib) 20 | config.autoload_paths += Dir["#{config.root}/lib/**/"] 21 | 22 | # Only load the plugins named here, in the order given (default is alphabetical). 23 | # :all can be used as a placeholder for all plugins not explicitly named. 24 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 25 | 26 | # Activate observers that should always be running. 27 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 28 | 29 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 30 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 31 | config.time_zone = 'Eastern Time (US & Canada)' 32 | 33 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 34 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 35 | # config.i18n.default_locale = :de 36 | 37 | # Configure sensitive parameters which will be filtered from the log file. 38 | config.filter_parameters += [:password, :flag, :submitted_flag, :key] 39 | 40 | # Enable the asset pipeline 41 | config.assets.enabled = true 42 | 43 | config.generators do |g| 44 | g.test_framework :rspec, views: false, fixture: true 45 | g.fixture_replacement :factory_girl, dir: 'spec/factories' 46 | g.template_engine :haml 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'rubygems' 3 | 4 | # Set up gems listed in the Gemfile. 5 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 6 | 7 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 8 | -------------------------------------------------------------------------------- /config/brakeman.ignore: -------------------------------------------------------------------------------- 1 | { 2 | "ignored_warnings": [ 3 | { 4 | "warning_type": "Unscoped Find", 5 | "warning_code": 82, 6 | "fingerprint": "0123ccee12a0f682ace6e9cbe8c581141658b928232b6ed5f9067ff2fa2e2d5c", 7 | "message": "Unscoped call to Player#find", 8 | "file": "app/controllers/users_controller.rb", 9 | "line": 12, 10 | "link": "http://brakemanscanner.org/docs/warning_types/unscoped_find/", 11 | "code": "Player.find(params[:id])", 12 | "render_path": null, 13 | "location": { 14 | "type": "method", 15 | "class": "UsersController", 16 | "method": "show" 17 | }, 18 | "user_input": "params[:id]", 19 | "confidence": "Weak", 20 | "note": "" 21 | }, 22 | { 23 | "warning_type": "Cross Site Scripting", 24 | "warning_code": 2, 25 | "fingerprint": "15d2fa31f6a4a43c789b55fc2adb9322e57323aa9568f3b4cefc5778d58f1105", 26 | "message": "Unescaped model attribute", 27 | "file": "app/views/challenges/show.html.haml", 28 | "line": 8, 29 | "link": "http://brakemanscanner.org/docs/warning_types/cross_site_scripting", 30 | "code": "BlueCloth.new(Game.instance.challenges.find(params[:id]).description).to_html", 31 | "render_path": [{"type":"controller","class":"ChallengesController","method":"show","line":28,"file":"app/controllers/challenges_controller.rb"}], 32 | "location": { 33 | "type": "template", 34 | "template": "challenges/show" 35 | }, 36 | "user_input": "Game.instance.challenges", 37 | "confidence": "Weak", 38 | "note": "" 39 | }, 40 | { 41 | "warning_type": "Cross Site Scripting", 42 | "warning_code": 2, 43 | "fingerprint": "5799145c15a00f79722836329235f7344887f1499fb311b83005834647f732b8", 44 | "message": "Unescaped model attribute", 45 | "file": "app/views/layouts/application.html.haml", 46 | "line": 29, 47 | "link": "http://brakemanscanner.org/docs/warning_types/cross_site_scripting", 48 | "code": "Game.instance.challenges.find(params[:id]).name.downcase", 49 | "render_path": [{"type":"controller","class":"ChallengesController","method":"show","line":28,"file":"app/controllers/challenges_controller.rb"}], 50 | "location": { 51 | "type": "template", 52 | "template": "layouts/application" 53 | }, 54 | "user_input": null, 55 | "confidence": "High", 56 | "note": "" 57 | }, 58 | { 59 | "warning_type": "Cross Site Scripting", 60 | "warning_code": 2, 61 | "fingerprint": "60fbf2fb66c271bdb85f4719ba2f59d516b08df9a464e632f9fa82dc645064d0", 62 | "message": "Unescaped model attribute", 63 | "file": "app/views/games/summary.html.haml", 64 | "line": 3, 65 | "link": "http://brakemanscanner.org/docs/warning_types/cross_site_scripting", 66 | "code": "((Game.instance.stop - Game.instance.start) / 1.hour).round", 67 | "render_path": [{"type":"controller","class":"GamesController","method":"summary","line":44,"file":"app/controllers/games_controller.rb"}], 68 | "location": { 69 | "type": "template", 70 | "template": "games/summary" 71 | }, 72 | "user_input": null, 73 | "confidence": "Medium", 74 | "note": "" 75 | }, 76 | { 77 | "warning_type": "Cross Site Scripting", 78 | "warning_code": 2, 79 | "fingerprint": "6e6f689c09417a3847317ccd7c7fa9ab8b79b602e021eac3bc7c2de7dba2d391", 80 | "message": "Unescaped model attribute", 81 | "file": "app/views/users/show.html.haml", 82 | "line": 42, 83 | "link": "http://brakemanscanner.org/docs/warning_types/cross_site_scripting", 84 | "code": "Game.instance.irc", 85 | "render_path": [{"type":"controller","class":"UsersController","method":"show","line":24,"file":"app/controllers/users_controller.rb"}], 86 | "location": { 87 | "type": "template", 88 | "template": "users/show" 89 | }, 90 | "user_input": null, 91 | "confidence": "Medium", 92 | "note": "" 93 | }, 94 | { 95 | "warning_type": "Command Injection", 96 | "warning_code": 14, 97 | "fingerprint": "f28d61ef51eb52749cf454e83fde9975764bb12e5ad0e64c8ef762505ff29796", 98 | "message": "Possible command injection", 99 | "file": "app/models/player.rb", 100 | "line": 43, 101 | "link": "http://brakemanscanner.org/docs/warning_types/command_injection/", 102 | "code": "system(\"touch /opt/keys/#{key_file_name}\")", 103 | "render_path": null, 104 | "location": { 105 | "type": "method", 106 | "class": "Player", 107 | "method": "touch_file" 108 | }, 109 | "user_input": "key_file_name", 110 | "confidence": "Medium", 111 | "note": "" 112 | } 113 | ], 114 | "updated": "2016-06-28 13:31:26 -0400", 115 | "brakeman_version": "3.3.2" 116 | } 117 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: postgresql 8 | database: scoreboard_development 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: postgresql 17 | database: scoreboard_test 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: postgresql 23 | database: scoreboard_production 24 | pool: 5 25 | timeout: 5000 26 | username: postgres 27 | password: 28 | host: db 29 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Load the rails application 3 | require File.expand_path('../application', __FILE__) 4 | 5 | # Initialize the rails application 6 | Scoreboard::Application.initialize! 7 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Scoreboard::Application.configure do 3 | # Settings specified here will take precedence over those in config/application.rb 4 | 5 | # In the development environment your application's code is reloaded on 6 | # every request. This slows down response time but is perfect for development 7 | # since you don't have to restart the web server when you make code changes. 8 | config.cache_classes = false 9 | 10 | # Add this to your production environment with the proper jumpbox ip specified. 11 | config.jumpbox = { ip: '' } 12 | 13 | # Log error messages when you accidentally call methods on nil. 14 | config.whiny_nils = true 15 | 16 | # Show full error reports and disable caching 17 | config.consider_all_requests_local = true 18 | config.action_controller.perform_caching = false 19 | 20 | # Don't care if the mailer can't send 21 | config.action_mailer.raise_delivery_errors = false 22 | 23 | # Print deprecation notices to the Rails logger 24 | config.active_support.deprecation = :log 25 | 26 | # Only use best-standards-support built into browsers 27 | config.action_dispatch.best_standards_support = :builtin 28 | 29 | # Do not compress assets 30 | config.assets.compress = false 31 | # when true, eager loads all registered config.eager_load_namespaces. 32 | # This includes your application, engines, Rails frameworks and any other registered namespace. 33 | config.eager_load = false 34 | 35 | config.after_initialize do 36 | Bullet.enable = true 37 | Bullet.bullet_logger = true 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Scoreboard::Application.configure do 3 | # Settings specified here will take precedence over those in config/application.rb 4 | 5 | # Code is not reloaded between requests 6 | config.cache_classes = true 7 | 8 | config.jumpbox = { ip: ENV['OPENVPN_JUMPBOX_IP'] } 9 | 10 | # Full error reports are disabled and caching is turned on 11 | config.consider_all_requests_local = false 12 | config.action_controller.perform_caching = true 13 | 14 | # Disable serving static files from the `/public` folder by default since 15 | # Apache or NGINX already handles this. 16 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 17 | 18 | # Compress JavaScripts and CSS. 19 | config.assets.js_compressor = :uglifier 20 | # config.assets.css_compressor = :sass 21 | 22 | # Specifies the header that your server uses for sending files 23 | # (comment out if your front-end server doesn't support this) 24 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" 25 | 26 | # Use 'X-Accel-Redirect' for nginx 27 | config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' 28 | 29 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 30 | # config.force_ssl = true 31 | 32 | # See everything in the log (default is :info) 33 | config.log_level = :info 34 | 35 | # Do not fallback to assets pipeline if a precompiled asset is missed. 36 | config.assets.compile = false 37 | 38 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 39 | # yet still be able to expire them through the digest params. 40 | config.assets.digest = true 41 | 42 | # Use a different logger for distributed setups 43 | # config.logger = SyslogLogger.new 44 | 45 | # Use a different cache store in production 46 | # config.cache_store = :mem_cache_store 47 | 48 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 49 | # config.action_controller.asset_host = "http://assets.example.com" 50 | 51 | # Precompile additional assets (application.js, application.css, 52 | # and all non-JS/CSS are already added) 53 | # config.assets.precompile += %w( search.js ) 54 | 55 | # Disable delivery errors, bad email addresses will be ignored 56 | # config.action_mailer.raise_delivery_errors = false 57 | 58 | # Enable threaded mode 59 | # config.threadsafe! 60 | 61 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 62 | # the I18n.default_locale when a translation can not be found) 63 | config.i18n.fallbacks = true 64 | 65 | # Send deprecation notices to registered listeners 66 | config.active_support.deprecation = :notify 67 | # when true, eager loads all registered config.eager_load_namespaces. 68 | # This includes your application, engines, Rails frameworks and any other registered namespace. 69 | config.eager_load = true 70 | end 71 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Scoreboard::Application.configure do 3 | # Settings specified here will take precedence over those in config/application.rb 4 | 5 | # The test environment is used exclusively to run your application's 6 | # test suite. You never need to work with it otherwise. Remember that 7 | # your test database is "scratch space" for the test suite and is wiped 8 | # and recreated between test runs. Don't rely on the data there! 9 | config.cache_classes = true 10 | 11 | # Configure static asset server for tests with Cache-Control for performance 12 | config.serve_static_files = true 13 | config.static_cache_control = 'public, max-age=3600' 14 | 15 | # Log error messages when you accidentally call methods on nil 16 | config.whiny_nils = true 17 | 18 | # Show full error reports and disable caching 19 | config.consider_all_requests_local = true 20 | config.action_controller.perform_caching = false 21 | 22 | # Raise exceptions instead of rendering exception templates 23 | config.action_dispatch.show_exceptions = false 24 | 25 | # Disable request forgery protection in test environment 26 | config.action_controller.allow_forgery_protection = false 27 | 28 | # Tell Action Mailer not to deliver emails to the real world. 29 | # The :test delivery method accumulates sent emails in the 30 | # ActionMailer::Base.deliveries array. 31 | config.action_mailer.delivery_method = :test 32 | 33 | # Use SQL instead of Active Record's schema dumper when creating the test database. 34 | # This is necessary if your schema can't be completely dumped by the schema dumper, 35 | # like if you have constraints or database-specific column types 36 | # config.active_record.schema_format = :sql 37 | 38 | # Randomize the order test cases are executed. 39 | config.active_support.test_order = :random 40 | 41 | # Print deprecation notices to the stderr 42 | config.active_support.deprecation = :stderr 43 | # when true, eager loads all registered config.eager_load_namespaces. 44 | # This includes your application, engines, Rails frameworks and any other registered namespace. 45 | config.eager_load = false 46 | end 47 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but 4 | # don't wish to see in your backtraces. 5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 6 | 7 | # You can also remove all the silencers if you're trying to debug a problem that might stem 8 | # from framework code. 9 | # Rails.backtrace_cleaner.remove_silencers! 10 | -------------------------------------------------------------------------------- /config/initializers/devise.rb: -------------------------------------------------------------------------------- 1 | # Use this hook to configure devise mailer, warden hooks and so forth. The first 2 | # four configuration values can also be set straight in your models. 3 | Devise.setup do |config| 4 | # ==> Mailer Configuration 5 | # Configure the e-mail address which will be shown in DeviseMailer. 6 | config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com" 7 | 8 | # Configure the class responsible to send e-mails. 9 | # config.mailer = "Devise::Mailer" 10 | 11 | # ==> ORM configuration 12 | # Load and configure the ORM. Supports :active_record (default) and 13 | # :mongoid (bson_ext recommended) by default. Other ORMs may be 14 | # available as additional gems. 15 | require 'devise/orm/active_record' 16 | 17 | # ==> Configuration for any authentication mechanism 18 | # Configure which keys are used when authenticating a user. The default is 19 | # just :email. You can configure it to use [:username, :subdomain], so for 20 | # authenticating a user, both parameters are required. Remember that those 21 | # parameters are used only when authenticating and not when retrieving from 22 | # session. If you need permissions, you should implement that in a before filter. 23 | # You can also supply a hash where the value is a boolean determining whether 24 | # or not authentication should be aborted when the value is not present. 25 | config.authentication_keys = [ :email ] 26 | 27 | # Configure parameters from the request object used for authentication. Each entry 28 | # given should be a request method and it will automatically be passed to the 29 | # find_for_authentication method and considered in your model lookup. For instance, 30 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. 31 | # The same considerations mentioned for authentication_keys also apply to request_keys. 32 | # config.request_keys = [] 33 | 34 | # Configure which authentication keys should be case-insensitive. 35 | # These keys will be downcased upon creating or modifying a user and when used 36 | # to authenticate or find a user. Default is :email. 37 | config.case_insensitive_keys = [ :email ] 38 | 39 | # Configure which authentication keys should have whitespace stripped. 40 | # These keys will have whitespace before and after removed upon creating or 41 | # modifying a user and when used to authenticate or find a user. Default is :email. 42 | config.strip_whitespace_keys = [ :email ] 43 | 44 | # Tell if authentication through request.params is enabled. True by default. 45 | # config.params_authenticatable = true 46 | 47 | # Tell if authentication through HTTP Basic Auth is enabled. False by default. 48 | config.http_authenticatable = true 49 | 50 | # If http headers should be returned for AJAX requests. True by default. 51 | # config.http_authenticatable_on_xhr = true 52 | 53 | # The realm used in Http Basic Authentication. "Application" by default. 54 | # config.http_authentication_realm = "Application" 55 | 56 | # It will change confirmation, password recovery and other workflows 57 | # to behave the same regardless if the e-mail provided was right or wrong. 58 | # Does not affect registerable. 59 | # config.paranoid = true 60 | 61 | # ==> Configuration for :database_authenticatable 62 | # For bcrypt, this is the cost for hashing the password and defaults to 10. If 63 | # using other encryptors, it sets how many times you want the password re-encrypted. 64 | config.stretches = 10 65 | 66 | # Setup a pepper to generate the encrypted password. 67 | # config.pepper = "6d79aa820012805b338ae893685d8c163c0812c48fbaa6f3e12829234485d4ed9b8cc5d6e5a82489ae078318995abab27e4389942fdbc088644805ade62533a3" 68 | 69 | # ==> Configuration for :confirmable 70 | # The time you want to give your user to confirm his account. During this time 71 | # he will be able to access your application without confirming. Default is 0.days 72 | # When confirm_within is zero, the user won't be able to sign in without confirming. 73 | # You can use this to let your user access some features of your application 74 | # without confirming the account, but blocking it after a certain period 75 | # (ie 2 days). 76 | # config.confirm_within = 2.days 77 | 78 | # Defines which key will be used when confirming an account 79 | # config.confirmation_keys = [ :email ] 80 | 81 | # ==> Configuration for :rememberable 82 | # The time the user will be remembered without asking for credentials again. 83 | # config.remember_for = 2.weeks 84 | 85 | # If true, a valid remember token can be re-used between multiple browsers. 86 | # config.remember_across_browsers = true 87 | 88 | # If true, extends the user's remember period when remembered via cookie. 89 | # config.extend_remember_period = false 90 | 91 | # If true, uses the password salt as remember token. This should be turned 92 | # to false if you are not using database authenticatable. 93 | # changed based on https://github.com/plataformatec/devise/issues/1638 94 | # config.use_salt_as_remember_token = true 95 | 96 | # Options to be passed to the created cookie. For instance, you can set 97 | # :secure => true in order to force SSL only cookies. 98 | # config.cookie_options = {} 99 | 100 | config.secret_key = Rails.application.secrets.secret_key_base 101 | 102 | # ==> Configuration for :validatable 103 | # Range for password length. Default is 6..128. 104 | config.password_length = 10..128 105 | 106 | # Regex to use to validate the email address 107 | # config.email_regexp = /\A([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})\z/i 108 | config.email_regexp = /\w*/ 109 | 110 | # ==> Configuration for :timeoutable 111 | # The time you want to timeout the user session without activity. After this 112 | # time the user will be asked for credentials again. Default is 30 minutes. 113 | # config.timeout_in = 30.minutes 114 | 115 | # ==> Configuration for :lockable 116 | # Defines which strategy will be used to lock an account. 117 | # :failed_attempts = Locks an account after a number of failed attempts to sign in. 118 | # :none = No lock strategy. You should handle locking by yourself. 119 | config.lock_strategy = :failed_attempts 120 | 121 | # Defines which key will be used when locking and unlocking an account 122 | # config.unlock_keys = [ :email ] 123 | 124 | # Defines which strategy will be used to unlock an account. 125 | # :email = Sends an unlock link to the user email 126 | # :time = Re-enables login after a certain amount of time (see :unlock_in below) 127 | # :both = Enables both strategies 128 | # :none = No unlock strategy. You should handle unlocking by yourself. 129 | config.unlock_strategy = :time 130 | 131 | # Number of authentication tries before locking an account if lock_strategy 132 | # is failed attempts. 133 | config.maximum_attempts = 20 134 | 135 | # Time interval to unlock the account if :time is enabled as unlock_strategy. 136 | config.unlock_in = 30.minutes 137 | 138 | # ==> Configuration for :recoverable 139 | # 140 | # Defines which key will be used when recovering the password for an account 141 | # config.reset_password_keys = [ :email ] 142 | 143 | # Time interval you can reset your password with a reset password key. 144 | # Don't put a too small interval or your users won't have the time to 145 | # change their passwords. 146 | config.reset_password_within = 6.hours 147 | 148 | # ==> Configuration for :encryptable 149 | # Allow you to use another encryption algorithm besides bcrypt (default). You can use 150 | # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1, 151 | # :authlogic_sha512 (then you should set stretches above to 20 for default behavior) 152 | # and :restful_authentication_sha1 (then you should set stretches to 10, and copy 153 | # REST_AUTH_SITE_KEY to pepper) 154 | # config.encryptor = :sha512 155 | 156 | # ==> Configuration for :token_authenticatable 157 | # Defines name of the authentication token params key 158 | # config.token_authentication_key = :auth_token 159 | 160 | # If true, authentication through token does not store user in session and needs 161 | # to be supplied on each request. Useful if you are using the token as API token. 162 | # config.stateless_token = false 163 | 164 | # ==> Scopes configuration 165 | # Turn scoped views on. Before rendering "sessions/new", it will first check for 166 | # "users/sessions/new". It's turned off by default because it's slower if you 167 | # are using only default views. 168 | # config.scoped_views = false 169 | 170 | # Configure the default scope given to Warden. By default it's the first 171 | # devise role declared in your routes (usually :user). 172 | # config.default_scope = :user 173 | 174 | # Configure sign_out behavior. 175 | # Sign_out action can be scoped (i.e. /users/sign_out affects only :user scope). 176 | # The default is true, which means any logout action will sign out all active scopes. 177 | # config.sign_out_all_scopes = true 178 | 179 | # ==> Navigation configuration 180 | # Lists the formats that should be treated as navigational. Formats like 181 | # :html, should redirect to the sign in page when the user does not have 182 | # access, but formats like :xml or :json, should return 401. 183 | # 184 | # If you have any extra navigational formats, like :iphone or :mobile, you 185 | # should add them to the navigational formats lists. 186 | # 187 | # The :"*/*" and "*/*" formats below is required to match Internet 188 | # Explorer requests. 189 | # config.navigational_formats = [:"*/*", "*/*", :html] 190 | 191 | # The default HTTP method used to sign out a resource. Default is :delete. 192 | config.sign_out_via = :delete 193 | 194 | # ==> OmniAuth 195 | # Add a new OmniAuth provider. Check the wiki for more information on setting 196 | # up on your models and hooks. 197 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo' 198 | 199 | # ==> Warden configuration 200 | # If you want to use other strategies, that are not supported by Devise, or 201 | # change the failure app, you can configure them inside the config.warden block. 202 | # 203 | # config.warden do |manager| 204 | # manager.failure_app = AnotherApp 205 | # manager.intercept_401 = false 206 | # manager.default_strategies(:scope => :user).unshift :some_external_strategy 207 | # end 208 | end 209 | -------------------------------------------------------------------------------- /config/initializers/geocoder.rb: -------------------------------------------------------------------------------- 1 | Geocoder.configure( 2 | :timeout => 15 3 | ) 4 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/initializers/rails_admin.rb: -------------------------------------------------------------------------------- 1 | RailsAdmin.config do |config| 2 | config.current_user_method { current_user } # auto-generated 3 | 4 | config.label_methods << :email 5 | 6 | config.authorize_with do 7 | redirect_to main_app.root_path unless current_user.try(:admin?) 8 | end 9 | 10 | config.model User do 11 | visible false 12 | end 13 | 14 | config.model FeedItem do 15 | visible false 16 | end 17 | 18 | config.model Admin do 19 | edit do 20 | field :email do 21 | label 'Login' 22 | end 23 | fields :password, :password_confirmation 24 | end 25 | list do 26 | field :id 27 | field :email do 28 | label 'Login' 29 | end 30 | fields :current_sign_in_at, :locked_at 31 | end 32 | end 33 | 34 | config.model Player do 35 | configure :change_password 36 | edit do 37 | field :email do 38 | label 'Login' 39 | end 40 | field :city do 41 | label 'Location' 42 | end 43 | exclude_fields :password, :password_confirmation 44 | field :display_name 45 | field :tags 46 | field :division 47 | field :city 48 | field :affiliation 49 | field :eligible 50 | field :change_password 51 | field :change_password do 52 | label 'Password' 53 | end 54 | end 55 | list do 56 | field :id 57 | field :display_name 58 | field :current_sign_in_at 59 | field :locked_at 60 | field :division 61 | end 62 | create do 63 | field :change_password do 64 | label 'Password' 65 | required :true 66 | end 67 | end 68 | export do 69 | configure :score, :integer do 70 | export_value do 71 | bindings[:object].score 72 | end 73 | end 74 | end 75 | end 76 | 77 | config.model Game do 78 | configure :disable_vpn, :boolean 79 | configure :disable_flags_an_hour_graph, :boolean 80 | list do 81 | fields :name, :start, :stop 82 | end 83 | edit do 84 | fields :name, :start, :stop, :irc 85 | field :disable_vpn do 86 | label 'Hide VPN Cert Download' 87 | end 88 | field :disable_flags_an_hour_graph do 89 | label 'Hide Flags/Hour graph from Game Summary' 90 | end 91 | end 92 | end 93 | 94 | config.model Category do 95 | list do 96 | fields :name, :game 97 | end 98 | edit do 99 | fields :name, :game 100 | end 101 | end 102 | 103 | config.model Challenge do 104 | list do 105 | fields :name, :point_value, :starting_state, :category 106 | end 107 | end 108 | 109 | config.model Flag do 110 | nested do 111 | fields :flag, :api_request, :video_url 112 | end 113 | end 114 | 115 | config.model SolvedChallenge do 116 | list do 117 | fields :player, :challenge, :created_at 118 | end 119 | edit do 120 | fields :player, :challenge, :created_at 121 | end 122 | end 123 | 124 | config.model ScoreAdjustment do 125 | list do 126 | fields :player, :point_value 127 | field :text do 128 | label 'Comments' 129 | end 130 | field :created_at 131 | end 132 | edit do 133 | fields :player, :point_value 134 | field :text do 135 | label 'Comments' 136 | end 137 | end 138 | end 139 | 140 | config.model Achievement do 141 | list do 142 | field :text do 143 | label 'Name' 144 | end 145 | fields :player, :created_at 146 | end 147 | edit do 148 | field :text do 149 | label 'Name' 150 | end 151 | fields :player 152 | end 153 | end 154 | 155 | config.model Message do 156 | edit do 157 | fields :title, :game, :text 158 | end 159 | list do 160 | fields :title, :game, :created_at 161 | end 162 | end 163 | 164 | config.model SubmittedFlag do 165 | list do 166 | fields :player, :challenge, :text, :created_at 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /config/initializers/rails_admin.rb.example: -------------------------------------------------------------------------------- 1 | # RailsAdmin config file. Generated on May 01, 2012 11:11 2 | # See github.com/sferik/rails_admin for more informations 3 | 4 | RailsAdmin.config do |config| 5 | 6 | # If your default_local is different from :en, uncomment the following 2 lines and set your default locale here: 7 | # require 'i18n' 8 | # I18n.default_locale = :de 9 | 10 | config.current_user_method { current_user } # auto-generated 11 | 12 | # If you want to track changes on your models: 13 | # config.audit_with :history, User 14 | 15 | # Or with a PaperTrail: (you need to install it first) 16 | # config.audit_with :paper_trail, User 17 | 18 | # Set the admin name here (optional second array element will appear in a beautiful RailsAdmin red ©) 19 | config.main_app_name = ['Scoreboard', 'Admin'] 20 | # or for a dynamic name: 21 | # config.main_app_name = Proc.new { |controller| [Rails.application.engine_name.titleize, controller.params['action'].titleize] } 22 | 23 | 24 | # ==> Global show view settings 25 | # Display empty fields in show views 26 | # config.compact_show_view = false 27 | 28 | # ==> Global list view settings 29 | # Number of default rows per-page: 30 | # config.default_items_per_page = 20 31 | 32 | # ==> Included models 33 | # Add all excluded models here: 34 | # config.excluded_models = [Achievement, Admin, Category, Challenge, Game, Message, Penalty, Player, SolvedChallenge, SubmittedFlag, User] 35 | 36 | # Add models here if you want to go 'whitelist mode': 37 | # config.included_models = [Achievement, Admin, Category, Challenge, Game, Message, Penalty, Player, SolvedChallenge, SubmittedFlag, User] 38 | 39 | # Application wide tried label methods for models' instances 40 | # config.label_methods << :description # Default is [:name, :title] 41 | 42 | # ==> Global models configuration 43 | # config.models do 44 | # # Configuration here will affect all included models in all scopes, handle with care! 45 | # 46 | # list do 47 | # # Configuration here will affect all included models in list sections (same for show, export, edit, update, create) 48 | # 49 | # fields_of_type :date do 50 | # # Configuration here will affect all date fields, in the list section, for all included models. See README for a comprehensive type list. 51 | # end 52 | # end 53 | # end 54 | # 55 | # ==> Model specific configuration 56 | # Keep in mind that *all* configuration blocks are optional. 57 | # RailsAdmin will try his best to provide the best defaults for each section, for each field. 58 | # Try to override as few things as possible, in the most generic way. Try to avoid setting labels for models and attributes, use ActiveRecord I18n API instead. 59 | # Less code is better code! 60 | # config.model MyModel do 61 | # # Cross-section field configuration 62 | # object_label_method :name # Name of the method called for pretty printing an *instance* of ModelName 63 | # label 'My model' # Name of ModelName (smartly defaults to ActiveRecord's I18n API) 64 | # label_plural 'My models' # Same, plural 65 | # weight -1 # Navigation priority. Bigger is higher. 66 | # parent OtherModel # Set parent model for navigation. MyModel will be nested below. OtherModel will be on first position of the dropdown 67 | # navigation_label # Sets dropdown entry's name in navigation. Only for parents! 68 | # # Section specific configuration: 69 | # list do 70 | # filters [:id, :name] # Array of field names which filters should be shown by default in the table header 71 | # items_per_page 100 # Override default_items_per_page 72 | # sort_by :id # Sort column (default is primary key) 73 | # sort_reverse true # Sort direction (default is true for primary key, last created first) 74 | # # Here goes the fields configuration for the list view 75 | # end 76 | # end 77 | 78 | # Your model's configuration, to help you get started: 79 | 80 | # All fields marked as 'hidden' won't be shown anywhere in the rails_admin unless you mark them as visible. (visible(true)) 81 | 82 | # config.model Achievement do 83 | # # Found associations: 84 | # configure :player, :belongs_to_association # # Found columns: 85 | # configure :id, :integer 86 | # configure :name, :string 87 | # configure :user_id, :integer # Hidden 88 | # configure :created_at, :datetime 89 | # configure :updated_at, :datetime # # Sections: 90 | # list do; end 91 | # export do; end 92 | # show do; end 93 | # edit do; end 94 | # create do; end 95 | # update do; end 96 | # end 97 | # config.model Admin do 98 | # # Found associations: 99 | # # Found columns: 100 | # configure :id, :integer 101 | # configure :email, :string 102 | # configure :password, :password # Hidden 103 | # configure :password_confirmation, :password # Hidden 104 | # configure :sign_in_count, :integer 105 | # configure :current_sign_in_at, :datetime 106 | # configure :last_sign_in_at, :datetime 107 | # configure :current_sign_in_ip, :string 108 | # configure :last_sign_in_ip, :string 109 | # configure :created_at, :datetime 110 | # configure :updated_at, :datetime 111 | # configure :failed_attempts, :integer 112 | # configure :locked_at, :datetime 113 | # configure :type, :string 114 | # configure :game_id, :integer 115 | # configure :messages_stamp, :datetime # # Sections: 116 | # list do; end 117 | # export do; end 118 | # show do; end 119 | # edit do; end 120 | # create do; end 121 | # update do; end 122 | # end 123 | # config.model Category do 124 | # # Found associations: 125 | # configure :game, :belongs_to_association 126 | # configure :challenges, :has_many_association # # Found columns: 127 | # configure :id, :integer 128 | # configure :name, :string 129 | # configure :created_at, :datetime 130 | # configure :updated_at, :datetime 131 | # configure :game_id, :integer # Hidden # # Sections: 132 | # list do; end 133 | # export do; end 134 | # show do; end 135 | # edit do; end 136 | # create do; end 137 | # update do; end 138 | # end 139 | # config.model Challenge do 140 | # # Found associations: 141 | # configure :category, :belongs_to_association # # Found columns: 142 | # configure :id, :integer 143 | # configure :name, :string 144 | # configure :description, :text 145 | # configure :point_value, :integer 146 | # configure :flag, :string 147 | # configure :state, :enum 148 | # configure :created_at, :datetime 149 | # configure :updated_at, :datetime 150 | # configure :category_id, :integer # Hidden 151 | # configure :achievement_name, :string # # Sections: 152 | # list do; end 153 | # export do; end 154 | # show do; end 155 | # edit do; end 156 | # create do; end 157 | # update do; end 158 | # end 159 | # config.model Game do 160 | # # Found associations: 161 | # configure :players, :has_many_association 162 | # configure :categories, :has_many_association 163 | # configure :messages, :has_many_association 164 | # configure :challenges, :has_many_association 165 | # configure :solved_challenges, :has_many_association 166 | # configure :achievements, :has_many_association # # Found columns: 167 | # configure :id, :integer 168 | # configure :name, :string 169 | # configure :start, :datetime 170 | # configure :stop, :datetime 171 | # configure :description, :text 172 | # configure :created_at, :datetime 173 | # configure :updated_at, :datetime # # Sections: 174 | # list do; end 175 | # export do; end 176 | # show do; end 177 | # edit do; end 178 | # create do; end 179 | # update do; end 180 | # end 181 | # config.model Message do 182 | # # Found associations: 183 | # configure :game, :belongs_to_association # # Found columns: 184 | # configure :id, :integer 185 | # configure :user_id, :integer 186 | # configure :game_id, :integer # Hidden 187 | # configure :text, :text 188 | # configure :created_at, :datetime 189 | # configure :updated_at, :datetime 190 | # configure :title, :string # # Sections: 191 | # list do; end 192 | # export do; end 193 | # show do; end 194 | # edit do; end 195 | # create do; end 196 | # update do; end 197 | # end 198 | # config.model Penalty do 199 | # # Found associations: 200 | # configure :player, :belongs_to_association # # Found columns: 201 | # configure :id, :integer 202 | # configure :user_id, :integer # Hidden 203 | # configure :point_value, :integer 204 | # configure :comments, :text 205 | # configure :created_at, :datetime 206 | # configure :updated_at, :datetime # # Sections: 207 | # list do; end 208 | # export do; end 209 | # show do; end 210 | # edit do; end 211 | # create do; end 212 | # update do; end 213 | # end 214 | # config.model Player do 215 | # # Found associations: 216 | # configure :game, :belongs_to_association 217 | # configure :solved_challenges, :has_many_association 218 | # configure :achievements, :has_many_association 219 | # configure :penalties, :has_many_association # # Found columns: 220 | # configure :id, :integer 221 | # configure :email, :string 222 | # configure :password, :password # Hidden 223 | # configure :password_confirmation, :password # Hidden 224 | # configure :sign_in_count, :integer 225 | # configure :current_sign_in_at, :datetime 226 | # configure :last_sign_in_at, :datetime 227 | # configure :current_sign_in_ip, :string 228 | # configure :last_sign_in_ip, :string 229 | # configure :created_at, :datetime 230 | # configure :updated_at, :datetime 231 | # configure :failed_attempts, :integer 232 | # configure :locked_at, :datetime 233 | # configure :type, :string 234 | # configure :game_id, :integer # Hidden 235 | # configure :messages_stamp, :datetime # # Sections: 236 | # list do; end 237 | # export do; end 238 | # show do; end 239 | # edit do; end 240 | # create do; end 241 | # update do; end 242 | # end 243 | # config.model SolvedChallenge do 244 | # # Found associations: 245 | # configure :player, :belongs_to_association 246 | # configure :challenge, :belongs_to_association # # Found columns: 247 | # configure :id, :integer 248 | # configure :user_id, :integer # Hidden 249 | # configure :challenge_id, :integer # Hidden 250 | # configure :created_at, :datetime 251 | # configure :updated_at, :datetime # # Sections: 252 | # list do; end 253 | # export do; end 254 | # show do; end 255 | # edit do; end 256 | # create do; end 257 | # update do; end 258 | # end 259 | # config.model SubmittedFlag do 260 | # # Found associations: 261 | # configure :player, :belongs_to_association 262 | # configure :challenge, :belongs_to_association # # Found columns: 263 | # configure :id, :integer 264 | # configure :user_id, :integer # Hidden 265 | # configure :challenge_id, :integer # Hidden 266 | # configure :text, :string 267 | # configure :created_at, :datetime 268 | # configure :updated_at, :datetime # # Sections: 269 | # list do; end 270 | # export do; end 271 | # show do; end 272 | # edit do; end 273 | # create do; end 274 | # update do; end 275 | # end 276 | # config.model User do 277 | # # Found associations: 278 | # # Found columns: 279 | # configure :id, :integer 280 | # configure :email, :string 281 | # configure :password, :password # Hidden 282 | # configure :password_confirmation, :password # Hidden 283 | # configure :sign_in_count, :integer 284 | # configure :current_sign_in_at, :datetime 285 | # configure :last_sign_in_at, :datetime 286 | # configure :current_sign_in_ip, :string 287 | # configure :last_sign_in_ip, :string 288 | # configure :created_at, :datetime 289 | # configure :updated_at, :datetime 290 | # configure :failed_attempts, :integer 291 | # configure :locked_at, :datetime 292 | # configure :type, :string 293 | # configure :game_id, :integer 294 | # configure :messages_stamp, :datetime # # Sections: 295 | # list do; end 296 | # export do; end 297 | # show do; end 298 | # edit do; end 299 | # create do; end 300 | # update do; end 301 | # end 302 | end 303 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Scoreboard::Application.config.session_store :cookie_store, key: '_scoreboard_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Scoreboard::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActionController::Base.wrap_parameters format: [:json] 8 | 9 | # Disable root element in JSON by default. 10 | ActiveRecord::Base.include_root_in_json = false if defined?(ActiveRecord) 11 | -------------------------------------------------------------------------------- /config/initializers/wrong_flag_messages.rb: -------------------------------------------------------------------------------- 1 | Rails.configuration.wrong_flag_messages = [ 2 | 'Really? Is that all you got?', 3 | "You can't be serious", 4 | 'Incorrect flag', 5 | 'Try again.', 6 | 'lulz', 7 | 'Yeah...no', 8 | "This is not the flag you're looking for *waves hand*", 9 | "Ok now you're just guessing." 10 | ] 11 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at http://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | errors: 5 | messages: 6 | expired: "has expired, please request a new one" 7 | not_found: "not found" 8 | already_confirmed: "was already confirmed, please try signing in" 9 | not_locked: "was not locked" 10 | not_saved: 11 | one: "1 error prohibited this %{resource} from being saved:" 12 | other: "%{count} errors prohibited this %{resource} from being saved:" 13 | 14 | devise: 15 | failure: 16 | already_authenticated: 'You are already signed in.' 17 | unauthenticated: 'You need to sign in or sign up before continuing.' 18 | unconfirmed: 'You have to confirm your account before continuing.' 19 | locked: 'Your account is locked.' 20 | invalid: 'Invalid email or password.' 21 | invalid_token: 'Invalid authentication token.' 22 | timeout: 'Your session expired, please sign in again to continue.' 23 | inactive: 'Your account was not activated yet.' 24 | sessions: 25 | signed_in: 'Signed in successfully.' 26 | signed_out: 'Signed out successfully.' 27 | passwords: 28 | send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.' 29 | updated: 'Your password was changed successfully. You are now signed in.' 30 | send_paranoid_instructions: "If your e-mail exists on our database, you will receive a password recovery link on your e-mail" 31 | confirmations: 32 | send_instructions: 'You will receive an email with instructions about how to confirm your account in a few minutes.' 33 | send_paranoid_instructions: 'If your e-mail exists on our database, you will receive an email with instructions about how to confirm your account in a few minutes.' 34 | confirmed: 'Your account was successfully confirmed. You are now signed in.' 35 | registrations: 36 | signed_up: 'Welcome! You have signed up successfully.' 37 | signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.' 38 | signed_up_but_inactive: 'You have signed up successfully. However, we could not sign you in because your account is not yet activated.' 39 | signed_up_but_locked: 'You have signed up successfully. However, we could not sign you in because your account is locked.' 40 | updated: 'You updated your account successfully.' 41 | destroyed: 'Bye! Your account was successfully cancelled. We hope to see you again soon.' 42 | unlocks: 43 | send_instructions: 'You will receive an email with instructions about how to unlock your account in a few minutes.' 44 | unlocked: 'Your account was successfully unlocked. You are now signed in.' 45 | send_paranoid_instructions: 'If your account exists, you will receive an email with instructions about how to unlock it in a few minutes.' 46 | omniauth_callbacks: 47 | success: 'Successfully authorized from %{kind} account.' 48 | failure: 'Could not authorize you from %{kind} because "%{reason}".' 49 | mailer: 50 | confirmation_instructions: 51 | subject: 'Confirmation instructions' 52 | reset_password_instructions: 53 | subject: 'Reset password instructions' 54 | unlock_instructions: 55 | subject: 'Unlock Instructions' 56 | -------------------------------------------------------------------------------- /config/locales/scoreboard.en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | game: 6 | closed: 'The game is currently closed.' 7 | too_many: 'You may not create more than one game.' 8 | date_mismatch: 'The start date must be before the end date.' 9 | flag: 10 | accepted: 'Flag accepted!' 11 | api: 12 | http_error: 'An HTTP error occured. No actions were performed in Cyberville, but points were awarded.' 13 | challenge: 14 | not_open: 'must be open.' 15 | game_not_open: 'The game must be open.' 16 | already_solved: 'This player has already solved this challenge.' 17 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Scoreboard::Application.routes.draw do 3 | # admin 4 | mount RailsAdmin::Engine => '/admin', :as => 'rails_admin' 5 | 6 | # devise 7 | devise_for :users, controllers: { sessions: :sessions } 8 | devise_scope :user do 9 | get 'signin', to: 'devise/sessions#new' 10 | get 'signout', to: 'devise/sessions#destroy' 11 | end 12 | 13 | # game 14 | resources :challenges, only: [:index, :show] do 15 | member do 16 | post 'submit_flag' 17 | end 18 | end 19 | resources :achievements, only: [:index] 20 | resources :messages, only: [:index] 21 | resources :users, only: [:index, :show], as: :players do 22 | resources :keys, except: [:index, :show] 23 | member do 24 | get 'download' 25 | end 26 | end 27 | get 'summary' => 'games#summary' 28 | 29 | # root 30 | root to: 'games#show' 31 | end 32 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 3bde0b70db68b1ccb8aec33f0f1e8ca7e895f1c77d6007874394441330580097810e564b35ce6c26de78c4cfabeb79886acdf6f0ac3d7a39300175033444d3f4 15 | 16 | test: 17 | secret_key_base: 34b5e77b45b87537a504a1ec84de7fea04faf1204f066aa56d531d34b39d2f7c783fcb42c2e505373450b561574e84d14a318fab1c245ad1c4678b70fb6e79eb 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> -------------------------------------------------------------------------------- /db/migrate/20110816191149_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseCreateUsers < ActiveRecord::Migration 2 | def self.up 3 | create_table(:users) do |t| 4 | t.string :email, :null => false, :default => "" 5 | t.string :encrypted_password, :null => false, :default => "" 6 | t.integer :sign_in_count, :default => 0 7 | t.datetime :current_sign_in_at 8 | t.datetime :last_sign_in_at 9 | t.string :current_sign_in_ip 10 | t.string :last_sign_in_ip 11 | t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts 12 | t.string :unlock_token # Only if unlock strategy is :email or :both 13 | t.datetime :locked_at 14 | 15 | t.timestamps 16 | 17 | # t.encryptable 18 | # t.confirmable 19 | # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both 20 | # t.token_authenticatable 21 | end 22 | 23 | add_index :users, :email, :unique => true 24 | # add_index :users, :confirmation_token, :unique => true 25 | # add_index :users, :unlock_token, :unique => true 26 | # add_index :users, :authentication_token, :unique => true 27 | end 28 | 29 | def self.down 30 | drop_table :users 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /db/migrate/20110816191206_create_rails_admin_histories_table.rb: -------------------------------------------------------------------------------- 1 | class CreateRailsAdminHistoriesTable < ActiveRecord::Migration 2 | def self.up 3 | create_table :rails_admin_histories do |t| 4 | t.string :message # title, name, or object_id 5 | t.string :username 6 | t.integer :item 7 | t.string :table 8 | t.integer :month, :limit => 2 9 | t.integer :year, :limit => 5 10 | t.timestamps 11 | end 12 | add_index(:rails_admin_histories, [:item, :table, :month, :year], :name => 'index_rails_admin_histories' ) 13 | end 14 | 15 | def self.down 16 | drop_table :rails_admin_histories 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /db/migrate/20110816192037_create_games.rb: -------------------------------------------------------------------------------- 1 | class CreateGames < ActiveRecord::Migration 2 | def change 3 | create_table :games do |t| 4 | t.string :name 5 | t.datetime :start 6 | t.datetime :stop 7 | t.text :description 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20110817154811_create_challenges.rb: -------------------------------------------------------------------------------- 1 | class CreateChallenges < ActiveRecord::Migration 2 | def change 3 | create_table :challenges do |t| 4 | t.string :name 5 | t.text :description 6 | t.integer :point_value 7 | t.string :flag 8 | t.string :state 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20110817154849_create_categories.rb: -------------------------------------------------------------------------------- 1 | class CreateCategories < ActiveRecord::Migration 2 | def change 3 | create_table :categories do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20110817173940_add_category_id_to_challenge.rb: -------------------------------------------------------------------------------- 1 | class AddCategoryIdToChallenge < ActiveRecord::Migration 2 | def change 3 | add_column :challenges, :category_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20110817174121_add_game_id_to_category.rb: -------------------------------------------------------------------------------- 1 | class AddGameIdToCategory < ActiveRecord::Migration 2 | def change 3 | add_column :categories, :game_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20110817175559_add_type_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddTypeToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :type, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20110817191639_add_game_id_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddGameIdToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :game_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120315193309_add_achievement_name_to_challenge.rb: -------------------------------------------------------------------------------- 1 | class AddAchievementNameToChallenge < ActiveRecord::Migration 2 | def change 3 | add_column :challenges, :achievement_name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120424134356_create_messages.rb: -------------------------------------------------------------------------------- 1 | class CreateMessages < ActiveRecord::Migration 2 | def change 3 | create_table :messages do |t| 4 | t.integer :user_id 5 | t.integer :game_id 6 | t.text :text 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20120424140054_add_title_to_messages.rb: -------------------------------------------------------------------------------- 1 | class AddTitleToMessages < ActiveRecord::Migration 2 | def change 3 | add_column :messages, :title, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120424180652_create_submitted_flags.rb: -------------------------------------------------------------------------------- 1 | class CreateSubmittedFlags < ActiveRecord::Migration 2 | def change 3 | create_table :submitted_flags do |t| 4 | t.integer :user_id 5 | t.integer :challenge_id 6 | t.string :text 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20120426145021_add_messages_stamp_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddMessagesStampToUser < ActiveRecord::Migration 2 | def change 3 | add_column :users, :messages_stamp, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120501154332_create_feed_items.rb: -------------------------------------------------------------------------------- 1 | class CreateFeedItems < ActiveRecord::Migration 2 | def change 3 | create_table :feed_items do |t| 4 | t.integer :user_id 5 | t.string :type 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20120501165852_add_challenge_id_to_feed_item.rb: -------------------------------------------------------------------------------- 1 | class AddChallengeIdToFeedItem < ActiveRecord::Migration 2 | def change 3 | add_column :feed_items, :challenge_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120501171043_add_text_to_feed_item.rb: -------------------------------------------------------------------------------- 1 | class AddTextToFeedItem < ActiveRecord::Migration 2 | def change 3 | add_column :feed_items, :text, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120508213250_add_point_value_to_feed_item.rb: -------------------------------------------------------------------------------- 1 | class AddPointValueToFeedItem < ActiveRecord::Migration 2 | def change 3 | add_column :feed_items, :point_value, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120518181822_add_tags_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddTagsToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :tags, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120522161744_add_reset_password_sent_at_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddResetPasswordSentAtToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :reset_password_sent_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120626161246_create_keys.rb: -------------------------------------------------------------------------------- 1 | class CreateKeys < ActiveRecord::Migration 2 | def change 3 | create_table :keys do |t| 4 | t.string :name 5 | t.text :key 6 | t.integer :user_id 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20120627164138_add_display_name_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddDisplayNameToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :display_name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20120706171912_add_tos_to_game.rb: -------------------------------------------------------------------------------- 1 | class AddTosToGame < ActiveRecord::Migration 2 | def change 3 | add_column :games, :tos, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20130507195030_add_city_to_players.rb: -------------------------------------------------------------------------------- 1 | class AddCityToPlayers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :city, :string 4 | add_column :users, :latitude, :float 5 | add_column :users, :longitude, :float 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20130515183428_add_irc_to_game.rb: -------------------------------------------------------------------------------- 1 | class AddIrcToGame < ActiveRecord::Migration 2 | def change 3 | add_column :games, :irc, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20130628164632_change_affiliation_to_string.rb: -------------------------------------------------------------------------------- 1 | class ChangeAffiliationToString < ActiveRecord::Migration 2 | def change 3 | add_column :users, :affiliation, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150812172020_remove_flag_from_challenge.rb: -------------------------------------------------------------------------------- 1 | class RemoveFlagFromChallenge < ActiveRecord::Migration 2 | def change 3 | remove_column :challenges, :flag, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150812172928_create_flags.rb: -------------------------------------------------------------------------------- 1 | class CreateFlags < ActiveRecord::Migration 2 | def change 3 | create_table :flags do |t| 4 | t.references :challenge, index: true, foreign_key: true 5 | t.string :flag 6 | t.string :api_request 7 | t.string :video_url 8 | 9 | t.timestamps null: false 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20150812232430_add_flag_to_feed_items.rb: -------------------------------------------------------------------------------- 1 | class AddFlagToFeedItems < ActiveRecord::Migration 2 | def change 3 | add_reference :feed_items, :flag, index: true, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150818183333_add_eligibility_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddEligibilityToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :eligible, :boolean, :default => true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160112192523_add_disable_vpn_to_game.rb: -------------------------------------------------------------------------------- 1 | class AddDisableVpnToGame < ActiveRecord::Migration 2 | def change 3 | add_column :games, :disable_vpn, :boolean, :default => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160112203043_add_disable_flag_an_hour_to_game.rb: -------------------------------------------------------------------------------- 1 | class AddDisableFlagAnHourToGame < ActiveRecord::Migration 2 | def change 3 | add_column :games, :disable_flags_an_hour_graph, :boolean, :default => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160408200244_create_divisions.rb: -------------------------------------------------------------------------------- 1 | class CreateDivisions < ActiveRecord::Migration 2 | def change 3 | create_table :divisions do |t| 4 | t.string :name 5 | t.references :game, index: true, foreign_key: true 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20160408204720_rename_gameid_divisionid.rb: -------------------------------------------------------------------------------- 1 | class RenameGameidDivisionid < ActiveRecord::Migration 2 | def change 3 | remove_column :users, :game_id 4 | add_column :users, :division_id, :integer 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160415192739_create_challenge_states.rb: -------------------------------------------------------------------------------- 1 | class CreateChallengeStates < ActiveRecord::Migration 2 | def change 3 | create_table :challenge_states do |t| 4 | t.integer :state 5 | t.references :challenge, index: true, foreign_key: true 6 | t.references :division, index: true, foreign_key: true 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20160415194524_add_division_ref_to_solved_challenges.rb: -------------------------------------------------------------------------------- 1 | class AddDivisionRefToSolvedChallenges < ActiveRecord::Migration 2 | def change 3 | add_reference :feed_items, :division, index: true, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160422151153_rename_state_to_current_state_on_challenge.rb: -------------------------------------------------------------------------------- 1 | class RenameStateToCurrentStateOnChallenge < ActiveRecord::Migration 2 | def change 3 | rename_column :challenges, :state, :starting_state 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160422181517_change_status_to_int.rb: -------------------------------------------------------------------------------- 1 | class ChangeStatusToInt < ActiveRecord::Migration 2 | def change 3 | change_column :challenges, :starting_state, 'integer USING CAST(starting_state AS integer)' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160624144411_drop_keys_table.rb: -------------------------------------------------------------------------------- 1 | class DropKeysTable < ActiveRecord::Migration 2 | def up 3 | drop_table :keys 4 | end 5 | 6 | def down 7 | raise ActiveRecord::IrreversibleMigration 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160628171437_remove_tos_from_game.rb: -------------------------------------------------------------------------------- 1 | class RemoveTosFromGame < ActiveRecord::Migration 2 | def change 3 | remove_column :games, :tos 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20160628171437) do 15 | 16 | # These are extensions that must be enabled in order to support this database 17 | enable_extension "plpgsql" 18 | 19 | create_table "categories", force: :cascade do |t| 20 | t.string "name" 21 | t.datetime "created_at" 22 | t.datetime "updated_at" 23 | t.integer "game_id" 24 | end 25 | 26 | create_table "challenge_states", force: :cascade do |t| 27 | t.integer "state" 28 | t.integer "challenge_id" 29 | t.integer "division_id" 30 | t.datetime "created_at", null: false 31 | t.datetime "updated_at", null: false 32 | end 33 | 34 | add_index "challenge_states", ["challenge_id"], name: "index_challenge_states_on_challenge_id", using: :btree 35 | add_index "challenge_states", ["division_id"], name: "index_challenge_states_on_division_id", using: :btree 36 | 37 | create_table "challenges", force: :cascade do |t| 38 | t.string "name" 39 | t.text "description" 40 | t.integer "point_value" 41 | t.integer "starting_state" 42 | t.datetime "created_at" 43 | t.datetime "updated_at" 44 | t.integer "category_id" 45 | t.string "achievement_name" 46 | end 47 | 48 | create_table "divisions", force: :cascade do |t| 49 | t.string "name" 50 | t.integer "game_id" 51 | t.datetime "created_at", null: false 52 | t.datetime "updated_at", null: false 53 | end 54 | 55 | add_index "divisions", ["game_id"], name: "index_divisions_on_game_id", using: :btree 56 | 57 | create_table "feed_items", force: :cascade do |t| 58 | t.integer "user_id" 59 | t.string "type" 60 | t.datetime "created_at" 61 | t.datetime "updated_at" 62 | t.integer "challenge_id" 63 | t.string "text" 64 | t.integer "point_value" 65 | t.integer "flag_id" 66 | t.integer "division_id" 67 | end 68 | 69 | add_index "feed_items", ["division_id"], name: "index_feed_items_on_division_id", using: :btree 70 | add_index "feed_items", ["flag_id"], name: "index_feed_items_on_flag_id", using: :btree 71 | 72 | create_table "flags", force: :cascade do |t| 73 | t.integer "challenge_id" 74 | t.string "flag" 75 | t.string "api_request" 76 | t.string "video_url" 77 | t.datetime "created_at", null: false 78 | t.datetime "updated_at", null: false 79 | end 80 | 81 | add_index "flags", ["challenge_id"], name: "index_flags_on_challenge_id", using: :btree 82 | 83 | create_table "games", force: :cascade do |t| 84 | t.string "name" 85 | t.datetime "start" 86 | t.datetime "stop" 87 | t.text "description" 88 | t.datetime "created_at" 89 | t.datetime "updated_at" 90 | t.string "irc" 91 | t.boolean "disable_vpn", default: false 92 | t.boolean "disable_flags_an_hour_graph", default: false 93 | end 94 | 95 | create_table "messages", force: :cascade do |t| 96 | t.integer "user_id" 97 | t.integer "game_id" 98 | t.text "text" 99 | t.datetime "created_at" 100 | t.datetime "updated_at" 101 | t.string "title" 102 | end 103 | 104 | create_table "rails_admin_histories", force: :cascade do |t| 105 | t.string "message" 106 | t.string "username" 107 | t.integer "item" 108 | t.string "table" 109 | t.integer "month", limit: 2 110 | t.integer "year", limit: 8 111 | t.datetime "created_at" 112 | t.datetime "updated_at" 113 | end 114 | 115 | add_index "rails_admin_histories", ["item", "table", "month", "year"], name: "index_rails_admin_histories", using: :btree 116 | 117 | create_table "submitted_flags", force: :cascade do |t| 118 | t.integer "user_id" 119 | t.integer "challenge_id" 120 | t.string "text" 121 | t.datetime "created_at" 122 | t.datetime "updated_at" 123 | end 124 | 125 | create_table "users", force: :cascade do |t| 126 | t.string "email", default: "", null: false 127 | t.string "encrypted_password", default: "", null: false 128 | t.integer "sign_in_count", default: 0 129 | t.datetime "current_sign_in_at" 130 | t.datetime "last_sign_in_at" 131 | t.string "current_sign_in_ip" 132 | t.string "last_sign_in_ip" 133 | t.integer "failed_attempts", default: 0 134 | t.string "unlock_token" 135 | t.datetime "locked_at" 136 | t.datetime "created_at" 137 | t.datetime "updated_at" 138 | t.string "type" 139 | t.datetime "messages_stamp" 140 | t.string "tags" 141 | t.datetime "reset_password_sent_at" 142 | t.string "display_name" 143 | t.string "city" 144 | t.float "latitude" 145 | t.float "longitude" 146 | t.string "affiliation" 147 | t.boolean "eligible", default: true 148 | t.integer "division_id" 149 | end 150 | 151 | add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree 152 | 153 | add_foreign_key "challenge_states", "challenges" 154 | add_foreign_key "challenge_states", "divisions" 155 | add_foreign_key "divisions", "games" 156 | add_foreign_key "feed_items", "divisions" 157 | add_foreign_key "feed_items", "flags" 158 | add_foreign_key "flags", "challenges" 159 | end 160 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create ([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | 9 | # default admin user - CHANGE PASSWORD!!! 10 | Admin.create!(email: 'root', password: 'ChangePa$$w0rd') 11 | 12 | # default game 13 | game = Game.create!(name: 'Test Game', start: Time.now, stop: Time.now + 2.days) 14 | 15 | 16 | division1 = Division.create!(name: "High School", game_id: game.id) 17 | division2 = Division.create!(name: "College", game_id: game.id) 18 | 19 | # players 20 | Player.create!(display_name: 'pwnies', email: 'pwnies', password: 'test123456', division_id: division1.id) 21 | Player.create!(display_name: 'n00bs', email: 'n00bs', password: 'test123456', division_id: division2.id) 22 | 23 | # crypto 24 | category = Category.create!(name: 'Crypto', game: game) 25 | Challenge.create!(name: 'Challenge A', point_value: 100, flags: [ Flag.create(flag: "flag") ], starting_state: 'open', category: category) 26 | Challenge.create!(name: 'Challenge B', point_value: 200, flags: [ Flag.create(flag: "flag") ], starting_state: 'closed', category: category) 27 | Challenge.create!(name: 'Challenge C', point_value: 300, flags: [ Flag.create(flag: "flag") ], starting_state: 'closed', category: category) 28 | Challenge.create!(name: 'Challenge D', point_value: 400, flags: [ Flag.create(flag: "flag") ], starting_state: 'closed', category: category) 29 | 30 | category = Category.create!(name: 'Forensics', game: game) 31 | Challenge.create!(name: 'Challenge E', point_value: 100, flags: [ Flag.create(flag: "flag") ], starting_state: 'open', category: category) 32 | Challenge.create!(name: 'Challenge F', point_value: 200, flags: [ Flag.create(flag: "flag") ], starting_state: 'closed', category: category) 33 | Challenge.create!(name: 'Challenge G', point_value: 300, flags: [ Flag.create(flag: "flag") ], starting_state: 'closed', category: category) 34 | Challenge.create!(name: 'Challenge H', point_value: 400, flags: [ Flag.create(flag: "flag") ], starting_state: 'closed', category: category) 35 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | volumes: 3 | scoreboard-db: 4 | driver: local 5 | services: 6 | db: 7 | image: postgres 8 | volumes: 9 | - scoreboard-db:/var/lib/postgresql/data 10 | web: 11 | image: mitrectf/ctf-scoreboard 12 | env_file: ./.env_vars 13 | ports: 14 | - "3000:3000" 15 | depends_on: 16 | - db 17 | -------------------------------------------------------------------------------- /lib/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-cyber-academy/ctf-scoreboard-archive/56bda947589b60284e6d61817af8d7535ad74b8a/lib/assets/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-cyber-academy/ctf-scoreboard-archive/56bda947589b60284e6d61817af8d7535ad74b8a/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /lib/tasks/cucumber.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. 3 | # It is recommended to regenerate this file in the future when you upgrade to a 4 | # newer version of cucumber-rails. Consider adding your own code to a new file 5 | # instead of editing this one. Cucumber will automatically load all features/**/*.rb 6 | # files. 7 | 8 | unless ARGV.any? { |a| a =~ /^gems/ } # Don't load anything when running the gems:* tasks 9 | 10 | vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first 11 | $LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? 12 | 13 | begin 14 | require 'cucumber/rake/task' 15 | 16 | namespace :cucumber do 17 | Cucumber::Rake::Task.new({ ok: 'db:test:prepare' }, 'Run features that should pass') do |t| 18 | t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. 19 | t.fork = true # You may get faster startup if you set this to false 20 | t.profile = 'default' 21 | end 22 | 23 | Cucumber::Rake::Task.new({ wip: 'db:test:prepare' }, 'Run features that are being 24 | worked on') do |t| 25 | t.binary = vendored_cucumber_bin 26 | t.fork = true # You may get faster startup if you set this to false 27 | t.profile = 'wip' 28 | end 29 | 30 | Cucumber::Rake::Task.new({ rerun: 'db:test:prepare' }, 'Record failing features and run 31 | only them if any exist') do |t| 32 | t.binary = vendored_cucumber_bin 33 | t.fork = true # You may get faster startup if you set this to false 34 | t.profile = 'rerun' 35 | end 36 | 37 | desc 'Run all features' 38 | task all: [:ok, :wip] 39 | 40 | task :statsetup do 41 | require 'rails/code_statistics' 42 | ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') 43 | ::CodeStatistics::TEST_TYPES << 'Cucumber features' if File.exist?('features') 44 | end 45 | end 46 | desc 'Alias for cucumber:ok' 47 | task cucumber: 'cucumber:ok' 48 | 49 | task default: :cucumber 50 | 51 | task features: :cucumber do 52 | STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" 53 | end 54 | 55 | # In case we don't have ActiveRecord, append a no-op task that we can depend upon. 56 | task 'db:test:prepare' do 57 | end 58 | 59 | task stats: 'cucumber:statsetup' 60 | rescue LoadError 61 | desc 'cucumber rake task not available (cucumber not installed)' 62 | task :cucumber do 63 | abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' 64 | end 65 | end 66 | 67 | end 68 | -------------------------------------------------------------------------------- /lib/tasks/scoreboard.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | namespace :scoreboard do 3 | namespace :user do 4 | desc 'Add a user to the Game' 5 | task :add, [:player_login, :player, :password] => [:environment] do |_t, args| 6 | p = Game.instance.players.find_by_email((args[:player_login]).to_s) 7 | if p.nil? 8 | p = Player.new(email: to_s(args[:player_login]), password: to_s(args[:password]), game_id: 9 | Game.instance.id) 10 | p.display_name = to_s(args[:player]) 11 | p.save! 12 | else 13 | puts 'User Exists' 14 | end 15 | end 16 | end 17 | 18 | namespace :db do 19 | desc 'Perform a dump of the production database and copy to Amazon S3' 20 | task backup: :environment do 21 | # load rails env 22 | config = ActiveRecord::Base.configurations[Rails.env] 23 | database = config['database'] 24 | ENV['PGPASSWORD'] = (config['password'] || '') 25 | 26 | # perform dump 27 | date = Time.zone.now.strftime('%Y-%m-%d-%H-%M') 28 | path = "/tmp/#{date}.pgdump" 29 | `rm -f "#{path}"` 30 | `/usr/bin/pg_dump "#{database}" > "#{path}"` 31 | 32 | # send to s3 33 | AWS::S3::Base.establish_connection!( 34 | access_key_id: Rails.configuration.s3_access_key_id, 35 | secret_access_key: Rails.configuration.s3_secret_access_key 36 | ) 37 | bucket = Rails.configuration.s3_bucket_name 38 | AWS::S3::Bucket.create(bucket) 39 | AWS::S3::S3Object.store(File.basename(path), open(path), Rails.configuration.s3_bucket_name) 40 | 41 | # delete 42 | `rm -f "#{path}"` 43 | end 44 | 45 | desc 'Perform a restore of the production database given the date stamp as 46 | seen on S3' 47 | task restore: [:environment, 'db:drop', 'db:create'] do 48 | # load rails env 49 | config = ActiveRecord::Base.configurations[Rails.env] 50 | database = config['database'] 51 | ENV['PGPASSWORD'] = (config['password'] || '') 52 | 53 | # get from s3 54 | AWS::S3::Base.establish_connection!( 55 | access_key_id: Rails.configuration.s3_access_key_id, 56 | secret_access_key: Rails.configuration.s3_secret_access_key 57 | ) 58 | date = ENV['date'] 59 | path = "/tmp/#{date}.pgdump" 60 | `rm -f "#{path}"` 61 | open(path, 'w') do |file| 62 | AWS::S3::S3Object.stream(File.basename(path), Rails.configuration.s3_bucket_name) do |chunk| 63 | file.write chunk 64 | end 65 | end 66 | 67 | # import into postgres 68 | `/usr/lib/postgresql/8.4/bin/psql "#{database}" < "#{path}"` 69 | 70 | # delete 71 | `rm -f "#{path}"` 72 | end 73 | end 74 | 75 | namespace :certificates do 76 | desc 'Sync user certificates between scoreboard and VPN box' 77 | task copy: [:environment] do 78 | system '/usr/bin/unison -auto -batch -ignorearchives /opt/keys '\ 79 | "ssh://#{Rails.configuration.jumpbox[:ip]}//opt/openvpn_keys" 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    The page you were looking for doesn't exist.

    23 |

    You may have mistyped the address or the page may have moved.

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    The change you wanted was rejected.

    23 |

    Maybe you tried to change something you didn't have access to.

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    We're sorry, but something went wrong.

    23 |

    We've been notified about this issue and we'll take a look at it shortly.

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-cyber-academy/ctf-scoreboard-archive/56bda947589b60284e6d61817af8d7535ad74b8a/public/favicon.ico -------------------------------------------------------------------------------- /public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-cyber-academy/ctf-scoreboard-archive/56bda947589b60284e6d61817af8d7535ad74b8a/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-cyber-academy/ctf-scoreboard-archive/56bda947589b60284e6d61817af8d7535ad74b8a/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | # This command will automatically be run when you run "rails" with Rails 3 4 | gems installed from the root of your application. 5 | 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require File.expand_path('../../config/boot', __FILE__) 8 | require 'rails/commands' 9 | -------------------------------------------------------------------------------- /test/fixtures/categories.yml: -------------------------------------------------------------------------------- 1 | category_one: 2 | name: binary 3 | game: game_one 4 | category_two: 5 | name: forensics 6 | game: game_one -------------------------------------------------------------------------------- /test/fixtures/challenge_states.yml: -------------------------------------------------------------------------------- 1 | challenge_state_one_d1: 2 | state: <%= ChallengeState.states[:open] %> 3 | challenge: challenge_one_cat_one 4 | division: division_one 5 | 6 | challenge_state_one_d2: 7 | state: <%= ChallengeState.states[:open] %> 8 | challenge: challenge_one_cat_one 9 | division: division_two 10 | 11 | challenge_state_two_d1: 12 | state: <%= ChallengeState.states[:force_closed] %> 13 | challenge: challenge_two_cat_one 14 | division: division_one 15 | 16 | challenge_state_two_d2: 17 | state: <%= ChallengeState.states[:force_closed] %> 18 | challenge: challenge_two_cat_one 19 | division: division_two 20 | 21 | challenge_state_three_d1: 22 | state: <%= ChallengeState.states[:closed] %> 23 | challenge: challenge_three_cat_one 24 | division: division_one 25 | 26 | challenge_state_three_d2: 27 | state: <%= ChallengeState.states[:closed] %> 28 | challenge: challenge_three_cat_one 29 | division: division_two 30 | 31 | challenge_state_four_d1: 32 | state: <%= ChallengeState.states[:open] %> 33 | challenge: challenge_four_cat_two 34 | division: division_one 35 | 36 | challenge_state_four_d2: 37 | state: <%= ChallengeState.states[:open] %> 38 | challenge: challenge_four_cat_two 39 | division: division_two 40 | 41 | challenge_state_five_d1: 42 | state: <%= ChallengeState.states[:closed] %> 43 | challenge: challenge_five_cat_two 44 | division: division_one 45 | 46 | challenge_state_five_d2: 47 | state: <%= ChallengeState.states[:closed] %> 48 | challenge: challenge_five_cat_two 49 | division: division_two 50 | 51 | challenge_state_six_d1: 52 | state: <%= ChallengeState.states[:closed] %> 53 | challenge: challenge_six_cat_two 54 | division: division_one 55 | 56 | challenge_state_six_d2: 57 | state: <%= ChallengeState.states[:closed] %> 58 | challenge: challenge_six_cat_two 59 | division: division_two 60 | -------------------------------------------------------------------------------- /test/fixtures/challenges.yml: -------------------------------------------------------------------------------- 1 | challenge_one_cat_one: 2 | name: challenge_one 3 | description: challenge one description 4 | point_value: 100 5 | starting_state: <%= ChallengeState.states[:open] %> 6 | category: category_one 7 | 8 | challenge_two_cat_one: 9 | name: challenge_two 10 | description: challenge two description 11 | point_value: 200 12 | starting_state: <%= ChallengeState.states[:force_closed] %> 13 | category: category_one 14 | 15 | challenge_three_cat_one: 16 | name: challenge_three 17 | description: challenge three description 18 | point_value: 300 19 | starting_state: <%= ChallengeState.states[:closed] %> 20 | category: category_one 21 | 22 | challenge_four_cat_two: 23 | name: challenge_four_cat_two 24 | description: challenge one description 25 | point_value: 200 26 | category: category_two 27 | starting_state: <%= ChallengeState.states[:force_closed] %> 28 | achievement_name: challenge three achievement 29 | 30 | challenge_five_cat_two: 31 | name: challenge_five_cat_two 32 | description: challenge two description 33 | point_value: 300 34 | starting_state: <%= ChallengeState.states[:open] %> 35 | category: category_two 36 | achievement_name: challenge four achievement 37 | 38 | challenge_six_cat_two: 39 | name: challenge_six_cat_two 40 | description: challenge two description 41 | point_value: 300 42 | starting_state: <%= ChallengeState.states[:open] %> 43 | category: category_two 44 | achievement_name: challenge four achievement 45 | -------------------------------------------------------------------------------- /test/fixtures/divisions.yml: -------------------------------------------------------------------------------- 1 | division_one: 2 | name: division one 3 | game: game_one 4 | 5 | division_two: 6 | name: division two 7 | game: game_one -------------------------------------------------------------------------------- /test/fixtures/feed_items.yml: -------------------------------------------------------------------------------- 1 | feed_item_one: 2 | player: player_one 3 | point_value: 200 4 | text: test 5 | type: Achievement 6 | 7 | feed_item_two: 8 | player: player_two 9 | point_value: -100 10 | text: test 11 | type: Achievement 12 | 13 | feed_item_three: 14 | player: player_one 15 | point_value: 400 16 | text: test 17 | type: Achievement 18 | 19 | score_adjustment_one: 20 | player: player_one 21 | point_value: 200 22 | text: Helped us fix a bug in a challenge 23 | type: ScoreAdjustment 24 | 25 | score_adjustment_two: 26 | player: player_two 27 | point_value: -100 28 | text: Broke a challenge 29 | type: ScoreAdjustment 30 | 31 | solved_challenge_one: 32 | player: player_two 33 | challenge: challenge_three_cat_one 34 | flag: flag_three 35 | type: SolvedChallenge 36 | 37 | solved_challenge_two: 38 | player: player_two 39 | challenge: challenge_four_cat_two 40 | flag: flag_four 41 | type: SolvedChallenge 42 | -------------------------------------------------------------------------------- /test/fixtures/flags.yml: -------------------------------------------------------------------------------- 1 | flag_one: 2 | challenge: challenge_one_cat_one 3 | flag: flag one 4 | 5 | flag_two: 6 | challenge: challenge_two_cat_one 7 | flag: 8 | 9 | flag_three: 10 | challenge: challenge_three_cat_one 11 | flag: flag three 12 | 13 | flag_four: 14 | challenge: challenge_four_cat_two 15 | flag: flag four 16 | 17 | flag_five: 18 | challenge: challenge_five_cat_two 19 | flag: flag five 20 | 21 | flag_six: 22 | challenge: challenge_six_cat_two 23 | flag: flag six -------------------------------------------------------------------------------- /test/fixtures/games.yml: -------------------------------------------------------------------------------- 1 | game_one: 2 | name: game one 3 | start: <%= Time.now %> 4 | stop: <%= Time.now + 10.hours %> 5 | description: game one description 6 | irc: game one irc -------------------------------------------------------------------------------- /test/fixtures/messages.yml: -------------------------------------------------------------------------------- 1 | message_one: 2 | game: game_one 3 | text: message one text 4 | title: message one title 5 | -------------------------------------------------------------------------------- /test/fixtures/submitted_flags.yml: -------------------------------------------------------------------------------- 1 | submitted_flag_one: 2 | player: player_one 3 | challenge: challenge_one_cat_one 4 | text: submitted flag one -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | player_one: 2 | email: playerone@test.com 3 | encrypted_password: <%= Devise::Encryptor.digest(User, 'TestPassword123') %> 4 | division: division_one 5 | display_name: Player One 6 | type: Player 7 | 8 | player_two: 9 | email: playertwo@test.com 10 | encrypted_password: <%= Devise::Encryptor.digest(User, 'TestPassword123') %> 11 | division: division_two 12 | display_name: Player Two 13 | type: Player 14 | 15 | player_three: 16 | email: playerthree@test.com 17 | encrypted_password: <%= Devise::Encryptor.digest(User, 'TestPassword123') %> 18 | division: division_one 19 | display_name: Player Three 20 | eligible: false 21 | type: Player 22 | 23 | player_four: 24 | email: playerfour@test.com 25 | encrypted_password: <%= Devise::Encryptor.digest(User, 'TestPassword123') %> 26 | division: division_one 27 | display_name: Player Four 28 | eligible: true 29 | type: Player 30 | 31 | admin_one: 32 | email: admin@test.com 33 | encrypted_password: <%= Devise::Encryptor.digest(User, 'TestPassword123') %> 34 | type: Admin 35 | -------------------------------------------------------------------------------- /test/functional/achievements_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AchievementsControllerTest < ActionController::TestCase 4 | 5 | def setup 6 | @request.env["devise.mapping"] = Devise.mappings[:user] 7 | end 8 | 9 | test 'index' do 10 | sign_in users(:player_one) 11 | # render 12 | get :index 13 | assert :success 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/functional/application_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ApplicationControllerTest < ActionController::TestCase 4 | def setup 5 | @request.env["devise.mapping"] = Devise.mappings[:user] 6 | end 7 | 8 | test 'to timeline' do 9 | end 10 | 11 | test 'enable auto reload' do 12 | end 13 | 14 | test 'load game' do 15 | end 16 | 17 | test 'load messages count' do 18 | end 19 | 20 | test 'enforce access' do 21 | end 22 | 23 | test 'after sign in path' do 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/functional/challenges_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ChallengesControllerTest < ActionController::TestCase 4 | 5 | def setup 6 | @request.env["devise.mapping"] = Devise.mappings[:user] 7 | end 8 | 9 | test 'index' do 10 | sign_in users(:player_one) 11 | # Render 12 | get :index 13 | assert :success 14 | end 15 | 16 | test 'show' do 17 | end 18 | 19 | test 'submit correct flag' do 20 | end 21 | 22 | test 'submit incorrect flag' do 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/functional/games_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GamesControllerTest < ActionController::TestCase 4 | 5 | def setup 6 | @request.env["devise.mapping"] = Devise.mappings[:user] 7 | end 8 | 9 | test 'index' do 10 | end 11 | 12 | test 'show' do 13 | end 14 | 15 | test 'summary' do 16 | end 17 | end -------------------------------------------------------------------------------- /test/functional/keys_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class KeysControllerTest < ActionController::TestCase 4 | 5 | def setup 6 | @request.env["devise.mapping"] = Devise.mappings[:user] 7 | end 8 | 9 | test 'edit' do 10 | end 11 | 12 | test 'new' do 13 | end 14 | 15 | test 'valid key create' do 16 | end 17 | 18 | test 'invalid key create' do 19 | end 20 | 21 | test 'valid key update' do 22 | end 23 | 24 | test 'invalid key update' do 25 | end 26 | 27 | test 'destroy' do 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/functional/messages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MessagesControllerTest < ActionController::TestCase 4 | 5 | def setup 6 | @request.env["devise.mapping"] = Devise.mappings[:user] 7 | end 8 | 9 | test 'index' do 10 | sign_in users(:player_one) 11 | get :index 12 | assert :success 13 | end 14 | 15 | test 'users messages are updated' do 16 | end 17 | end -------------------------------------------------------------------------------- /test/functional/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UsersControllerTest < ActionController::TestCase 4 | def setup 5 | @request.env["devise.mapping"] = Devise.mappings[:user] 6 | end 7 | 8 | test 'index' do 9 | sign_in users(:player_two) 10 | get :index 11 | assert_response :success 12 | end 13 | 14 | test 'show' do 15 | end 16 | 17 | test 'download' do 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/helpers/application_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ApplicationHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/categories_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CategoriesHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/challenges_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ChallengesHelperTest < ActionView::TestCase 4 | test 'embed' do 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/helpers/games_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GamesHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/messages_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MessagesHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/sessions_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SessionsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/timeline_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TimelineHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitre-cyber-academy/ctf-scoreboard-archive/56bda947589b60284e6d61817af8d7535ad74b8a/test/mailers/.gitkeep -------------------------------------------------------------------------------- /test/models/achievement_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AchievementTest < ActiveSupport::TestCase 4 | test 'description' do 5 | text = feed_items(:feed_item_three).text 6 | description_string = %(Unlocked achievement "#{text}") 7 | assert_equal description_string, feed_items(:feed_item_three).description 8 | end 9 | 10 | test 'icon' do 11 | icon_string = 'certificate' 12 | assert_equal icon_string, feed_items(:feed_item_three).icon 13 | end 14 | end -------------------------------------------------------------------------------- /test/models/admin_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AdminTest < ActiveSupport::TestCase 4 | end -------------------------------------------------------------------------------- /test/models/category_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CategoryTest < ActiveSupport::TestCase 4 | test 'next challenge' do 5 | category_one = categories(:category_two) 6 | challenge_one = challenges(:challenge_four_cat_two) 7 | assert_equal challenges(:challenge_five_cat_two), category_one.next_challenge(challenge_one) 8 | end 9 | end -------------------------------------------------------------------------------- /test/models/challenge_state_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ChallengeStateTest < ActiveSupport::TestCase 4 | test 'default values' do 5 | # 1 == forced-closed 6 | assert_equal 'force_closed', challenge_states(:challenge_state_two_d2).default_values 7 | assert_equal 'open', challenge_states(:challenge_state_four_d2).default_values 8 | end 9 | end -------------------------------------------------------------------------------- /test/models/challenge_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ChallengeTest < ActiveSupport::TestCase 4 | test 'challenge state open' do 5 | assert_equal true, challenges(:challenge_one_cat_one).challenge_open?(divisions(:division_one)) 6 | assert_equal false, challenges(:challenge_three_cat_one).challenge_open?(divisions(:division_one)) 7 | end 8 | 9 | test 'challenge open' do 10 | assert_equal true, challenges(:challenge_one_cat_one).open?(divisions(:division_one)) 11 | end 12 | 13 | test 'challenge solved' do 14 | assert_equal false, challenges(:challenge_one_cat_one).solved? 15 | end 16 | 17 | test 'challenge available' do 18 | assert_equal false, challenges(:challenge_one_cat_one).available?(divisions(:division_one)) 19 | end 20 | 21 | test 'challenge force closed' do 22 | assert_equal true, challenges(:challenge_two_cat_one).force_closed?(divisions(:division_two)) 23 | end 24 | 25 | test 'get current solved challenge' do 26 | assert_equal nil, challenges(:challenge_one_cat_one).get_current_solved_challenge(users(:player_one)) 27 | assert_equal feed_items(:solved_challenge_one), challenges(:challenge_three_cat_one).get_current_solved_challenge(users(:player_two)) 28 | end 29 | 30 | test 'solved by user' do 31 | assert_equal false, challenges(:challenge_one_cat_one).solved_by_user?(users(:player_one)) 32 | assert_equal true, challenges(:challenge_three_cat_one).solved_by_user?(users(:player_two)) 33 | end 34 | 35 | test 'get video url for flag' do 36 | # current challenge nil 37 | assert_equal nil, challenges(:challenge_one_cat_one).get_video_url_for_flag(users(:player_one)) 38 | # current challenge flag nil 39 | assert_equal nil, challenges(:challenge_two_cat_one).get_video_url_for_flag(users(:player_one)) 40 | # current challenge solved 41 | assert_equal flags(:flag_three).video_url, challenges(:challenge_three_cat_one).get_video_url_for_flag(users(:player_two)) 42 | end 43 | 44 | test 'get api request for flag' do 45 | # current challenge nil 46 | assert_equal nil, challenges(:challenge_one_cat_one).get_api_request_for_flag(users(:player_one)) 47 | # current challenge flag nil 48 | assert_equal nil, challenges(:challenge_two_cat_one).get_api_request_for_flag(users(:player_one)) 49 | # current challenge solved 50 | assert_equal flags(:flag_three).api_request, challenges(:challenge_three_cat_one).get_api_request_for_flag(users(:player_two)) 51 | end 52 | 53 | test 'set state' do 54 | challenges(:challenge_one_cat_one).set_state(divisions(:division_one), ChallengeState.states[:force_closed]) 55 | assert_equal 'force_closed', challenges(:challenge_one_cat_one).challenge_states.find_by(division: divisions(:division_one)).state 56 | end 57 | end -------------------------------------------------------------------------------- /test/models/division_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DivisionTest < ActiveSupport::TestCase 4 | test 'all eligible players' do 5 | ordered_players = divisions(:division_one).ordered_players 6 | assert_equal divisions(:division_one).players.size, ordered_players.size 7 | end 8 | 9 | test 'top five eligible players' do 10 | ordered_players = divisions(:division_one).ordered_players(true) 11 | assert_operator 5, :>=, ordered_players.size 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/models/feed_item_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FeedItemTest < ActiveSupport::TestCase 4 | end -------------------------------------------------------------------------------- /test/models/flag_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FlagTest < ActiveSupport::TestCase 4 | end -------------------------------------------------------------------------------- /test/models/game_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GameTest < ActiveSupport::TestCase 4 | 5 | test 'instance is singleton' do 6 | game = Game.new( 7 | name: 'game', 8 | start: Time.now, 9 | stop: Time.now - 10.hours, 10 | description: 'game description', 11 | irc: 'game irc' 12 | ) 13 | assert_not game.valid? 14 | assert_equal true, game.errors.added?(:base, I18n.t('game.too_many')) 15 | end 16 | 17 | test 'order of start stop date' do 18 | game = Game.new( 19 | name: 'game', 20 | start: Time.now, 21 | stop: Time.now - 10.hours, 22 | description: 'game description', 23 | irc: 'game irc' 24 | ) 25 | assert_not game.valid? 26 | assert_equal true, game.errors.added?(:base, I18n.t('game.date_mismatch')) 27 | end 28 | 29 | test 'open' do 30 | assert_equal true, games(:game_one).open? 31 | end 32 | end -------------------------------------------------------------------------------- /test/models/message_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MessageTest < ActiveSupport::TestCase 4 | end -------------------------------------------------------------------------------- /test/models/player_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PlayerTest < ActiveSupport::TestCase 4 | test 'score method returns proper value' do 5 | team_one = users(:player_one) 6 | # Player 1 has a 200 point score adjustment added from the fixtures 7 | assert_equal 200, team_one.score 8 | end 9 | 10 | test 'display name' do 11 | # Eligible 12 | assert_equal users(:player_one).display_name, users(:player_one).display_name 13 | # Ineligible 14 | assert_equal users(:player_three).display_name, users(:player_three).display_name 15 | end 16 | 17 | test 'key file name' do 18 | key_phile_name = 'playeronete' + users(:player_one).id.to_s 19 | assert_equal key_phile_name, users(:player_one).key_file_name 20 | end 21 | end -------------------------------------------------------------------------------- /test/models/score_adjustment_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ScoreAdjustmentTest < ActiveSupport::TestCase 4 | include ActionView::Helpers::TextHelper 5 | 6 | test 'build description' do 7 | points = feed_items(:score_adjustment_one).point_value 8 | description_string = %(Score was increased by #{pluralize(points.abs, 'point')}) 9 | assert_equal description_string, feed_items(:score_adjustment_one).description 10 | points = feed_items(:score_adjustment_two).point_value 11 | description_string = %(Score was decreased by #{pluralize(points.abs, 'point')}) 12 | assert_equal description_string, feed_items(:score_adjustment_two).description 13 | end 14 | 15 | test 'build icon' do 16 | assert_equal 'chevron-up', feed_items(:score_adjustment_one).icon 17 | assert_equal 'chevron-down', feed_items(:score_adjustment_two).icon 18 | end 19 | 20 | test 'point value is not zero' do 21 | score_adjustment = ScoreAdjustment.new( 22 | point_value: 0) 23 | assert_not score_adjustment.valid? 24 | assert_equal ['must not be zero.'], score_adjustment.errors.messages[:point_value] 25 | end 26 | 27 | test 'score adjustment add points to team' do 28 | team_one = users(:player_one) 29 | # Player 1 has a 200 point score adjustment added from the fixtures 30 | assert_equal 200, team_one.score 31 | ScoreAdjustment.create!(player: team_one, point_value: 100, text: 'Did real good on a challenge') 32 | end 33 | end -------------------------------------------------------------------------------- /test/models/solved_challenge_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SolvedChallengeTest < ActiveSupport::TestCase 4 | 5 | def setup 6 | @solved_challenge_first = SolvedChallenge.new( 7 | player: users(:player_one), 8 | challenge: challenges(:challenge_two_cat_one), 9 | flag: flags(:flag_two) 10 | ) 11 | @solved_challenge_second = SolvedChallenge.new( 12 | player: users(:player_one), 13 | challenge: challenges(:challenge_one_cat_one), 14 | flag: flags(:flag_one), 15 | division: divisions(:division_one) 16 | ) 17 | @solved_challenge_third = SolvedChallenge.new( 18 | player: users(:player_one), 19 | challenge: challenges(:challenge_four_cat_two), 20 | flag: flags(:flag_four), 21 | division: divisions(:division_one) 22 | ) 23 | @solved_challenge_fourth = SolvedChallenge.new( 24 | player: users(:player_two), 25 | challenge: challenges(:challenge_four_cat_two), 26 | flag: flags(:flag_four), 27 | division: divisions(:division_two) 28 | ) 29 | end 30 | 31 | test 'description' do 32 | description_string = %(Solved challenge "#{feed_items(:solved_challenge_one).challenge.category.name} #{feed_items(:solved_challenge_one).challenge.point_value}") 33 | assert_equal description_string, feed_items(:solved_challenge_one).description 34 | end 35 | 36 | test 'icon' do 37 | assert_equal 'ok', feed_items(:solved_challenge_one).icon 38 | end 39 | 40 | test 'challenge is open' do 41 | assert_not @solved_challenge_first.valid? 42 | assert_equal true, @solved_challenge_first.errors.added?(:challenge, I18n.t('challenge.not_open')) 43 | end 44 | 45 | test 'game is open' do 46 | game = games(:game_one) 47 | game.stop = Time.now 48 | game.save 49 | 50 | assert_not @solved_challenge_first.valid? 51 | assert_equal true, @solved_challenge_first.errors.added?(:base, I18n.t('challenge.game_not_open')) 52 | end 53 | 54 | test 'player has already solved challenge' do 55 | assert_not @solved_challenge_fourth.valid? 56 | assert_equal true, @solved_challenge_fourth.errors.added?(:base, I18n.t('challenge.already_solved')) 57 | end 58 | 59 | test 'award achievement' do 60 | assert_difference 'Achievement.count', +1 do 61 | feed_items(:solved_challenge_two).award_achievement 62 | end 63 | end 64 | 65 | test 'award first blood' do 66 | SolvedChallenge.delete_all 67 | assert_difference '@solved_challenge_second.player.achievements.count', +1 do 68 | @solved_challenge_second.save 69 | end 70 | end 71 | 72 | test 'open next challenge will work when not force closed' do 73 | @solved_challenge_third.save 74 | @solved_challenge_third.open_next_challenge 75 | assert_equal true, challenges(:challenge_five_cat_two).open?(divisions(:division_one)) 76 | assert_equal false, challenges(:challenge_six_cat_two).open?(divisions(:division_one)) 77 | end 78 | 79 | test 'open next challenge will not open next challenge when force closed' do 80 | @solved_challenge_second.save 81 | @solved_challenge_second.open_next_challenge 82 | assert_equal false, challenges(:challenge_two_cat_one).open?(divisions(:division_one)) 83 | # Next challenge will also stay closed 84 | assert_equal false, challenges(:challenge_three_cat_one).open?(divisions(:division_one)) 85 | end 86 | end -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | test 'is an admin' do 5 | assert_equal true, users(:admin_one).admin? 6 | end 7 | 8 | test 'set password' do 9 | assert_nil users(:player_one).change_password 10 | end 11 | 12 | test 'set password with value' do 13 | password = 'NewTestPassword123' 14 | assert_nil users(:player_one).change_password 15 | assert_equal password, users(:player_one).change_password = password 16 | end 17 | end -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | Coveralls.wear! 3 | 4 | ENV['RAILS_ENV'] ||= 'test' 5 | require File.expand_path('../../config/environment', __FILE__) 6 | require 'rails/test_help' 7 | 8 | class ActiveSupport::TestCase 9 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 10 | fixtures :all 11 | 12 | # Load fixtures from the engine 13 | end 14 | 15 | class ActionController::TestCase 16 | include Devise::Test::ControllerHelpers 17 | end 18 | 19 | class ActionView::TestCase 20 | include Devise::Test::ControllerHelpers 21 | end 22 | --------------------------------------------------------------------------------