From 958b203cdce65c2bcc97282dfbe6e60e0feb1a7d Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 12 Jan 2026 10:43:32 +0200 Subject: [PATCH 01/87] fix typo in path name --- ansible/runonce/mui.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/runonce/mui.yml b/ansible/runonce/mui.yml index b3c247965..068b0df82 100755 --- a/ansible/runonce/mui.yml +++ b/ansible/runonce/mui.yml @@ -272,7 +272,7 @@ dest: "{{item.dest}}" with_items: - { src: '{{playbook_dir}}/../templates/httpd.conf.j2', dest: '/etc/httpd.conf', domain: '{{hostname}}' } - - { src: '{{playbook_dir}}/../templates/acme-client.conf.j2', dest: '/etc/acme-client.conf', domain: '{{hostname}}', challenge_dir: "/home/moderatortUI/acme/.well-known/acme-challenge/" } + - { src: '{{playbook_dir}}/../templates/acme-client.conf.j2', dest: '/etc/acme-client.conf', domain: '{{hostname}}', challenge_dir: "/home/moderatorUI/acme/.well-known/acme-challenge/" } - name: Generate pf tables files command: "{{item.cmd}}" From e22be841106af2f4a5657dfedd4de1f9abfb8adf Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 12 Jan 2026 10:43:53 +0200 Subject: [PATCH 02/87] allow us to perform acme requests even when in DT mode --- ansible/templates/dt.conf.j2 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ansible/templates/dt.conf.j2 b/ansible/templates/dt.conf.j2 index 17f7650b4..5a3699bdc 100644 --- a/ansible/templates/dt.conf.j2 +++ b/ansible/templates/dt.conf.j2 @@ -43,7 +43,15 @@ http { ssl_prefer_server_ciphers on; ssl_dhparam /etc/ssl/private/dhparam.pem; - return 503; + # ACME challenge must bypass maintenance + location ^~ /.well-known/acme-challenge/ { + root /acme; + try_files $uri =404; + } + + location / { + return 503; + } error_page 503 @maintenance; location @maintenance { add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; From e1461fdde93fb83faae8b1362f9543482f4f5731 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 12 Jan 2026 10:44:15 +0200 Subject: [PATCH 03/87] a simple playbook to add new ssh keys to a host/user --- ansible/maintenance/authorized_keys.yml | 33 +++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 ansible/maintenance/authorized_keys.yml diff --git a/ansible/maintenance/authorized_keys.yml b/ansible/maintenance/authorized_keys.yml new file mode 100644 index 000000000..cda89202b --- /dev/null +++ b/ansible/maintenance/authorized_keys.yml @@ -0,0 +1,33 @@ +--- +- name: Update authorized SSH keys for a user + hosts: all + become: true + + vars: + target_user: root + + pre_tasks: + - name: Ensure ssh_keys_source is provided + ansible.builtin.fail: + msg: "You must provide ssh_keys_source (file path or URL)" + when: ssh_keys_source is not defined + + - name: Load SSH public keys from file or URL + ansible.builtin.set_fact: + ssh_public_keys: >- + {{ + lookup( + ssh_keys_source is match('^https?://') + | ternary('url', 'file'), + ssh_keys_source + ) + }} + + tasks: + - name: Update authorized_keys for user + ansible.posix.authorized_key: + user: "{{ target_user }}" + key: "{{ ssh_public_keys }}" + manage_dir: true + state: present + # exclusive: true # uncomment to replace all existing keys From fd4fd17021abed9ae9ad9ecf0b6829d7f8b56199 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 12 Jan 2026 10:44:54 +0200 Subject: [PATCH 04/87] sync composer lock with json --- .github/update-composer-json.php | 137 +++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 .github/update-composer-json.php diff --git a/.github/update-composer-json.php b/.github/update-composer-json.php new file mode 100644 index 000000000..2144c54fb --- /dev/null +++ b/.github/update-composer-json.php @@ -0,0 +1,137 @@ + 'package', // The type is at the root level of the repository entry + 'package' => [ + 'name' => $packageName, + 'version' => $packageVersionRange, + 'type' => 'library', // Default type, you can adjust this if needed + 'dist' => [ + 'url' => $repositoryUrl, + 'type' => $distType, // Use the dynamically derived dist type + 'reference' => $packageHash // Adding the hash (reference) if available + ] + ] + ]; + // Track pinned package + $pinnedPackages[] = $packageName; + } +} + +// Write the updated composer.json back to the file +if (file_put_contents($composerJsonPath, json_encode($composerJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL)) { + echo "composer.json has been updated with the versions from composer.lock.\n"; + if (count($pinnedPackages) > 0) { + echo "The following packages were pinned to repositories:\n"; + foreach ($pinnedPackages as $packageName) { + echo " - $packageName\n"; + } + } +} else { + echo "Failed to update composer.json.\n"; + exit(1); +} From 5164b74ced76d62bfe7475c350aac7f480b41747 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 14 Jan 2026 14:17:15 +0200 Subject: [PATCH 05/87] add auto-assign pr --- .github/workflows/auto-assign-pr.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/auto-assign-pr.yml diff --git a/.github/workflows/auto-assign-pr.yml b/.github/workflows/auto-assign-pr.yml new file mode 100644 index 000000000..dcc6fb843 --- /dev/null +++ b/.github/workflows/auto-assign-pr.yml @@ -0,0 +1,21 @@ +name: Auto-Assign PR + +on: + pull_request: + types: [opened] + +jobs: + assign: + runs-on: ubuntu-latest + steps: + - name: Assign PR to repo owner + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GH_ADMIN_TOKEN }} + script: | + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + assignees: ["proditis"] + }); From 0151a10a67fac092c501ba979cdbda3fd73af72d Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 14 Jan 2026 14:17:31 +0200 Subject: [PATCH 06/87] create docker-servers bootstrap and normal ops --- ansible/runonce/docker-servers.yml | 121 ++++++++++++++++------------- 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/ansible/runonce/docker-servers.yml b/ansible/runonce/docker-servers.yml index af2024e10..1b9d3e404 100755 --- a/ansible/runonce/docker-servers.yml +++ b/ansible/runonce/docker-servers.yml @@ -1,40 +1,17 @@ #!/usr/bin/env ansible-playbook --- -- name: Configure docker servers for echoCTF +- name: Bootstrap docker servers for echoCTF hosts: all gather_facts: true become: true become_method: su + remote_user: sysadmin tasks: + - name: Set timezone to UTC timezone: name: UTC - - name: Kill any running pm2 - shell: pm2 kill - no_log: true - ignore_errors: true - - - name: Ensure docker services are stopped - command: service docker stop - no_log: true - ignore_errors: true - - - name: Ensure existing docker service overrides are removed - ansible.builtin.file: - path: "{{item}}" - state: absent - with_items: - - /etc/systemd/system/docker.service.d/dockerd-service-override.conf - - /etc/systemd/system/docker.service.d/override.conf - - /etc/systemd/system/pm2-root.service.d/pm2-root-service-override.conf - - /etc/systemd/system/pm2-root.service.d/override.conf - - - name: Remove any existing /etc/docker/daemon.json - ansible.builtin.file: - path: /etc/docker/daemon.json - state: absent - - name: Set hostname based on host_var hostname: name: "{{fqdn}}" @@ -61,29 +38,6 @@ pkg: "{{pre_apt}}" when: pre_apt is defined and pre_apt|length > 0 - - name: Add apt keys - when: aptKeys is defined - apt_key: - url: "{{item.key}}" - state: "{{item.state}}" - with_items: "{{aptKeys}}" - - - name: Add apt repositories - when: aptRepos is defined - apt_repository: - repo: "{{item.repo}}" - state: "{{item.state}}" - with_items: "{{aptRepos}}" - - - name: Update package cache - apt: - update_cache: yes - - - name: Update all packages to the latest version - no_log: "{{DEBUG|default(true)}}" - apt: - upgrade: dist - - name: Adding defined users (optional) when: users is defined user: @@ -109,16 +63,73 @@ key: "https://github.com/{{item}}.keys" with_items: "{{sshkeys}}" - - name: Make sure sysadmin has sudo access - lineinfile: - path: /etc/sudoers.d/90_sysadmin - line: 'sysadmin ALL=(ALL) NOPASSWD:ALL' - create: yes - - name: Ensure /home/sysadmin is owned by sysadmin user (recursive) shell: chown -R sysadmin /home/sysadmin when: users is defined + - name: Allow user to have passwordless sudo + copy: + dest: "/etc/sudoers.d/90_sysadmin" + content: "sysadmin ALL=(ALL) NOPASSWD:ALL\n" + owner: root + group: root + mode: '0440' + +- name: Configure docker servers for echoCTF + hosts: all + gather_facts: true + become: false + remote_user: root + tasks: + + - name: Kill any running pm2 + shell: pm2 kill + no_log: true + ignore_errors: true + + - name: Ensure docker services are stopped + command: service docker stop + no_log: true + ignore_errors: true + + - name: Ensure existing docker service overrides are removed + ansible.builtin.file: + path: "{{item}}" + state: absent + with_items: + - /etc/systemd/system/docker.service.d/dockerd-service-override.conf + - /etc/systemd/system/docker.service.d/override.conf + - /etc/systemd/system/pm2-root.service.d/pm2-root-service-override.conf + - /etc/systemd/system/pm2-root.service.d/override.conf + + - name: Remove any existing /etc/docker/daemon.json + ansible.builtin.file: + path: /etc/docker/daemon.json + state: absent + + - name: Add apt keys + when: aptKeys is defined + apt_key: + url: "{{item.key}}" + state: "{{item.state}}" + with_items: "{{aptKeys}}" + + - name: Add apt repositories + when: aptRepos is defined + apt_repository: + repo: "{{item.repo}}" + state: "{{item.state}}" + with_items: "{{aptRepos}}" + + - name: Update package cache + apt: + update_cache: yes + + - name: Update all packages to the latest version + no_log: "{{DEBUG|default(true)}}" + apt: + upgrade: dist + - name: Install post install packages apt: state: latest From a2b7d7447a7b5655c32b2fe717bcc4b6425fb55d Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 14 Jan 2026 14:17:51 +0200 Subject: [PATCH 07/87] dont show personal stats on team events --- frontend/components/Img.php | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/frontend/components/Img.php b/frontend/components/Img.php index f4371ad72..75126b2e1 100644 --- a/frontend/components/Img.php +++ b/frontend/components/Img.php @@ -55,12 +55,22 @@ public static function profile($profile) imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"root@%s:/# ./userinfo --profile %d"),\Yii::$app->sys->offense_domain,$profile->id),$textcolor); imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"username.....: %s"),$profile->owner->username),$greencolor); imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"joined.......: %s"),date("d.m.Y", strtotime($profile->owner->created))),$greencolor); - imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"points.......: %s"),number_format($profile->owner->playerScore->points)),$greencolor); - imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"rank.........: %s"),$profile->owner->playerScore->points == 0 ? "-":$profile->rank->ordinalPlace),$greencolor); - imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"level........: %d / %s"),$profile->experience->id, $profile->experience->name),$greencolor); - imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"flags........: %d"), $profile->totalTreasures),$greencolor); - imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"challenges...: %d / %d first"),$profile->challengesSolverCount, $profile->firstChallengeSolversCount),$greencolor); - imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"headshots....: %d / %d first"),$profile->headshotsCount, $profile->firstHeadshotsCount),$greencolor); + if (\Yii::$app->sys->team_only_leaderboards !== true) + { + imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"points.......: %s"),number_format($profile->owner->playerScore->points)),$greencolor); + imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"rank.........: %s"),$profile->owner->playerScore->points == 0 ? "-":$profile->rank->ordinalPlace),$greencolor); + imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"level........: %d / %s"),$profile->experience->id, $profile->experience->name),$greencolor); + imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"flags........: %d"), $profile->totalTreasures),$greencolor); + imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"challenges...: %d / %d first"),$profile->challengesSolverCount, $profile->firstChallengeSolversCount),$greencolor); + imagestring($image, 6, 200, $lineheight*$i++, sprintf(\Yii::t('app',"headshots....: %d / %d first"),$profile->headshotsCount, $profile->firstHeadshotsCount),$greencolor); + } + else if($profile->owner->teamPlayer) + { + imagestring($image, 6, 200, $lineheight*$i++, \Yii::t('app',"team.........: {team}",['team'=>$profile->owner->team->name]),$greencolor); + imagestring($image, 6, 200, $lineheight*$i++, \Yii::t('app',"team rank....: {rank}",['rank'=>($profile->owner->team->rank !== null ? $profile->owner->team->rank->ordinalPlace : 'empty')]),$greencolor); + imagestring($image, 6, 200, $lineheight*$i++, \Yii::t('app',"team points..: {points,plural,=0{0 pts} =1{# pts} other{# pts}}",['points'=>($profile->owner->team->score !== null ? $profile->owner->team->score->points : 0)]),$greencolor); + imagestring($image, 6, 200, $lineheight*$i++, \Yii::t('app',"contributed..: {points,plural,=0{0 pts} =1{# pts} other{# pts}}",['points'=>($profile->owner->teamStreamPoints->points ?? 0)]),$greencolor); + } imagedestroy($avatar); imagedestroy($cover); imagedestroy($src); From 426c95db281136f88cd2c2df9eaa8659a0ccc819 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 14 Jan 2026 14:18:30 +0200 Subject: [PATCH 08/87] dont show leaderboards on guest view profile on team only events --- frontend/themes/material/profile/guest/index.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/themes/material/profile/guest/index.php b/frontend/themes/material/profile/guest/index.php index ac6ab237d..468b9cf8c 100644 --- a/frontend/themes/material/profile/guest/index.php +++ b/frontend/themes/material/profile/guest/index.php @@ -61,6 +61,7 @@ render('../_profile_tabs',['profile'=>$profile,'game'=>$game,'headshots'=>$headshots]);?> +sys->team_only_leaderboards!==true):?>
+ From 201a4f7a4f2e79fe509fe90111bbd269d80ac5d4 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 14 Jan 2026 14:18:57 +0200 Subject: [PATCH 09/87] team module required for badge generation on team events --- frontend/config/console.php | 105 +++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/frontend/config/console.php b/frontend/config/console.php index 4b146d6c7..61ad81f0e 100644 --- a/frontend/config/console.php +++ b/frontend/config/console.php @@ -1,54 +1,60 @@ 'basic-console', -// 'language' => 'el-GR', - 'sourceLanguage' => 'en-US', - 'basePath' => dirname(__DIR__), - 'bootstrap' => ['log'], - 'controllerNamespace' => 'app\commands', - 'aliases' => [ - '@bower' => '@vendor/bower-asset', - '@npm' => '@vendor/npm-asset', - '@tests' => '@app/tests', +$config = [ + 'id' => 'basic-console', + // 'language' => 'el-GR', + 'sourceLanguage' => 'en-US', + 'basePath' => dirname(__DIR__), + 'bootstrap' => ['log'], + 'controllerNamespace' => 'app\commands', + 'aliases' => [ + '@bower' => '@vendor/bower-asset', + '@npm' => '@vendor/npm-asset', + '@tests' => '@app/tests', + ], + 'modules' => [ + 'team' => [ + 'class' => 'app\modules\team\Module', ], - 'components' => [ - 'i18n' => [ - 'translations' => [ - 'yii' => [ - 'class' => 'yii\i18n\PhpMessageSource', - ], - 'app*' => [ - 'class' => 'yii\i18n\PhpMessageSource', - 'basePath' => '@app/messages', - 'sourceLanguage' => 'en-US', - 'fileMap' => [ - 'app' => 'app.php', - 'app/error' => 'error.php', - ], - ], - ], + ], + + 'components' => [ + 'i18n' => [ + 'translations' => [ + 'yii' => [ + 'class' => 'yii\i18n\PhpMessageSource', ], - 'sys'=> [ - 'class' => 'app\components\Sysconfig', + 'app*' => [ + 'class' => 'yii\i18n\PhpMessageSource', + 'basePath' => '@app/messages', + 'sourceLanguage' => 'en-US', + 'fileMap' => [ + 'app' => 'app.php', + 'app/error' => 'error.php', + ], ], - 'cache' => $cache, - 'log' => [ - 'targets' => [ - [ - 'class' => 'yii\log\FileTarget', - 'levels' => ['error', 'warning'], - ], - ], + ], + ], + 'sys' => [ + 'class' => 'app\components\Sysconfig', + ], + 'cache' => $cache, + 'log' => [ + 'targets' => [ + [ + 'class' => 'yii\log\FileTarget', + 'levels' => ['error', 'warning'], ], - 'db' => $db, + ], ], - 'params' => $params, - /* + 'db' => $db, + ], + 'params' => $params, + /* 'controllerMap' => [ 'fixture' => [ // Fixture generation command line. 'class' => 'yii\faker\FixtureController', @@ -57,13 +63,12 @@ */ ]; -if(YII_ENV_DEV) -{ - // configuration adjustments for 'dev' environment - $config['bootstrap'][]='gii'; - $config['modules']['gii']=[ - 'class' => 'yii\gii\Module', - ]; +if (YII_ENV_DEV) { + // configuration adjustments for 'dev' environment + $config['bootstrap'][] = 'gii'; + $config['modules']['gii'] = [ + 'class' => 'yii\gii\Module', + ]; } return $config; From 2338b8b4e9aa253a26b77b78cafc41e8936e72f5 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 14 Jan 2026 19:17:48 +0200 Subject: [PATCH 10/87] add missing pass from admins --- ansible/runonce/docker-registry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/runonce/docker-registry.yml b/ansible/runonce/docker-registry.yml index 547330003..49b150e46 100755 --- a/ansible/runonce/docker-registry.yml +++ b/ansible/runonce/docker-registry.yml @@ -137,7 +137,7 @@ content: "{{item.content|default(omit)}}" with_items: - { dest: "/etc/pf.conf", src: "../files/pf.conf"} - - { dest: "/etc/service.pf.conf", content: "anchor \"dynamic\"\npass quick inet proto tcp from to port {{registry_bind_port}} label \"service_clients\"\n"} + - { dest: "/etc/service.pf.conf", content: "pass quick from label \"administrators\"\nanchor \"dynamic\"\npass quick inet proto tcp from to port {{registry_bind_port}} label \"service_clients\"\n"} - { dest: "/etc/service_clients.conf", content: "{{registry_targets_cidr}}\n"} - name: Dump administrators PF table (if exists) From ce1ed68ec5ad667f63b016066b2e3dc2a183c127 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 14 Jan 2026 19:20:02 +0200 Subject: [PATCH 11/87] fix the typo in targets_cidr --- ansible/inventories/servers/host_vars/registry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/inventories/servers/host_vars/registry.yml b/ansible/inventories/servers/host_vars/registry.yml index 87ad4d3cc..37fa85f1e 100644 --- a/ansible/inventories/servers/host_vars/registry.yml +++ b/ansible/inventories/servers/host_vars/registry.yml @@ -5,7 +5,7 @@ registry_storage: "/storage" registry_bind_ip: "0.0.0.0" registry_bind_port: "5000" registry_targets_if: if0 -registry_targets_cidr: 10.0.100.0/24 +registry_targets_cidr: 10.0.0.100/24 backups: - { tgz: "/altroot/root.tgz", src: '/root' } - { tgz: "/altroot/etc.tgz", src: '/etc' } From 5b0400a032b25c8b110ecd2bc212054a94d4b612 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Thu, 15 Jan 2026 10:30:16 +0200 Subject: [PATCH 12/87] disable dynamic treasures for sanitycheck --- ansible/Dockerfiles/sanitycheck/variables.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ansible/Dockerfiles/sanitycheck/variables.yml b/ansible/Dockerfiles/sanitycheck/variables.yml index 9766edd51..1762de0cb 100644 --- a/ansible/Dockerfiles/sanitycheck/variables.yml +++ b/ansible/Dockerfiles/sanitycheck/variables.yml @@ -12,6 +12,7 @@ writeup_allowed: 0 headshot_spin: 0 instance_allowed: 0 TargetOndemand: false +dynamic_treasures: 0 container: name: "{{hostname}}" hostname: "{{fqdn}}" From e5f69418cb94de0b059ee9161658001af7eb21d1 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Sun, 18 Jan 2026 13:55:59 +0200 Subject: [PATCH 13/87] support expire, typecast params, catch Throwable instead of exceptions --- backend/commands/CronController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/commands/CronController.php b/backend/commands/CronController.php index ec16a5bc0..9a4b0e5dd 100644 --- a/backend/commands/CronController.php +++ b/backend/commands/CronController.php @@ -225,7 +225,7 @@ public function actionInstancePfTables($dopf = false) /** * Process player private instances */ - public function actionInstances($pfonly = false) + public function actionInstances(bool $pfonly = false,int $expire = 40) { if (file_exists("/tmp/cron-instances.lock")) { echo date("Y-m-d H:i:s ") . "Instances: /tmp/cron-instances.lock exists, skipping execution\n"; @@ -242,7 +242,7 @@ public function actionInstances($pfonly = false) } } - $t = TargetInstance::find()->pending_action(40); + $t = TargetInstance::find()->pending_action($expire); foreach ($t->all() as $val) { try { $ips = []; @@ -298,7 +298,7 @@ public function actionInstances($pfonly = false) if ($pfonly === false) { try { $dc->destroy(); - } catch (\Exception $e) { + } catch (\Throwable $e) { } $dc->pull(); $dc->spin(); @@ -337,7 +337,7 @@ public function actionInstances($pfonly = false) default: printf("Error: Unknown action\n"); } - } catch (\Exception $e) { + } catch (\Throwable $e) { if (method_exists($e, 'getErrorResponse')) echo "Instances:", $e->getErrorResponse()->getMessage(), "\n"; else From 71e42f21510a0b3df2950f3b75dd144fc91465c9 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Sun, 18 Jan 2026 13:56:49 +0200 Subject: [PATCH 14/87] avoid filtering on 0 minutes_ago --- .../modules/infrastructure/models/TargetInstanceQuery.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/modules/infrastructure/models/TargetInstanceQuery.php b/backend/modules/infrastructure/models/TargetInstanceQuery.php index b2551b250..7af994882 100644 --- a/backend/modules/infrastructure/models/TargetInstanceQuery.php +++ b/backend/modules/infrastructure/models/TargetInstanceQuery.php @@ -14,9 +14,11 @@ public function active() return $this->andWhere('[[ip]] IS NOT NULL')->andWhere('[[reboot]]!=2'); } - public function pending_action($minutes_ago = 60) + public function pending_action(int $minutes_ago = 60) { - return $this->andWhere('[[ip]] IS NULL')->orWhere(['>', 'reboot', 0])->orWhere(['<', 'updated_at', new \yii\db\Expression("NOW() - INTERVAL $minutes_ago MINUTE")]); + if($minutes_ago !== 0) + return $this->andWhere('[[ip]] IS NULL')->orWhere(['>', 'reboot', 0])->orWhere(['<', 'updated_at', new \yii\db\Expression("NOW() - INTERVAL $minutes_ago MINUTE")]); + return $this->andWhere('[[ip]] IS NULL')->orWhere(['>', 'reboot', 0]); } From 264df81d1b8d24408bbc56b856233c04798a10bc Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Sun, 18 Jan 2026 13:57:10 +0200 Subject: [PATCH 15/87] only allow mui to fetch identificationFiles --- ansible/templates/nginx.conf.j2 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ansible/templates/nginx.conf.j2 b/ansible/templates/nginx.conf.j2 index f2584dcc6..fc537052e 100644 --- a/ansible/templates/nginx.conf.j2 +++ b/ansible/templates/nginx.conf.j2 @@ -252,6 +252,15 @@ http { set $yii_bootstrap "/index.php"; {% if "mui" not in inventory_hostname %} + {% if mui_ext_ip is defined %} + location /identificationFiles/ { + allow {{ mui_ext_ip }}; + deny all; + + try_files $uri $uri/ =404; + } + {% endif %} + # Rate limit /api to 10 requests/sec # This is way too loose location /api { From 904841d3b4ae53a7b3c38bb4c81fddd8ee32f0c0 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:15:07 +0200 Subject: [PATCH 16/87] fix permissions and allow run from outside PR --- .github/workflows/auto-assign-pr.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auto-assign-pr.yml b/.github/workflows/auto-assign-pr.yml index dcc6fb843..fecb812bc 100644 --- a/.github/workflows/auto-assign-pr.yml +++ b/.github/workflows/auto-assign-pr.yml @@ -1,9 +1,12 @@ name: Auto-Assign PR on: - pull_request: + pull_request_target: types: [opened] +permissions: + issues: write + jobs: assign: runs-on: ubuntu-latest From 10dc3a16d5e7c299240e2de5ac962bc59edc00aa Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:15:31 +0200 Subject: [PATCH 17/87] no need for everyone to have access to the WS --- ansible/files/pui.service.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible/files/pui.service.conf b/ansible/files/pui.service.conf index fb1fa8ea0..9dd62ce24 100644 --- a/ansible/files/pui.service.conf +++ b/ansible/files/pui.service.conf @@ -1,6 +1,8 @@ # Allow users to connect to port 80/443 pass in quick on egress inet proto tcp from {, } to (egress:0) port 8888 rdr-to 127.0.0.1 +pass in quick on interconnect inet proto tcp from (interconnect:network) to (interconnect:0) port 8888 rdr-to 127.0.0.1 + pass quick from label "administrators" pass quick inet proto tcp from to (egress:0) port { 80 , 443 } label "www-moderators" @@ -8,9 +10,7 @@ pass quick inet proto tcp from to (egress:0) port { 80 , 443 } labe # FOR DT OPERATIONS pass in quick inet proto tcp from to port 80 rdr-to 127.0.0.1 port 8080 label "maintenance" pass in quick inet proto tcp from to port 443 rdr-to 127.0.0.1 port 8443 label "maintenance" -block in quick on egress inet proto tcp from to (egress:0) port 8888 pass in on egress inet proto tcp from to port { 80, 443 } label "www-normal" pass in on egress inet proto tcp to port { 80, 443 } label "www-normal" -pass in quick on egress inet proto tcp to (egress:0) port 8888 rdr-to 127.0.0.1 From 924e911f2d7725146eebe29f69ce96d9b35c0e4d Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:16:15 +0200 Subject: [PATCH 18/87] sync with github repo playbook --- ansible/maintenance/sync-github.yml | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 ansible/maintenance/sync-github.yml diff --git a/ansible/maintenance/sync-github.yml b/ansible/maintenance/sync-github.yml new file mode 100644 index 000000000..e413658f2 --- /dev/null +++ b/ansible/maintenance/sync-github.yml @@ -0,0 +1,32 @@ +#!/usr/bin/env ansible-playbook +--- +- name: Update deployed application from Git + hosts: all + tasks: + - name: Ensure repository is up to date + git: + repo: "{{ GITHUB_REPO }}" + dest: "{{ REPO }}" + version: "{{ GITHUB_REPO_BRANCH }}" + force: yes + accept_hostkey: yes + update: yes + notify: restart applications + register: git_result + + - name: Clean untracked files (preserving paths) + shell: git clean -fd {{ '-n' if ansible_check_mode else '' }} {% for p in PRESERVE_PATHS %}-e {{ p }} {% endfor %} + args: + chdir: "{{ REPO }}" + register: clean_result + changed_when: clean_result.stdout != "" + when: git_result.changed and PRESERVE_PATHS is defined + notify: restart applications + + handlers: + - name: restart applications + when: APP_SERVICES is defined + service: + name: "{{ item }}" + state: restarted + loop: "{{ APP_SERVICES }}" From 42167589a4ebbc4fe6d646209b8668e6e4c95a79 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:17:08 +0200 Subject: [PATCH 19/87] use install -d instead to limit the number of command from mkdir/chown --- ansible/runonce/mui.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible/runonce/mui.yml b/ansible/runonce/mui.yml index 068b0df82..4f27b56e5 100755 --- a/ansible/runonce/mui.yml +++ b/ansible/runonce/mui.yml @@ -425,8 +425,8 @@ - name: configure folders and perms command: "{{item}}" with_items: - - "mkdir -p /home/moderatorUI/{{domain_name}}/backend/web/assets" - - "chown -R moderatorUI /home/moderatorUI/{{domain_name}}/backend/web/assets" + - "install -d -o moderatorUI /home/moderatorUI/{{domain_name}}/backend/web/assets" + - "install -d -o moderatorUI /home/moderatorUI/{{domain_name}}/backend/web/identifcationFiles" - "mkdir -p /var/log/cron" - "ln -sf /home/moderatorUI/{{domain_name}}/backend/yii /usr/local/bin/backend" From 22c44083f9c4fce78e3c1619efdbd14975a6c8a0 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:18:04 +0200 Subject: [PATCH 20/87] use the proper ws socket for live installs and use the same user for dt.conf --- ansible/runonce/pui.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ansible/runonce/pui.yml b/ansible/runonce/pui.yml index 9a1ed2bf4..c23f90dd2 100755 --- a/ansible/runonce/pui.yml +++ b/ansible/runonce/pui.yml @@ -417,7 +417,7 @@ src: ../templates/dt.conf.j2 dest: /etc/nginx/dt.conf with_items: - - { user: 'www', domain: '{{domain_name}}', root: "/htdocs" } + - { user: 'participantUI', domain: '{{domain_name}}', root: "/htdocs" } tags: - nginx @@ -466,6 +466,7 @@ - { section: PHP, option: "error_reporting", value: "E_NONE" } - { section: PHP, option: "post_max_size", value: "180M" } - { section: PHP, option: "upload_max_filesize", value: "120M" } + - { section: PHP, option: "allow_url_fopen", value: "On"} - { section: Session, option: "session.save_handler", value: "memcached"} - { section: Session, option: "session.save_path", value: "{{db_ip}}:11211"} - { section: Session, option: "session.gc_maxlifetime", value: "43200" } @@ -487,13 +488,10 @@ - name: configure folders and permissions command: "{{item}}" with_items: - - mkdir -p /home/participantUI/{{domain_name}}/frontend/web/assets - - mkdir -p /home/participantUI/{{domain_name}}/frontend/web/identificationFiles - - mkdir -p /home/participantUI/{{domain_name}}/frontend/web/images/avatars/team + - install -d -o participantUI /home/participantUI/{{domain_name}}/frontend/web/assets + - install -d -o participantUI /home/participantUI/{{domain_name}}/frontend/web/identificationFiles + - install -d -o participantUI /home/participantUI/{{domain_name}}/frontend/web/images/avatars/team - mkdir -p /var/log/cron - - chown -R participantUI /home/participantUI/{{domain_name}}/frontend/web/assets - - chown -R participantUI /home/participantUI/{{domain_name}}/frontend/web/images/avatars/ - - chown -R participantUI /home/participantUI/{{domain_name}}/frontend/web/identificationFiles - ln -sf /home/participantUI/{{domain_name}}/frontend/yii /usr/local/bin/frontend - name: configure participant rc.d @@ -635,10 +633,12 @@ redirect_stderr=true - name: "Update frontend/config/params.php" - replace: - path: "/home/participantUI/{{domain_name}}/frontend/config/params.php" - regexp: 'ws://localhost:8888/ws' - replace: 'wss://{{domain_name}}/ws' + ansible.builtin.lineinfile: + path: "/home/participantUI/{{ domain_name }}/frontend/config/params.php" + search_string: 'ws://localhost:8888/ws' + line: 'wss://{{ domain_name }}/ws' + insertafter: null + insertbefore: null - name: copy nstables script copy: From fc8f59dc3b4958a75d9085dec220cec73861f737 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:18:33 +0200 Subject: [PATCH 21/87] add php ini that is needed for our service publisher --- ansible/runonce/vpngw.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ansible/runonce/vpngw.yml b/ansible/runonce/vpngw.yml index 495bcef33..c59c46cbe 100755 --- a/ansible/runonce/vpngw.yml +++ b/ansible/runonce/vpngw.yml @@ -252,6 +252,19 @@ - name: Activate install php modules shell: "cp /etc/php-{{versions.PHP}}.sample/*.ini /etc/php-{{versions.PHP}}/" + - name: Fixing /etc/php.ini + ini_file: dest=/etc/php-{{versions.PHP}}.ini section={{item.section}} option={{item.option}} value={{item.value}} mode=0644 owner=root group=wheel + with_items: + - { section: PHP, option: "expose_php", value: "Off" } + - { section: PHP, option: "log_errors_max_len", value: 4096 } + - { section: PHP, option: "html_errors", value: "Off" } + - { section: PHP, option: "max_execution_time", value: "60" } + - { section: PHP, option: "max_input_time", value: "120" } + - { section: PHP, option: "memory_limit", value: "256M" } + - { section: PHP, option: "error_reporting", value: "E_NONE" } + - { section: PHP, option: "post_max_size", value: "180M" } + - { section: PHP, option: "upload_max_filesize", value: "120M" } + - { section: PHP, option: "allow_url_fopen", value: "On"} - name: Update my.cnf ini_file: From 7de4ed6296ee0327a55200c46b3c98865b7901c3 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:18:52 +0200 Subject: [PATCH 22/87] make sure we only allow mui to acceess identificationFiles --- ansible/templates/nginx.conf.j2 | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ansible/templates/nginx.conf.j2 b/ansible/templates/nginx.conf.j2 index fc537052e..30552f110 100644 --- a/ansible/templates/nginx.conf.j2 +++ b/ansible/templates/nginx.conf.j2 @@ -253,7 +253,7 @@ http { set $yii_bootstrap "/index.php"; {% if "mui" not in inventory_hostname %} {% if mui_ext_ip is defined %} - location /identificationFiles/ { + location ^~ /identificationFiles/ { allow {{ mui_ext_ip }}; deny all; @@ -345,18 +345,18 @@ http { return 404; } {% endif %} - location /ws { - proxy_pass http://127.0.0.1:8888/ws; - - # WebSocket-specific headers - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - - # Optional: increase timeout for long-lived connections - proxy_read_timeout 86400s; - proxy_send_timeout 86400s; - } + location /ws { + proxy_pass http://127.0.0.1:8888/ws; + + # WebSocket-specific headers + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + + # Optional: increase timeout for long-lived connections + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } {% if captcha is defined %} # Check for requests to /profile/ location ~ ^/profile/\d+$ { From 58a3e773373e3f7b5b0b2c2a1971880242742052 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:19:47 +0200 Subject: [PATCH 23/87] simplify actionInstances() and document action --- backend/commands/CronController.php | 82 ++++++++++++++++------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/backend/commands/CronController.php b/backend/commands/CronController.php index 9a4b0e5dd..9c7a1fa31 100644 --- a/backend/commands/CronController.php +++ b/backend/commands/CronController.php @@ -15,6 +15,7 @@ use app\modules\infrastructure\models\TargetInstanceAudit; use app\modules\infrastructure\models\NetworkTargetSchedule as NTS; use app\modules\gameplay\models\NetworkTarget; +use app\components\helpers\ArrayHelperExtended; /** * @method docker_connect() @@ -159,11 +160,7 @@ public function actionInstancePf($before = 60) case SELF::ACTION_START: case SELF::ACTION_RESTART: if (($val->team_allowed === true || \Yii::$app->sys->team_visible_instances === true) && $val->player->teamPlayer !== null && $val->player->teamPlayer->approved === 1) { - foreach ($val->player->teamPlayer->team->approvedMembers as $teamPlayer) { - if ((int)$teamPlayer->player->last->vpn_local_address !== 0) { - $ips[] = long2ip($teamPlayer->player->last->vpn_local_address); - } - } + $ips = $val->player->teamPlayer->team->approvedMemberIPs; } else if ((int)$val->player->last->vpn_local_address !== 0) { $ips[] = long2ip($val->player->last->vpn_local_address); } @@ -200,15 +197,9 @@ public function actionInstancePfTables($dopf = false) } $team_visible_instances = \Yii::$app->sys->team_visible_instances; if ($val->team_allowed == true || $team_visible_instances === true) { - // get team members if ($val->player->teamPlayer) { $team = $val->player->teamPlayer->team; - foreach ($team->approvedMembers as $member) { - // get IP's of connected players - if (($pIP = $member->player->last->vpn_local_address) !== null) { - $IPs[] = long2ip($pIP); - } - } + $IPs = ArrayHelperExtended::mergeUnique($IPs, $team->approvedMemberIPs); } } @@ -224,8 +215,16 @@ public function actionInstancePfTables($dopf = false) /** * Process player private instances + * + * This command updates the last updated_at field for active instances + * and processes instances that are pending action (eg. reboot, destroy, powerup) + * and updates the PF according to currently online players. + * + * @param bool $pfonly Perform PF operations and skip instance docker actions (default: false) + * @param int $expired_ago Filter instances based that haven't been updated X seconds ago (default: 2400 seconds = 40 minutes) + * @return int Exit code */ - public function actionInstances(bool $pfonly = false,int $expire = 40) + public function actionInstances(bool $pfonly = false, int $expired_ago = 2400) { if (file_exists("/tmp/cron-instances.lock")) { echo date("Y-m-d H:i:s ") . "Instances: /tmp/cron-instances.lock exists, skipping execution\n"; @@ -233,21 +232,13 @@ public function actionInstances(bool $pfonly = false,int $expire = 40) } touch("/tmp/cron-instances.lock"); $action = SELF::ACTION_EXPIRED; - // Get powered instances - $t = TargetInstance::find()->active(); - foreach ($t->all() as $instance) { - if ($instance->player->last->vpn_local_address !== null && $pfonly === false) { - printf("Updating heartbeat [%d: %s for %d: %s]\n", $instance->target_id, $instance->target->name, $instance->player_id, $instance->player->username); - $instance->touch('updated_at'); - } - } - - $t = TargetInstance::find()->pending_action($expire); + $t = TargetInstance::find()->pending_action($expired_ago); foreach ($t->all() as $val) { try { $ips = []; $dc = new DockerContainer($val->target); - $dc->timeout = ($val->server->timeout ? $val->server->timeout : 2000); + $dc->timeout = ($val->server->timeout ? $val->server->timeout : 10000); + if ($val->target->targetVolumes !== null) $dc->targetVolumes = $val->target->targetVolumes; @@ -279,17 +270,18 @@ public function actionInstances(bool $pfonly = false,int $expire = 40) $dc->server = $val->server->connstr; $dc->net = $val->server->network; - if ($val->ip == null) { - echo date("Y-m-d H:i:s ") . "Starting"; - $action = SELF::ACTION_START; + if ($val->reboot === 2) { + echo date("Y-m-d H:i:s ") . "Destroying"; + $action = SELF::ACTION_DESTROY; } else if ($val->reboot === 1) { echo date("Y-m-d H:i:s ") . "Restarting"; $action = SELF::ACTION_RESTART; - } else if ($val->reboot === 2) { - echo date("Y-m-d H:i:s ") . "Destroying"; - $action = SELF::ACTION_DESTROY; + } else if ($val->ip == null && $val->reboot == 0) { + echo date("Y-m-d H:i:s ") . "Starting"; + $action = SELF::ACTION_START; } else { echo date("Y-m-d H:i:s ") . "Expiring"; + $action = SELF::ACTION_EXPIRED; } printf(" %s for %s (%s) at %s\n", $val->target->name, $val->player->username, $dc->name, $val->server->name); switch ($action) { @@ -304,11 +296,7 @@ public function actionInstances(bool $pfonly = false,int $expire = 40) $dc->spin(); } if (($val->team_allowed === true || \Yii::$app->sys->team_visible_instances === true) && $val->player->teamPlayer && $val->player->teamPlayer->approved === 1) { - foreach ($val->player->teamPlayer->team->approvedMembers as $teamPlayer) { - if ((int)$teamPlayer->player->last->vpn_local_address !== 0) { - $ips[] = long2ip($teamPlayer->player->last->vpn_local_address); - } - } + $ips = $val->player->teamPlayer->team->approvedMemberIPs; } else if ((int)$val->player->last->vpn_local_address !== 0) { $ips[] = long2ip($val->player->last->vpn_local_address); } @@ -317,8 +305,10 @@ public function actionInstances(bool $pfonly = false,int $expire = 40) $val->ipoctet = $dc->container->getNetworkSettings()->getNetworks()->{$val->server->network}->getIPAddress(); Pf::add_table_ip($dc->name, $val->ipoctet, true); $val->reboot = 0; - if ($pfonly === false) + if ($pfonly === false) { + $val->updated_at = new \yii\db\Expression('NOW()'); $val->save(); + } break; case SELF::ACTION_EXPIRED: @@ -339,12 +329,28 @@ public function actionInstances(bool $pfonly = false,int $expire = 40) } } catch (\Throwable $e) { if (method_exists($e, 'getErrorResponse')) - echo "Instances:", $e->getErrorResponse()->getMessage(), "\n"; + echo "Instances: ", $e->getErrorResponse()->getMessage(), "\n"; else - echo "Instances:", $e->getMessage(), "\n"; + echo "Instances: ", $e->getMessage(), "\n"; + if (getenv('DEBUG', true) !== false) + \Yii::error($e); } + unset($dc); + usleep(100); } // end foreach $this->actionInstancePfTables(true); + + if ($pfonly === false) { + try { + $t = TargetInstance::find()->active()->withApprovedMemberHeartbeat()->last_updated(round($expired_ago / 2)); + foreach ($t->all() as $instance) { + printf("Updating heartbeat [%d: %s for %d: %s]\n", $instance->target_id, $instance->target->name, $instance->player_id, $instance->player->username); + $instance->touch('updated_at'); + } + } catch (\Throwable $e) { + echo "Instrances: Error while touching updated_at. ", $e->getMessage(); + } + } @unlink("/tmp/cron-instances.lock"); } From 9040674fb0d4054bfae3e7aa6eddbdf5750421cd Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:20:14 +0200 Subject: [PATCH 24/87] make sure player register respect status and active --- backend/commands/PlayerController.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/commands/PlayerController.php b/backend/commands/PlayerController.php index 586d94869..ca6040328 100644 --- a/backend/commands/PlayerController.php +++ b/backend/commands/PlayerController.php @@ -182,7 +182,7 @@ public function actionMail($active = false, $email = false, $status = 9, $approv /* Register Users and generate OpenVPN keys and settings */ - public function actionRegister($username, $email, $fullname, $password = false, $player_type = "offense", $active = false, $academic = false, $team_name = false, $approved = 0) + public function actionRegister($username, $email, $fullname, $password = false, $player_type = "offense", bool $active = true, int $status=9, $academic = false, $team_name = false, $approved = 0) { echo "Registering: ", $email, "\n"; $trans = Yii::$app->db->beginTransaction(); @@ -210,16 +210,15 @@ public function actionRegister($username, $email, $fullname, $password = false, $player->password = Yii::$app->security->generatePasswordHash($password); $player->active = intval($active); - $player->status = 10; + $player->status = $status; $player->auth_key = Yii::$app->security->generateRandomString(); if (!$player->saveWithSsl()) { - if (!$player->active && intval(\Yii::$app->sys->disable_mailer)==0) { + if (!$player->active && $players->status===9 && intval(\Yii::$app->sys->disable_mailer)==0) { $player->generateEmailVerificationToken(); - $player->status = 9; } - throw new ConsoleException('Failed to save player:' . $player->username, "\n"); + throw new ConsoleException('Failed to save player:' . $player->username. "\n"); } $player->createTeam($team_name, $approved); From b8a953212fa4dee3a60b7bdcad39374059d90b25 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:20:48 +0200 Subject: [PATCH 25/87] Introduce ArrayHelperExtended --- .../helpers/ArrayHelperExtended.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 backend/components/helpers/ArrayHelperExtended.php diff --git a/backend/components/helpers/ArrayHelperExtended.php b/backend/components/helpers/ArrayHelperExtended.php new file mode 100644 index 000000000..b170f8b48 --- /dev/null +++ b/backend/components/helpers/ArrayHelperExtended.php @@ -0,0 +1,27 @@ + Date: Fri, 23 Jan 2026 19:21:40 +0200 Subject: [PATCH 26/87] Add TeamInvite and log errorSummary on errors --- backend/modules/frontend/models/Player.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/modules/frontend/models/Player.php b/backend/modules/frontend/models/Player.php index 97aec5de9..346eb72d3 100644 --- a/backend/modules/frontend/models/Player.php +++ b/backend/modules/frontend/models/Player.php @@ -54,6 +54,7 @@ public function getAuthKey() public function saveWithSsl() { if (!$this->save()) { + Yii::error($this->getErrorSummary(true)); return false; } @@ -63,6 +64,7 @@ public function saveWithSsl() if ($playerSsl->save()) { return $playerSsl->refresh(); } + Yii::error($playerSsl->getErrorSummary(true)); return false; } @@ -144,6 +146,9 @@ public function createTeam($team_name, $approved) if (!$tp->save()) echo Yii::t('app', "Error saving team player\n"); + $ti = new \app\modules\frontend\models\TeamInvite(['team_id' => $tp->id, 'token' => Yii::$app->security->generateRandomString(8)]); + if (!$ti->save()) + echo Yii::t('app', "Error saving team invite\n"); } /** From 978d0f7ed849e3b56a5469bb4338718eb07d1595 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:22:04 +0200 Subject: [PATCH 27/87] re-generate serial until unique --- backend/modules/frontend/models/PlayerSsl.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/modules/frontend/models/PlayerSsl.php b/backend/modules/frontend/models/PlayerSsl.php index 6e8bdae84..0cec4d946 100644 --- a/backend/modules/frontend/models/PlayerSsl.php +++ b/backend/modules/frontend/models/PlayerSsl.php @@ -107,7 +107,9 @@ public function generate() { $CAprivkey=array("file://".$tmpCAprivkey, null); file_put_contents($tmpCAprivkey, Yii::$app->sys->{'CA.key'}); file_put_contents($tmpCAcert, Yii::$app->sys->{'CA.crt'}); - $serial=time(); + do { + $serial=time(); + } while (self::findOne(['serial'=>$serial])!=null); $x509=openssl_csr_sign($csr, $CAcert, $CAprivkey, 3650, array('digest_alg'=>'sha256', 'config'=>Yii::getAlias('@appconfig').'/CA.cnf', 'x509_extensions'=>'usr_cert'), $serial); openssl_csr_export($csr, $csrout); openssl_x509_export($x509, $certout, false); From aea78c3fc32b2bc607fb89f8bd4dafb4c10b9529 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:23:07 +0200 Subject: [PATCH 28/87] add approvedMemberIP's method to team --- backend/modules/frontend/models/Team.php | 28 +++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/backend/modules/frontend/models/Team.php b/backend/modules/frontend/models/Team.php index ce40c889d..10cf9bb84 100644 --- a/backend/modules/frontend/models/Team.php +++ b/backend/modules/frontend/models/Team.php @@ -22,6 +22,7 @@ * @property Player $owner * @property TeamPlayer[] $teamPlayers * @property Player[] $players + * @property string[] $approvedMemberIPs */ class Team extends \yii\db\ActiveRecord { @@ -98,11 +99,12 @@ public function getTeamPlayers() public function getInstances() { - return $this->hasMany(\app\modules\infrastructure\models\TargetInstance::class, ['player_id' => 'player_id']) - ->via('teamPlayers', function($query) { - $query->andWhere(['approved' => 1]); - }); + return $this->hasMany(\app\modules\infrastructure\models\TargetInstance::class, ['player_id' => 'player_id']) + ->via('teamPlayers', function ($query) { + $query->andWhere(['approved' => 1]); + }); } + /** * @return \yii\db\ActiveQuery */ @@ -111,6 +113,22 @@ public function getApprovedMembers() return $this->hasMany(TeamPlayer::class, ['team_id' => 'id'])->andWhere(['approved' => 1]); } + /** + * Get array of all approved members currently online + * + * @return array + */ + public function getApprovedMemberIPs() + { + $ips=[]; + foreach ($this->approvedMembers as $tp) { + if ((int)$tp->player->last->vpn_local_address !== 0) { + $ips[] = long2ip($tp->player->last->vpn_local_address); + } + } + return $ips; + } + /** * @return \yii\db\ActiveQuery */ @@ -197,6 +215,6 @@ public function getInvite() */ public function repopulateStream() { - return \Yii::$app->db->createCommand('CALL repopulate_team_stream(:tid)',[':tid'=>$this->id])->execute(); + return \Yii::$app->db->createCommand('CALL repopulate_team_stream(:tid)', [':tid' => $this->id])->execute(); } } From f30dd643aeec0dee7ce01f9ecf7f90dc37f82c12 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:23:33 +0200 Subject: [PATCH 29/87] fix targetInstanceQuery withApprovedMemberHeartbeat --- .../models/TargetInstanceQuery.php | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/backend/modules/infrastructure/models/TargetInstanceQuery.php b/backend/modules/infrastructure/models/TargetInstanceQuery.php index 7af994882..da1067901 100644 --- a/backend/modules/infrastructure/models/TargetInstanceQuery.php +++ b/backend/modules/infrastructure/models/TargetInstanceQuery.php @@ -9,16 +9,50 @@ */ class TargetInstanceQuery extends \yii\db\ActiveQuery { + /** + * Filters TargetInstances to those that are active + * and whose team has at least one approved member with vpn_local_address != 0 + */ + public function withApprovedMemberHeartbeat() + { + return $this + // join to the team_instance player with teamPlayer + ->innerJoin(['tp' => 'team_player'], 'target_instance.player_id = tp.player_id AND tp.approved = 1') + // join to the team + ->innerJoin(['t' => 'team'], 'tp.team_id = t.id') + // join to approved members of the team + ->innerJoin(['am' => 'team_player'], 'am.team_id = t.id AND am.approved = 1') + // join approved member's last + ->innerJoin(['al' => 'player_last'], 'al.id = am.player_id and al.vpn_local_address is not null') + // ensure the approved member has a vpn_local_address + ->distinct(); + } + public function active() { - return $this->andWhere('[[ip]] IS NOT NULL')->andWhere('[[reboot]]!=2'); + return $this->andWhere('target_instance.[[ip]] IS NOT NULL')->andWhere('target_instance.[[reboot]]!=2'); + } + + public function last_updated(int $seconds_ago = 1) + { + return $this->andWhere(['<', 'target_instance.[[updated_at]]', new \yii\db\Expression("NOW() - INTERVAL $seconds_ago SECOND")]); } - public function pending_action(int $minutes_ago = 60) + public function pending_action(int $seconds_ago = 1) { - if($minutes_ago !== 0) - return $this->andWhere('[[ip]] IS NULL')->orWhere(['>', 'reboot', 0])->orWhere(['<', 'updated_at', new \yii\db\Expression("NOW() - INTERVAL $minutes_ago MINUTE")]); - return $this->andWhere('[[ip]] IS NULL')->orWhere(['>', 'reboot', 0]); + return $this->addSelect([ + 'target_instance.*', + 'reboot' => new \yii\db\Expression( + "IF(target_instance.updated_at < (NOW() - INTERVAL :seconds SECOND), 2, target_instance.reboot)", + [':seconds' => $seconds_ago] + ), + ]) + ->andWhere([ + 'or', + ['target_instance.ip' => null], + ['>', 'target_instance.reboot', 0], + ['<', 'target_instance.updated_at', new \yii\db\Expression("NOW() - INTERVAL $seconds_ago SECOND")] + ]); } From abe52d67d37e0c23f514cb85fd60d60a6a531fd9 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:24:04 +0200 Subject: [PATCH 30/87] dont apply timezone twice --- backend/modules/settings/models/Sysconfig.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/modules/settings/models/Sysconfig.php b/backend/modules/settings/models/Sysconfig.php index 021aad19f..7d01d8555 100644 --- a/backend/modules/settings/models/Sysconfig.php +++ b/backend/modules/settings/models/Sysconfig.php @@ -56,7 +56,7 @@ public function afterFind() if ($this->val == 0 || $this->val == "") $this->val = ""; else - $this->val = Yii::$app->formatter->asDatetime($this->val, 'php:Y-m-d H:i:s', 'UTC'); + $this->val = date('Y-m-d H:i:s', $this->val); break; default: break; @@ -101,11 +101,9 @@ public function afterSave($insert, $changedAttributes) if ($this->id === 'stripe_webhookLocalEndpoint' && array_key_exists('val', $changedAttributes)) { $oldVal = $changedAttributes['val']; $newVal = $this->val; - if(($u=UrlRoute::findOne(['destination'=>'subscription/default/webhook']))!==NULL) - { - $u->updateAttributes(['source'=>$newVal]); + if (($u = UrlRoute::findOne(['destination' => 'subscription/default/webhook'])) !== NULL) { + $u->updateAttributes(['source' => $newVal]); } - } } From 0c552eb380cd3662c64afc9d2dd603062363a05d Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:27:48 +0200 Subject: [PATCH 31/87] update some default migration settings --- .../m000000_000001_system_settings.php | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/contrib/sample-migrations/m000000_000001_system_settings.php b/contrib/sample-migrations/m000000_000001_system_settings.php index 5b76f0a90..ff7323611 100644 --- a/contrib/sample-migrations/m000000_000001_system_settings.php +++ b/contrib/sample-migrations/m000000_000001_system_settings.php @@ -31,6 +31,10 @@ class m000000_000001_system_settings extends Migration ['id' => "leaderboard_show_zero", 'val' => "0"], ['id' => "leaderboard_visible_after_event_end", 'val' => "1"], ['id' => "leaderboard_visible_before_event_start", 'val' => "0"], + ['id' => "country_rankings", 'val' => "0"], + ['id' => "player_point_rankings", 'val' => "0"], + ['id' => "player_monthly_rankings", 'val' => "0"], + ['id' => 'frontpage_scenario', 'val' => 'Welcome to our lovely event... Edit from backend Content => Frontpage Scenario'], ['id' => "event_end_notification_title", 'val' => "🎉 Our awesome echoCTF finished 🎉"], ['id' => "event_end_notification_body", 'val' => "The awesome echoCTF is over 🎉🎉🎉 Congratulations to you and your team 👏👏👏 Thank you for participating!!!"], @@ -59,23 +63,38 @@ class m000000_000001_system_settings extends Migration ['id' => "team_manage_members", 'val' => "1"], ['id' => "team_required", 'val' => "1"], ['id' => 'team_visible_instances', 'val' => "1"], + ['id' => 'team_only_leaderboards', 'val' => "1"], + ['id' => 'team_encrypted_claims_allowed', 'val' => "1"], + /** * Player settings */ ['id' => "approved_avatar", 'val' => "1"], ['id' => "player_profile", 'val' => "1"], ['id' => "profile_visibility", 'val' => "public"], - ['id' => "require_activation", 'val' => "0"], - ['id' => 'player_require_identification', 'val' => "0"], + ['id' => "require_activation", 'val' => "1"], + ['id' => 'player_require_identification', 'val' => "1"], ['id' => 'all_players_vip', 'val' => "1"], - ['id' => 'player_require_approval', 'val' => "0"], + ['id' => 'player_require_approval', 'val' => "1"], ['id' => 'profile_discord', 'val' => "1"], ['id' => 'profile_echoctf', 'val' => "1"], ['id' => 'profile_github', 'val' => "1"], ['id' => 'profile_settings_fields', 'val' => 'avatar,bio,country,discord,echoctf,email,fullname,github,pending_progress,twitter,username,visibility'], + ['id' => 'avatar_robohash_set', 'val' => 'set3'], + /** * Configuration settings */ + ['id' => 'target_guest_view_deny', 'val' => '1'], + ['id' => 'disable_ondemand_operations', 'val' => '1'], + ['id' => 'module_smartcity_disabled', 'val' => '1'], + ['id' => 'module_speedprogramming_enabled', 'val' => '0'], + ['id' => 'dashboard_news_total_pages', 'val' => '10'], + ['id' => 'dashboard_news_records_per_page', 'val' => '3'], + ['id' => 'force_https_urls', 'val' => '1'], + ['id' => 'subscriptions_menu_show', 'val' => '0'], + ['id' => 'log_failed_claims', 'val' => '1'], + ['id' => 'academic_grouping', 'val' => '0'], ['id' => "challenge_home", 'val' => "uploads/"], ['id' => "dashboard_is_home", 'val' => "1"], @@ -126,8 +145,11 @@ class m000000_000001_system_settings extends Migration */ public function safeUp() { - foreach ($this->news as $entry) + foreach ($this->news as $entry) { + $entry['created_at']=new \yii\db\Expression('NOW()'); + $entry['updated_at']=new \yii\db\Expression('NOW()'); $this->upsert('news', $entry, true); + } // delete not needed url routes foreach ($this->delete_url_routes as $route) { From d659c7978b2dbb4caee2e0daa3382b53d213d06f Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:28:11 +0200 Subject: [PATCH 32/87] private_network and private_network_targets is needed --- contrib/findingsd-federated.sql | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/contrib/findingsd-federated.sql b/contrib/findingsd-federated.sql index ac873b622..7456e9f2a 100644 --- a/contrib/findingsd-federated.sql +++ b/contrib/findingsd-federated.sql @@ -116,6 +116,33 @@ CREATE TABLE `player_ssl` ( UNIQUE KEY `serial` (`serial`) ) ENGINE=FEDERATED DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci CONNECTION='mysql://{{db_user}}:{{db_pass}}@{{db_host}}:3306/{{db_name}}/player_ssl'; +DROP TABLE IF EXISTS `private_network`; +CREATE TABLE `private_network` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `player_id` int(11) unsigned DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + `team_accessible` tinyint(1) DEFAULT NULL, + `created_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx-private_network-player_id` (`player_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci CONNECTION='mysql://{{db_user}}:{{db_pass}}@{{db_host}}:3306/{{db_name}}/private_network'; + +DROP TABLE IF EXISTS `private_network_target`; +CREATE TABLE `private_network_target` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `private_network_id` int(11) DEFAULT NULL, + `target_id` int(11) NOT NULL, + `ip` int(11) unsigned DEFAULT NULL, + `state` smallint(6) unsigned DEFAULT 0, + `server_id` int(11) DEFAULT NULL, + `ipoctet` varchar(15) GENERATED ALWAYS AS (inet_ntoa(`ip`)) VIRTUAL, + PRIMARY KEY (`id`), + UNIQUE KEY `idx-unique-private_network_id-target_id` (`private_network_id`,`target_id`), + KEY `idx-private_network_target-private_network_id` (`private_network_id`), + KEY `idx-private_network_target-server_id` (`server_id`), + KEY `idx-private_network_target-target_id` (`target_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci CONNECTION='mysql://{{db_user}}:{{db_pass}}@{{db_host}}:3306/{{db_name}}/private_network_target'; + DROP TABLE IF EXISTS `debuglogs`; CREATE TABLE debuglogs ( From 6161138754bf01a9e12eda040e31ac4c1245cda3 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:28:49 +0200 Subject: [PATCH 33/87] update triggers to match --- schemas/echoCTF-triggers.sql | 207 +++++++++++++++++++---------------- 1 file changed, 111 insertions(+), 96 deletions(-) diff --git a/schemas/echoCTF-triggers.sql b/schemas/echoCTF-triggers.sql index b558677d4..6f70a6f55 100644 --- a/schemas/echoCTF-triggers.sql +++ b/schemas/echoCTF-triggers.sql @@ -127,38 +127,49 @@ CREATE TRIGGER `tbi_headshot` BEFORE INSERT ON `headshot` FOR EACH ROW END IF; END ;; -DROP TRIGGER IF EXISTS tai_headshot ;; +DROP TRIGGER IF EXISTS `tai_headshot` ;; CREATE TRIGGER `tai_headshot` AFTER INSERT ON `headshot` FOR EACH ROW - thisBegin:BEGIN - DECLARE private_instance int; - IF (@TRIGGER_CHECKS = FALSE) THEN - LEAVE thisBegin; - END IF; - SET private_instance=(SELECT COUNT(*) FROM target_instance WHERE player_id=NEW.player_id AND target_id=NEW.target_id); - IF (SELECT headshot_spin FROM target WHERE id=NEW.target_id)>0 AND private_instance<1 THEN - INSERT IGNORE INTO spin_queue (target_id, player_id,created_at) VALUES (NEW.target_id,NEW.player_id,NOW()); - ELSEIF private_instance>0 THEN - UPDATE target_instance SET reboot=2 WHERE player_id=NEW.player_id AND target_id=NEW.target_id; - END IF; - IF (SELECT count(*) FROM target_ondemand WHERE target_id=NEW.target_id AND state=1)>0 THEN - UPDATE target_ondemand SET heartbeat=(NOW() - INTERVAL 59 MINUTE - INTERVAL 30 SECOND) WHERE target_id=NEW.target_id; - END IF; - UPDATE target_state SET total_headshots=total_headshots+1,timer_avg=(SELECT ifnull(round(avg(timer)),0) FROM headshot WHERE target_id=NEW.target_id) WHERE id=NEW.target_id; - END ;; + thisBegin:BEGIN + DECLARE private_instance int; + DECLARE local_points int; + DECLARE lheadshot_points int; + DECLARE lfirst_headshot_points int; + IF (@TRIGGER_CHECKS = FALSE) THEN + LEAVE thisBegin; + END IF; + SET local_points=0; + SELECT headshot_points,first_headshot_points INTO lheadshot_points,lfirst_headshot_points FROM target WHERE id=NEW.target_id; + SET private_instance=(SELECT COUNT(*) FROM target_instance WHERE player_id=NEW.player_id AND target_id=NEW.target_id); + IF (SELECT headshot_spin FROM target WHERE id=NEW.target_id)>0 AND private_instance<1 THEN + INSERT IGNORE INTO spin_queue (target_id, player_id,created_at) VALUES (NEW.target_id,NEW.player_id,NOW()); + ELSEIF private_instance>0 THEN + UPDATE target_instance SET reboot=2 WHERE player_id=NEW.player_id AND target_id=NEW.target_id; + END IF; + IF (SELECT count(*) FROM target_ondemand WHERE target_id=NEW.target_id AND state=1)>0 THEN + UPDATE target_ondemand SET heartbeat=(NOW() - INTERVAL 59 MINUTE - INTERVAL 30 SECOND) WHERE target_id=NEW.target_id; + END IF; + IF NEW.first = 1 AND lfirst_headshot_points>0 THEN + SET local_points=lfirst_headshot_points; + ELSEIF lheadshot_points>0 THEN + SET local_points=lheadshot_points; + END IF; + INSERT INTO stream (player_id,model,model_id,points,title,message,pubtitle,pubmessage,ts) VALUES (NEW.player_id,'headshot',NEW.target_id,local_points,'','','','',now()); + UPDATE target_state SET total_headshots=total_headshots+1,timer_avg=(SELECT ifnull(round(avg(timer)),0) FROM headshot WHERE target_id=NEW.target_id) WHERE id=NEW.target_id; + END ;; DROP TRIGGER IF EXISTS tau_headshot ;; CREATE TRIGGER `tau_headshot` AFTER UPDATE ON `headshot` FOR EACH ROW - thisBegin:BEGIN - IF (@TRIGGER_CHECKS = FALSE) THEN - LEAVE thisBegin; - END IF; +thisBegin:BEGIN + IF (@TRIGGER_CHECKS = FALSE) THEN + LEAVE thisBegin; + END IF; IF (OLD.rating IS NULL AND NEW.rating IS NOT NULL) OR (OLD.rating IS NOT NULL and NEW.rating!=OLD.rating) THEN UPDATE target_state SET player_rating=(SELECT round(avg(rating)) FROM headshot WHERE target_id=NEW.target_id AND rating>-1) WHERE id=NEW.target_id; END IF; IF (OLD.timer IS NULL AND NEW.timer IS NOT NULL) OR (OLD.timer IS NOT NULL AND NEW.timer!=OLD.timer) THEN - UPDATE target_state SET timer_avg=(SELECT ifnull(round(avg(timer)),0) FROM headshot WHERE target_id=NEW.target_id and timer>60) WHERE id=NEW.target_id; + UPDATE target_state SET timer_avg=(SELECT ifnull(round(avg(timer)),0) FROM headshot WHERE target_id=NEW.target_id and timer>60) WHERE id=NEW.target_id; END IF; - END ;; + END ;; DROP TRIGGER IF EXISTS tad_headshot ;; CREATE TRIGGER `tad_headshot` AFTER DELETE ON `headshot` FOR EACH ROW @@ -214,7 +225,7 @@ CREATE TRIGGER tai_player AFTER INSERT ON player FOR EACH ROW DROP TRIGGER IF EXISTS tau_player ;; CREATE TRIGGER `tau_player` AFTER UPDATE ON `player` FOR EACH ROW - thisBegin:BEGIN +thisBegin:BEGIN DECLARE ltitle VARCHAR(30) DEFAULT 'Joined the platform'; IF (@TRIGGER_CHECKS = FALSE) THEN LEAVE thisBegin; @@ -249,23 +260,23 @@ CREATE TRIGGER `tau_player` AFTER UPDATE ON `player` FOR EACH ROW ELSEIF NEW.status=10 AND (OLD.status=9 OR OLD.status=8) THEN DELETE FROM player_token WHERE player_id=NEW.id AND `type`='email_verification'; END IF; - END ;; +END ;; DROP TRIGGER IF EXISTS tbd_player ;; CREATE TRIGGER `tbd_player` BEFORE DELETE ON `player` FOR EACH ROW - thisBegin:BEGIN - DECLARE tid INT default 0; - IF (@TRIGGER_CHECKS = FALSE) THEN - LEAVE thisBegin; - END IF; - SELECT id INTO tid FROM team where owner_id=OLD.id; - IF tid > 0 THEN - DELETE FROM team_score WHERE team_id=tid; - END IF; - DELETE FROM player_ssl WHERE player_id=OLD.id; - DELETE FROM player_rank WHERE player_id=OLD.id; +thisBegin:BEGIN + DECLARE tid INT default 0; + IF (@TRIGGER_CHECKS = FALSE) THEN + LEAVE thisBegin; + END IF; + SELECT id INTO tid FROM team where owner_id=OLD.id; + IF tid > 0 THEN + DELETE FROM team_score WHERE team_id=tid; + END IF; + DELETE FROM player_ssl WHERE player_id=OLD.id; + DELETE FROM player_rank WHERE player_id=OLD.id; DELETE FROM headshot WHERE player_id=OLD.id; - END ;; +END ;; DROP TRIGGER IF EXISTS tad_player ;; CREATE TRIGGER `tad_player` AFTER DELETE ON `player` FOR EACH ROW @@ -365,7 +376,7 @@ CREATE TRIGGER `tbi_player_finding` BEFORE INSERT ON `player_finding` FOR EACH R DROP TRIGGER IF EXISTS tai_player_finding ;; CREATE TRIGGER `tai_player_finding` AFTER INSERT ON `player_finding` FOR EACH ROW - thisBegin:BEGIN +thisBegin:BEGIN DECLARE local_target_id INT; DECLARE headshoted INT default null; DECLARE min_finding,min_treasure,max_finding,max_treasure, max_val, min_val DATETIME; @@ -391,7 +402,7 @@ CREATE TRIGGER `tai_player_finding` AFTER INSERT ON `player_finding` FOR EACH RO INSERT INTO headshot (player_id,target_id,created_at,timer) VALUES (NEW.player_id,local_target_id,now(),UNIX_TIMESTAMP(max_val)-UNIX_TIMESTAMP(min_val)); END IF; INSERT INTO target_player_state (id,player_id,player_findings,player_points,created_at,updated_at) VALUES (local_target_id,NEW.player_id,1,NEW.points,now(),now()) ON DUPLICATE KEY UPDATE player_findings=player_findings+values(player_findings),player_points=player_points+values(player_points),updated_at=now(); - END ;; +END ;; DROP TRIGGER IF EXISTS tad_player_finding ;; CREATE TRIGGER `tad_player_finding` AFTER DELETE ON `player_finding` FOR EACH ROW @@ -408,25 +419,25 @@ END ;; DROP TRIGGER IF EXISTS tau_player_last ;; CREATE TRIGGER `tau_player_last` AFTER UPDATE ON `player_last` FOR EACH ROW - thisBegin:BEGIN - IF (@TRIGGER_CHECKS = FALSE) THEN - LEAVE thisBegin; - END IF; +thisBegin:BEGIN + IF (@TRIGGER_CHECKS = FALSE) THEN + LEAVE thisBegin; + END IF; - IF (OLD.vpn_local_address IS NULL AND NEW.vpn_local_address IS NOT NULL) THEN - DO memc_set(CONCAT('ovpn:',NEW.id),INET_NTOA(NEW.vpn_local_address)); - DO memc_set(CONCAT('ovpn:',INET_NTOA(NEW.vpn_local_address)),NEW.id); - DO memc_set(CONCAT('ovpn_remote:',NEW.id),INET_NTOA(NEW.vpn_remote_address)); - ELSEIF (OLD.vpn_local_address IS NOT NULL AND NEW.vpn_local_address IS NULL) THEN - DO memc_delete(CONCAT('ovpn:',NEW.id)); - DO memc_delete(CONCAT('ovpn_remote:',NEW.id)); - DO memc_delete(CONCAT('ovpn:',INET_NTOA(OLD.vpn_local_address))); - END IF; + IF (OLD.vpn_local_address IS NULL AND NEW.vpn_local_address IS NOT NULL) THEN + DO memc_set(CONCAT('ovpn:',NEW.id),INET_NTOA(NEW.vpn_local_address)); + DO memc_set(CONCAT('ovpn:',INET_NTOA(NEW.vpn_local_address)),NEW.id); + DO memc_set(CONCAT('ovpn_remote:',NEW.id),INET_NTOA(NEW.vpn_remote_address)); + ELSEIF (OLD.vpn_local_address IS NOT NULL AND NEW.vpn_local_address IS NULL) THEN + DO memc_delete(CONCAT('ovpn:',NEW.id)); + DO memc_delete(CONCAT('ovpn_remote:',NEW.id)); + DO memc_delete(CONCAT('ovpn:',INET_NTOA(OLD.vpn_local_address))); + END IF; - IF (OLD.vpn_local_address IS NULL AND NEW.vpn_local_address IS NOT NULL) OR (OLD.vpn_local_address IS NOT NULL AND NEW.vpn_local_address IS NOT NULL AND NEW.vpn_local_address!=OLD.vpn_local_address) THEN - INSERT INTO `player_vpn_history` (`player_id`,`vpn_local_address`,`vpn_remote_address`) VALUES (NEW.id,NEW.vpn_local_address,NEW.vpn_remote_address); - END IF; - END ;; + IF (OLD.vpn_local_address IS NULL AND NEW.vpn_local_address IS NOT NULL) OR (OLD.vpn_local_address IS NOT NULL AND NEW.vpn_local_address IS NOT NULL AND NEW.vpn_local_address!=OLD.vpn_local_address) THEN + INSERT INTO `player_vpn_history` (`player_id`,`vpn_local_address`,`vpn_remote_address`) VALUES (NEW.id,NEW.vpn_local_address,NEW.vpn_remote_address); + END IF; +END ;; DROP TRIGGER IF EXISTS tai_player_question ;; CREATE TRIGGER `tai_player_question` AFTER INSERT ON `player_question` FOR EACH ROW @@ -533,35 +544,35 @@ CREATE TRIGGER `tbi_player_treasure` BEFORE INSERT ON `player_treasure` FOR EACH DROP TRIGGER IF EXISTS tai_player_treasure ;; CREATE TRIGGER `tai_player_treasure` AFTER INSERT ON `player_treasure` FOR EACH ROW - thisBegin:BEGIN - DECLARE local_target_id INT; - DECLARE headshoted INT default null; +thisBegin:BEGIN + DECLARE local_target_id INT; + DECLARE headshoted INT default null; DECLARE tfindings,pfindings INT; - DECLARE min_finding,min_treasure,max_finding,max_treasure, max_val, min_val DATETIME; + DECLARE min_finding,min_treasure,max_finding,max_treasure, max_val, min_val DATETIME; - IF (@TRIGGER_CHECKS = FALSE) THEN - LEAVE thisBegin; - END IF; + IF (@TRIGGER_CHECKS = FALSE) THEN + LEAVE thisBegin; + END IF; - CALL add_treasure_stream(NEW.player_id,'treasure',NEW.treasure_id,NEW.points); - CALL add_player_treasure_hint(NEW.player_id,NEW.treasure_id); + CALL add_treasure_stream(NEW.player_id,'treasure',NEW.treasure_id,NEW.points); + CALL add_player_treasure_hint(NEW.player_id,NEW.treasure_id); - SET local_target_id=(SELECT target_id FROM treasure WHERE id=NEW.treasure_id); - SET headshoted=(select true as headshoted FROM target as t left join treasure as t2 on t2.target_id=t.id left join finding as t3 on t3.target_id=t.id LEFT JOIN player_treasure as t4 on t4.treasure_id=t2.id and t4.player_id=NEW.player_id left join player_finding as t5 on t5.finding_id=t3.id and t5.player_id=NEW.player_id WHERE t.id=local_target_id GROUP BY t.id HAVING count(distinct t2.id)=count(distinct t4.treasure_id) AND count(distinct t3.id)=count(distinct t5.finding_id)); + SET local_target_id=(SELECT target_id FROM treasure WHERE id=NEW.treasure_id); + SET headshoted=(select true as headshoted FROM target as t left join treasure as t2 on t2.target_id=t.id left join finding as t3 on t3.target_id=t.id LEFT JOIN player_treasure as t4 on t4.treasure_id=t2.id and t4.player_id=NEW.player_id left join player_finding as t5 on t5.finding_id=t3.id and t5.player_id=NEW.player_id WHERE t.id=local_target_id GROUP BY t.id HAVING count(distinct t2.id)=count(distinct t4.treasure_id) AND count(distinct t3.id)=count(distinct t5.finding_id)); SET tfindings=(SELECT count(*) FROM finding WHERE target_id=local_target_id); SET pfindings=(SELECT count(*) FROM player_finding WHERE player_id=NEW.player_id AND finding_id IN (SELECT id FROM finding WHERE target_id=local_target_id)); - IF headshoted IS NOT NULL THEN - SELECT min(ts),max(ts) INTO min_finding,max_finding FROM player_finding WHERE player_id=NEW.player_id AND finding_id IN (SELECT id FROM finding WHERE target_id=local_target_id); - SELECT min(ts),max(ts) INTO min_treasure,max_treasure FROM player_treasure WHERE player_id=NEW.player_id AND treasure_id IN (SELECT id FROM treasure WHERE target_id=local_target_id); - SELECT GREATEST(max_finding, max_treasure), LEAST(min_finding, min_treasure) INTO max_val,min_val; - INSERT INTO headshot (player_id,target_id,created_at,timer) VALUES (NEW.player_id,local_target_id,now(),UNIX_TIMESTAMP(max_val)-UNIX_TIMESTAMP(min_val)); - END IF; - INSERT INTO target_player_state (id,player_id,player_treasures,player_points,created_at,updated_at) VALUES (local_target_id,NEW.player_id,1,NEW.points,now(),now()) ON DUPLICATE KEY UPDATE player_treasures=player_treasures+values(player_treasures),player_points=player_points+values(player_points),updated_at=now(); + IF headshoted IS NOT NULL THEN + SELECT min(ts),max(ts) INTO min_finding,max_finding FROM player_finding WHERE player_id=NEW.player_id AND finding_id IN (SELECT id FROM finding WHERE target_id=local_target_id); + SELECT min(ts),max(ts) INTO min_treasure,max_treasure FROM player_treasure WHERE player_id=NEW.player_id AND treasure_id IN (SELECT id FROM treasure WHERE target_id=local_target_id); + SELECT GREATEST(max_finding, max_treasure), LEAST(min_finding, min_treasure) INTO max_val,min_val; + INSERT INTO headshot (player_id,target_id,created_at,timer) VALUES (NEW.player_id,local_target_id,now(),UNIX_TIMESTAMP(max_val)-UNIX_TIMESTAMP(min_val)); + END IF; + INSERT INTO target_player_state (id,player_id,player_treasures,player_points,created_at,updated_at) VALUES (local_target_id,NEW.player_id,1,NEW.points,now(),now()) ON DUPLICATE KEY UPDATE player_treasures=player_treasures+values(player_treasures),player_points=player_points+values(player_points),updated_at=now(); IF tfindings > 0 AND pfindings = 0 THEN INSERT INTO abuser (player_id,title,reason,model,model_id,created_at,updated_at) VALUES (NEW.player_id,'Claim flag before finding','tai_player_treasure','treasure',NEW.treasure_id,NOW(),NOW()); END IF; - END ;; +END ;; DROP TRIGGER IF EXISTS tbi_profile ;; CREATE TRIGGER `tbi_profile` BEFORE INSERT ON `profile` FOR EACH ROW @@ -611,29 +622,29 @@ END ;; DROP TRIGGER IF EXISTS tai_stream ;; CREATE TRIGGER `tai_stream` AFTER INSERT ON `stream` FOR EACH ROW - thisBegin:BEGIN - DECLARE lteam_id INT; - IF (@TRIGGER_CHECKS = FALSE) THEN - LEAVE thisBegin; - END IF; - IF NEW.points>0 THEN - INSERT INTO player_score (player_id,points) VALUES (NEW.player_id,NEW.points) ON DUPLICATE KEY UPDATE points=points+values(points); - END IF; - SELECT team_id INTO lteam_id FROM team_player WHERE player_id=NEW.player_id AND approved=1; - IF lteam_id IS NOT NULL THEN +thisBegin:BEGIN + DECLARE lteam_id INT; + IF (@TRIGGER_CHECKS = FALSE) THEN + LEAVE thisBegin; + END IF; + IF NEW.points>0 THEN + INSERT INTO player_score (player_id,points) VALUES (NEW.player_id,NEW.points) ON DUPLICATE KEY UPDATE points=points+values(points); + END IF; + SELECT team_id INTO lteam_id FROM team_player WHERE player_id=NEW.player_id AND approved=1; + IF lteam_id IS NOT NULL THEN INSERT IGNORE INTO team_stream (stream_id,player_id,team_id,model,model_id,points,ts) VALUES (NEW.id,NEW.player_id,lteam_id,NEW.model,NEW.model_id,NEW.points,NEW.ts); - END IF; - END ;; + END IF; +END ;; DROP TRIGGER IF EXISTS tad_stream ;; CREATE TRIGGER `tad_stream` AFTER DELETE ON `stream` FOR EACH ROW - thisBegin:BEGIN - IF (@TRIGGER_CHECKS = FALSE) THEN - LEAVE thisBegin; - END IF; - INSERT INTO player_score (player_id,points) VALUES (OLD.player_id,-OLD.points) ON DUPLICATE KEY UPDATE points=if(points+values(points)<0,0,points+values(points)); +thisBegin:BEGIN + IF (@TRIGGER_CHECKS = FALSE) THEN + LEAVE thisBegin; + END IF; + INSERT INTO player_score (player_id,points) VALUES (OLD.player_id,-OLD.points) ON DUPLICATE KEY UPDATE points=if(points+values(points)<0,0,points+values(points)); DELETE FROM team_stream where stream_id=OLD.id; - END ;; +END ;; DROP TRIGGER IF EXISTS tai_sysconfig ;; CREATE TRIGGER `tai_sysconfig` AFTER INSERT ON `sysconfig` FOR EACH ROW @@ -976,12 +987,16 @@ CREATE TRIGGER `tad_writeup` AFTER DELETE ON `writeup` FOR EACH ROW DROP TRIGGER IF EXISTS tai_player_target_help ;; CREATE TRIGGER tai_player_target_help AFTER INSERT ON player_target_help FOR EACH ROW - thisBegin:BEGIN +thisBegin:BEGIN + DECLARE stream_player_target_help INT; IF (@TRIGGER_CHECKS = FALSE) THEN LEAVE thisBegin; END IF; - INSERT INTO stream (player_id,model,model_id,points,title,message,pubtitle,pubmessage,ts) VALUES (NEW.player_id,'player_target_help',NEW.target_id,0,'','','','',now()); - END ;; + SET stream_player_target_help=memc_get('sysconfig:stream_player_target_help'); + IF stream_player_target_help IS NOT NULL and stream_player_target_help=1 THEN + INSERT INTO stream (player_id,model,model_id,points,title,message,pubtitle,pubmessage,ts) VALUES (NEW.player_id,'player_target_help',NEW.target_id,0,'Activated writeups for {target}','','','',now()); + END IF; +END ;; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; From 98da78b159c7479203531638c1b38bc2dbccc3fe Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:29:04 +0200 Subject: [PATCH 34/87] update routines to match --- schemas/echoCTF-routines.sql | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/schemas/echoCTF-routines.sql b/schemas/echoCTF-routines.sql index a289d74c9..7eb951c95 100644 --- a/schemas/echoCTF-routines.sql +++ b/schemas/echoCTF-routines.sql @@ -403,23 +403,16 @@ BEGIN UPDATE team_score SET points=0 WHERE team_id=tid; DELETE FROM team_stream WHERE team_id=tid; INSERT INTO team_stream (stream_id,player_id,team_id,model,model_id,points,ts) - SELECT id, player_id, tid, model, model_id, points, ts - FROM ( - SELECT s.*, - ROW_NUMBER() OVER ( - PARTITION BY model, model_id - ORDER BY ts ASC, id ASC - ) AS rn - FROM stream s - WHERE model != 'user' - AND player_id IN ( - SELECT player_id - FROM team_player - WHERE team_id = tid AND approved=1 - ) - ) t - WHERE rn = 1 - ORDER BY id, ts; + SELECT id, player_id, tid, model, model_id, points, ts + FROM ( + SELECT s.*, + ROW_NUMBER() OVER (PARTITION BY model, model_id ORDER BY ts ASC, id ASC) AS rn + FROM stream s + WHERE model != 'user' + AND player_id IN (SELECT player_id FROM team_player WHERE team_id = tid AND approved=1) + ) t + WHERE rn = 1 + ORDER BY id, ts; IF `_rollback` THEN ROLLBACK; ELSE From fca1e5190cb5af85ca0cb27d2059aee67b9f7a86 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:30:14 +0200 Subject: [PATCH 35/87] if team_visible_instances or team_subscription show only spawn team instance --- .../targetcardactions/TargetCardActions.php | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/frontend/widgets/targetcardactions/TargetCardActions.php b/frontend/widgets/targetcardactions/TargetCardActions.php index a315e5b67..a34d0c071 100644 --- a/frontend/widgets/targetcardactions/TargetCardActions.php +++ b/frontend/widgets/targetcardactions/TargetCardActions.php @@ -92,7 +92,6 @@ public function prep_instance_actions() 'linkOptions' => $linkOptions ]; } elseif ($this->target_instance === NULL) { - $this->target_actions[] = [ 'label' => \Yii::t('app', '  Spawn a private instance'), 'url' => Url::to(['/target/default/spawn', 'id' => $this->model->id]), @@ -100,16 +99,20 @@ public function prep_instance_actions() 'linkOptions' => ArrayHelper::merge($this->linkOptions, ['data-confirm' => \Yii::t('app', 'You are about to spawn a private instance of this target. Once booted, this instance will only be accessible by you and its IP will become visible here.')]) ]; // display start team instance - if (\Yii::$app->user->identity->subscription !== null && \Yii::$app->user->identity->subscription->active > 0 && \Yii::$app->user->identity->subscription->product !== null) { - $metadata = json_decode(\Yii::$app->user->identity->subscription->product->metadata); - if (isset($metadata->team_subscription) && boolval($metadata->team_subscription) === true) { - $this->target_actions[] = [ - 'label' => \Yii::t('app', '  Spawn a team instance'), - 'url' => Url::to(['/target/default/spawn', 'id' => $this->model->id, 'team' => true]), - 'options' => ['style' => 'white-space: nowrap;'], - 'linkOptions' => ArrayHelper::merge($this->linkOptions, ['data-confirm' => \Yii::t('app', 'You are about to spawn an instance of this target for your entire team. Once booted, this instance will only be accessible by you and your team. The IP will become visible here.')]) - ]; - } + if ( + \Yii::$app->sys->team_visible_instances === true || (\Yii::$app->user->identity->subscription !== null + && \Yii::$app->user->identity->subscription->active > 0 + && \Yii::$app->user->identity->subscription->product !== null + && ($metadata = json_decode(\Yii::$app->user->identity->subscription->product->metadata) !== null + && isset($metadata->team_subscription) && boolval($metadata->team_subscription) === true)) + ) { + $key = \Yii::$app->sys->team_visible_instances === true ? 0 : null; + $this->target_actions[$key] = [ + 'label' => \Yii::t('app', '  Spawn a team instance'), + 'url' => Url::to(['/target/default/spawn', 'id' => $this->model->id, 'team' => true]), + 'options' => ['style' => 'white-space: nowrap;'], + 'linkOptions' => ArrayHelper::merge($this->linkOptions, ['data-confirm' => \Yii::t('app', 'You are about to spawn an instance of this target for your entire team. Once booted, this instance will only be accessible by you and your team. The IP will become visible here.')]) + ]; } } elseif ($this->target_instance->target_id !== $this->model->id) { $this->target_actions[] = [ @@ -168,7 +171,7 @@ public function prep_private_network_target_actions() if ($this->model->private_network_id === null) return; - if(\app\modules\network\models\PrivateNetwork::findOne(['id'=>$this->model->private_network_id,'player_id'=>Yii::$app->user->id])!==NULL) + if (\app\modules\network\models\PrivateNetwork::findOne(['id' => $this->model->private_network_id, 'player_id' => Yii::$app->user->id]) !== NULL) if ($this->model->ipoctet !== '0.0.0.0' && $this->model->ipoctet !== NULL && intval($this->model->state) === 0) { $this->target_actions[] = [ 'label' => \Yii::t('app', '  Reboot target'), From 2c85024768d57830e1da349fd13b87307a49782c Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Fri, 23 Jan 2026 19:30:47 +0200 Subject: [PATCH 36/87] use inviteOrCreate instead --- .../material/modules/team/views/default/_team_card.php | 6 +++--- .../themes/material/modules/team/views/default/view.php | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/themes/material/modules/team/views/default/_team_card.php b/frontend/themes/material/modules/team/views/default/_team_card.php index 08939f0de..8b540334c 100644 --- a/frontend/themes/material/modules/team/views/default/_team_card.php +++ b/frontend/themes/material/modules/team/views/default/_team_card.php @@ -89,9 +89,9 @@
user->identity->isAdmin || (Yii::$app->user->identity->team && !$model->inviteonly || ($invite === false && $listing === true))) : ?> $model->token], ['class' => 'btn block text-dark text-bold orbitron' . (!$model->inviteonly ? ' btn-info' : ' btn-warning')]) ?> - getTeamPlayers()->count()) < Yii::$app->sys->members_per_team && !Yii::$app->user->identity->team && !$model->locked && $model->invite) : ?> - $model->invite->token], ['class' => 'btn block btn-primary text-dark text-bold orbitron', 'data-method' => 'POST', 'data' => ['confirm' => 'You are about to join this team. Your membership will have to be confirmed by the team captain.', 'method' => 'POST']]) ?> + getTeamPlayers()->count()) < Yii::$app->sys->members_per_team && !Yii::$app->user->identity->team && !$model->locked && $model->inviteOrCreate) : ?> + $model->inviteOrCreate->token], ['class' => 'btn block btn-primary text-dark text-bold orbitron', 'data-method' => 'POST', 'data' => ['confirm' => 'You are about to join this team. Your membership will have to be confirmed by the team captain.', 'method' => 'POST']]) ?>

- \ No newline at end of file + diff --git a/frontend/themes/material/modules/team/views/default/view.php b/frontend/themes/material/modules/team/views/default/view.php index 23e71ed62..4a34bd3be 100644 --- a/frontend/themes/material/modules/team/views/default/view.php +++ b/frontend/themes/material/modules/team/views/default/view.php @@ -19,10 +19,10 @@

[name) ?>]

getTeamPlayers()->count() < Yii::$app->sys->members_per_team): ?>

- owner_id === Yii::$app->user->id || ($team->invite && !$team->inviteonly)): ?> + owner_id === Yii::$app->user->id || ($team->inviteOrCreate && !$team->inviteonly)): ?> owner_id === Yii::$app->user->id) $class .= ' copy-to-clipboard'; ?> - $team->invite->token], 'https'), Url::to(['/team/default/invite', 'token' => $team->invite->token], 'https'), ['class' => $class, 'swal-data' => 'Copied to clipboard!']); ?> + $team->inviteOrCreate->token], 'https'), Url::to(['/team/default/invite', 'token' => $team->inviteOrCreate->token], 'https'), ['class' => $class, 'swal-data' => 'Copied to clipboard!']); ?> recruitment) ?> @@ -179,8 +179,8 @@

+registerJs( + 'hljs.highlightAll();', + $this::POS_READY, + 'markdown-highlighter' +); +?> From fe45c55896197faae7ba57cd4f29f5e593d4b0a2 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Mon, 26 Jan 2026 13:48:13 +0200 Subject: [PATCH 71/87] document websockets a bit --- docs/Websockets.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/Websockets.md diff --git a/docs/Websockets.md b/docs/Websockets.md new file mode 100644 index 000000000..3b46b4756 --- /dev/null +++ b/docs/Websockets.md @@ -0,0 +1,59 @@ +# Websockets service + +echoCTF.RED provides player updates to the live players through the use of [ws-server](https://github.com/echoCTF/ws-server). + +The services that want to communicate an update to the current live players submit their events through the HTTP service of ws-server. + +The system can send messages to a specific player or all connected players through the `/publish` and `/broadcast` endpoints respectively. + +Currently the following events are implemented: + +* `notification`: Sends a direct notification, Alert or Sweetalerts. +* `apiNotifications`: Tell the clients to perform an update of their in-page notifications. +* `target`: Update the target card if currently visible + +## Examples + +* Notify all users to perform an `apiNotifications()` js call. Effectively fetch the latest notifications through ajax. + +```shell +curl -X POST "http://localhost:8888/broadcast" \ + -H "Authorization: Bearer YOURTOKEN" \ + -H "Content-Type: application/json" \ + -d '{ "event": "apiNotifications" }' +``` + +* Send a SweetAlert (`"type": "swap:info"`) notification to player with id `1` + +```shell +curl -X POST "http://localhost:8888/publish" \ + -H "Authorization: Bearer server123token" \ + -H "Content-Type: application/json" \ + -d '{ + "player_id": "1", + "event": "notification", + "payload": + { + "title": "This is a notification", + "body": "This is the notification body", + "type": "swal:info" + } + }' +``` + +Note: Removing the `swal:` prefix from `type` sends a normal bootstrap alert notification. + +* Send an update for to player id `1` for updates on target id `2` + +```shell +curl -X POST "http://localhost:8888/publish" \ + -H "Authorization: Bearer server123token" \ + -H "Content-Type: application/json" \ + -d '{ + "player_id": "1", + "event": "target", + "payload": { "id": "2" } + }' +``` + +This will execute the js code `targetUpdates(2)`. From f57bfc7f6507a4cbf26bbc464200a54b1f9f235f Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:11:09 +0200 Subject: [PATCH 72/87] add login.conf classes --- ansible/files/login.conf.d/memcached | 5 +++++ ansible/files/login.conf.d/mysqld | 5 +++++ ansible/files/login.conf.d/supervisord | 8 ++++++++ ansible/files/login.conf.d/watchdoger | 8 ++++++++ 4 files changed, 26 insertions(+) create mode 100644 ansible/files/login.conf.d/memcached create mode 100644 ansible/files/login.conf.d/mysqld create mode 100644 ansible/files/login.conf.d/supervisord create mode 100644 ansible/files/login.conf.d/watchdoger diff --git a/ansible/files/login.conf.d/memcached b/ansible/files/login.conf.d/memcached new file mode 100644 index 000000000..6c6704d35 --- /dev/null +++ b/ansible/files/login.conf.d/memcached @@ -0,0 +1,5 @@ +memcached:\ + :datasize=2048M:\ + :openfiles-cur=6144:\ + :openfiles-max=20480:\ + :tc=daemon: diff --git a/ansible/files/login.conf.d/mysqld b/ansible/files/login.conf.d/mysqld new file mode 100644 index 000000000..ec90ada63 --- /dev/null +++ b/ansible/files/login.conf.d/mysqld @@ -0,0 +1,5 @@ +mysqld:\ + :datasize=8192M:\ + :openfiles-cur=6144:\ + :openfiles-max=20480:\ + :tc=daemon: diff --git a/ansible/files/login.conf.d/supervisord b/ansible/files/login.conf.d/supervisord new file mode 100644 index 000000000..a91739203 --- /dev/null +++ b/ansible/files/login.conf.d/supervisord @@ -0,0 +1,8 @@ +supervisord:\ + :ignorenologin:\ + :datasize=4096M:\ + :maxproc=infinity:\ + :openfiles-max=10024:\ + :openfiles-cur=2560:\ + :stacksize-cur=18M:\ + :tc=default: diff --git a/ansible/files/login.conf.d/watchdoger b/ansible/files/login.conf.d/watchdoger new file mode 100644 index 000000000..23fab3b31 --- /dev/null +++ b/ansible/files/login.conf.d/watchdoger @@ -0,0 +1,8 @@ +watchdoger:\ + :ignorenologin:\ + :datasize=512M:\ + :maxproc=infinity:\ + :openfiles-max=4096:\ + :openfiles-cur=1024:\ + :stacksize-cur=8M:\ + :tc=default: From b09100d7a5199c464c9e6fee460b528a4bb4d73f Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:13:09 +0200 Subject: [PATCH 73/87] use own class to watchdoger service --- ansible/runonce/db.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/runonce/db.yml b/ansible/runonce/db.yml index 58709d7bd..baaec867b 100755 --- a/ansible/runonce/db.yml +++ b/ansible/runonce/db.yml @@ -425,7 +425,7 @@ content: | [program:watchdoger] user = root - command = /usr/local/bin/watchdoger --file_path /tmp/event_finished --url {{ wsserver.url | default("http://10.7.0.200:8888/broadcast") }} --token {{ wsserver.token | default("server123token") }} + command = su -fm -c watchdoger -s /usr/local/bin/python3 root /usr/local/bin/watchdoger --file_path /tmp/event_finished --url {{ wsserver.url | default("http://10.7.0.200:8888/broadcast") }} --token {{ wsserver.token | default("server123token") }} autorestart = false startretries = 0 stdout_logfile=/var/log/watchdoger.log From 680762969bae11e1f8aba72f811af50a8ced9d48 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:13:45 +0200 Subject: [PATCH 74/87] copy login class files --- ansible/runonce/db.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ansible/runonce/db.yml b/ansible/runonce/db.yml index baaec867b..2e087e5d2 100755 --- a/ansible/runonce/db.yml +++ b/ansible/runonce/db.yml @@ -494,6 +494,16 @@ - { name: "events checker", minute: "*/1", job: "-ns /usr/local/sbin/mysql-events-checker" } - { name: "daily database backups", minute: "0",hour: "23", job: "-ns /usr/local/sbin/database_backup" } + - name: copy login.conf.d login classes + copy: + src: "../files/login.conf.d/{{item}}" + dest: "/etc/login.conf.d/{{item}}" + with_items: + - memcached + - mysqld + - supervisord + - watchdoger + - name: Execute fw_update command: fw_update -a From 51c24c55f2b45c686ed8d041e8e058ff287fd254 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:14:06 +0200 Subject: [PATCH 75/87] increase max execution to 180 seconds --- ansible/runonce/pui.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/runonce/pui.yml b/ansible/runonce/pui.yml index c23f90dd2..3aa933f1d 100755 --- a/ansible/runonce/pui.yml +++ b/ansible/runonce/pui.yml @@ -460,7 +460,7 @@ - { section: PHP, option: "expose_php", value: "Off" } - { section: PHP, option: "log_errors_max_len", value: 4096 } - { section: PHP, option: "html_errors", value: "Off" } - - { section: PHP, option: "max_execution_time", value: "60" } + - { section: PHP, option: "max_execution_time", value: "180" } - { section: PHP, option: "max_input_time", value: "120" } - { section: PHP, option: "memory_limit", value: "256M" } - { section: PHP, option: "error_reporting", value: "E_NONE" } From e3462b06e025b166a7c89f413f48eb95d1f8cdde Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:21:12 +0200 Subject: [PATCH 76/87] improve nginx error handling and rate limit * increase client body * increase fastcgi send/read timeouts * make rate limit configurable * fix error handling --- ansible/templates/nginx.conf.j2 | 49 ++++++++++++++------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/ansible/templates/nginx.conf.j2 b/ansible/templates/nginx.conf.j2 index 30552f110..5bd6c159c 100644 --- a/ansible/templates/nginx.conf.j2 +++ b/ansible/templates/nginx.conf.j2 @@ -22,6 +22,8 @@ http { fastcgi_temp_path /cache/fastcgi_temp 1 2; proxy_temp_path /cache/proxy_temp 1 2; + client_max_body_size 32M; + limit_req_zone $binary_remote_addr zone=Api:1m rate=10r/s; limit_req_zone $binary_remote_addr zone=ResendVerificationEmail:1m rate=10r/s; limit_req_zone $binary_remote_addr zone=RequestPasswordReset:1m rate=10r/s; @@ -35,6 +37,8 @@ http { #tcp_nopush on; keepalive_timeout 65; + fastcgi_send_timeout 180s; + fastcgi_read_timeout 180s; gzip on; gzip_static on; gzip_proxied expired no-cache no-store private auth; @@ -206,27 +210,13 @@ http { } # disable direct access to dt.html - location = /dt.html { - return 404; - } - location = /429.html { - return 404; - } - location = /500.html { - return 404; - } - location = /501.html { - return 404; - } - location = /504.html { - return 404; - } - location = /505.html { - return 404; - } - location ~ /\.ht { - return 404; - } + location = /dt.html { return 404; } + location = /429.html { return 404; } + location = /500.html { return 404; } + location = /501.html { return 404; } + location = /504.html { return 404; } + location = /505.html { return 404; } + location ~ /\.ht { return 404; } location /contrib/ { autoindex on; @@ -260,7 +250,7 @@ http { try_files $uri $uri/ =404; } {% endif %} - + {% if enable_rate_limit is defined %} # Rate limit /api to 10 requests/sec # This is way too loose location /api { @@ -334,6 +324,7 @@ http { index index.php; try_files $uri $uri/ /index.php$is_args$args; } + {% endif %} {% endif %} location / { index index.html index.php; @@ -381,10 +372,10 @@ http { # let yii catch the calls to non-existing PHP files set $fsn /$yii_bootstrap; set $yiiargs r=$request_uri; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; + #if (-f $document_root$fastcgi_script_name){ + # set $fsn $fastcgi_script_name; set $yiiargs $query_string; - } + #} fastcgi_pass {{item.fpm}}; include fastcgi_params; @@ -408,10 +399,10 @@ http { # let yii catch the calls to non-existing PHP files set $fsn /$yii_bootstrap; set $yiiargs r=$request_uri; - if (-f $document_root$fastcgi_script_name){ - set $fsn $fastcgi_script_name; - set $yiiargs $query_string; - } + #if (-f $document_root$fastcgi_script_name){ + # set $fsn $fastcgi_script_name; + # set $yiiargs $query_string; + #} fastcgi_pass {{item.fpm}}; include fastcgi_params; From 2f48ac7edf6d503e28846e712463e0999ed37146 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:23:46 +0200 Subject: [PATCH 77/87] cleaner handling of status lines supports refresh time --- backend/commands/VpnController.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/commands/VpnController.php b/backend/commands/VpnController.php index 0fa8fea47..ed837702a 100644 --- a/backend/commands/VpnController.php +++ b/backend/commands/VpnController.php @@ -120,8 +120,10 @@ public function actionLoad($filepath) $ovpn->conf = file_get_contents($filepath); $ovpn->name = $file; $ovpn->server = gethostbyaddr(gethostbyname(gethostname())); - if (preg_match('/status (.*)/', $conf, $matches) && count($matches) > 1) { - $ovpn->status_log = trim($matches[1]); + if (preg_match('/^status\s+(.+)/', $conf, $matches) && count($matches) > 1) { + $parts = explode(' ', trim($matches[0])); + if(isset($parts[1])) + $ovpn->status_log = trim($parts[1]); } if (preg_match('/management (.*) (.*) (.*)/', $conf, $matches) && count($matches) > 1) { $ovpn->management_ip_octet = $matches[1]; From 0c194b8346d70ac6bf5a164a592ed44886d47d34 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:24:07 +0200 Subject: [PATCH 78/87] add full namespace path --- backend/components/OpenVPN.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/components/OpenVPN.php b/backend/components/OpenVPN.php index a31faf6cb..945de6157 100644 --- a/backend/components/OpenVPN.php +++ b/backend/components/OpenVPN.php @@ -81,7 +81,7 @@ static public function parseStatus(string $location) { if(!file_exists($location)) { - throw new yii\base\UserException("Status file does not exist"); + throw new \yii\base\UserException("Status file does not exist"); } $statusLines=explode("\n",file_get_contents($location)); if(count($statusLines)==0) From 366bf6fe0dae5db3964b15f43e40670aecabb68e Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:27:22 +0200 Subject: [PATCH 79/87] fix permissions for moderators and admins --- .../controllers/NotificationController.php | 13 +- .../content/controllers/CreditsController.php | 196 +++++++------- .../content/controllers/DefaultController.php | 19 +- .../controllers/EmailTemplateController.php | 13 +- .../content/controllers/FaqController.php | 196 +++++++------- .../controllers/InstructionController.php | 196 +++++++------- .../controllers/LayoutOverrideController.php | 205 ++++++++------- .../content/controllers/NewsController.php | 239 ++++++++--------- .../controllers/ObjectiveController.php | 196 +++++++------- .../content/controllers/PagesController.php | 200 ++++++++------- .../content/controllers/RuleController.php | 196 +++++++------- .../controllers/VpnTemplateController.php | 205 ++++++++------- .../frontend/controllers/PlayerController.php | 2 +- .../controllers/CountryController.php | 211 +++++++-------- .../controllers/DisabledRouteController.php | 199 ++++++++------- .../controllers/OpenvpnController.php | 208 ++++++++------- .../PlayerDisabledrouteController.php | 208 ++++++++------- .../controllers/SysconfigController.php | 240 +++++++++--------- .../controllers/UrlRouteController.php | 203 ++++++++------- .../settings/controllers/UserController.php | 189 +++++++------- 20 files changed, 1767 insertions(+), 1567 deletions(-) diff --git a/backend/modules/activity/controllers/NotificationController.php b/backend/modules/activity/controllers/NotificationController.php index 601d6288a..c555cc366 100644 --- a/backend/modules/activity/controllers/NotificationController.php +++ b/backend/modules/activity/controllers/NotificationController.php @@ -21,7 +21,18 @@ class NotificationController extends \app\components\BaseController */ public function behaviors() { - return ArrayHelper::merge(parent::behaviors(), []); + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['create', 'delete', 'rotate'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); } /** diff --git a/backend/modules/content/controllers/CreditsController.php b/backend/modules/content/controllers/CreditsController.php index 091a1084b..80badcd2b 100644 --- a/backend/modules/content/controllers/CreditsController.php +++ b/backend/modules/content/controllers/CreditsController.php @@ -17,107 +17,115 @@ class CreditsController extends \app\components\BaseController /** * {@inheritdoc} */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); - } + public function behaviors() + { + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['update', 'delete'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); + } - /** - * Lists all Credits models. - * @return mixed - */ - public function actionIndex() - { - $searchModel=new CreditsSearch(); - $dataProvider=$searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } + /** + * Lists all Credits models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new CreditsSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - /** - * Displays a single Credits model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } - /** - * Creates a new Credits model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model=new Credits(); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } + /** + * Displays a single Credits model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new Credits model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new Credits(); - /** - * Updates an existing Credits model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model=$this->findModel($id); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Deletes an existing Credits model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing Credits model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Finds the Credits model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Credits the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if(($model=Credits::findOne($id)) !== null) - { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing Credits model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the Credits model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return Credits the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = Credits::findOne($id)) !== null) { + return $model; } + + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } } diff --git a/backend/modules/content/controllers/DefaultController.php b/backend/modules/content/controllers/DefaultController.php index b59cbc9c9..b36cc0caa 100644 --- a/backend/modules/content/controllers/DefaultController.php +++ b/backend/modules/content/controllers/DefaultController.php @@ -15,10 +15,21 @@ class DefaultController extends \app\components\BaseController /** * {@inheritdoc} */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); - } + public function behaviors() + { + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['css-override', 'js-override','menu-items','frontpage-scenario','defense-scenario', 'offense-scenario', 'footer-logos'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); + } /** * Renders the index view for the module diff --git a/backend/modules/content/controllers/EmailTemplateController.php b/backend/modules/content/controllers/EmailTemplateController.php index 607cd2004..ad3e3f88b 100644 --- a/backend/modules/content/controllers/EmailTemplateController.php +++ b/backend/modules/content/controllers/EmailTemplateController.php @@ -20,7 +20,18 @@ class EmailTemplateController extends \app\components\BaseController */ public function behaviors() { - return ArrayHelper::merge(parent::behaviors(), []); + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['update', 'delete','adhoc-mail'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); } /** diff --git a/backend/modules/content/controllers/FaqController.php b/backend/modules/content/controllers/FaqController.php index f02680889..7ca9f053a 100644 --- a/backend/modules/content/controllers/FaqController.php +++ b/backend/modules/content/controllers/FaqController.php @@ -17,107 +17,115 @@ class FaqController extends \app\components\BaseController /** * {@inheritdoc} */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); - } + public function behaviors() + { + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['update', 'delete'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); + } - /** - * Lists all Faq models. - * @return mixed - */ - public function actionIndex() - { - $searchModel=new FaqSearch(); - $dataProvider=$searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } + /** + * Lists all Faq models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new FaqSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - /** - * Displays a single Faq model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } - /** - * Creates a new Faq model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model=new Faq(); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } + /** + * Displays a single Faq model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new Faq model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new Faq(); - /** - * Updates an existing Faq model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model=$this->findModel($id); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Deletes an existing Faq model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing Faq model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Finds the Faq model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Faq the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if(($model=Faq::findOne($id)) !== null) - { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing Faq model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the Faq model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return Faq the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = Faq::findOne($id)) !== null) { + return $model; } + + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } } diff --git a/backend/modules/content/controllers/InstructionController.php b/backend/modules/content/controllers/InstructionController.php index 4f0168db7..576ef5c32 100644 --- a/backend/modules/content/controllers/InstructionController.php +++ b/backend/modules/content/controllers/InstructionController.php @@ -17,107 +17,115 @@ class InstructionController extends \app\components\BaseController /** * {@inheritdoc} */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); - } + public function behaviors() + { + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['update', 'delete'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); + } - /** - * Lists all Instruction models. - * @return mixed - */ - public function actionIndex() - { - $searchModel=new InstructionSearch(); - $dataProvider=$searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } + /** + * Lists all Instruction models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new InstructionSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - /** - * Displays a single Instruction model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } - /** - * Creates a new Instruction model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model=new Instruction(); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } + /** + * Displays a single Instruction model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new Instruction model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new Instruction(); - /** - * Updates an existing Instruction model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model=$this->findModel($id); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Deletes an existing Instruction model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing Instruction model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Finds the Instruction model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Instruction the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if(($model=Instruction::findOne($id)) !== null) - { - return $model; - } - - throw new NotFoundHttpException('The requested page does not exist.'); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing Instruction model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the Instruction model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return Instruction the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = Instruction::findOne($id)) !== null) { + return $model; } + + throw new NotFoundHttpException('The requested page does not exist.'); + } } diff --git a/backend/modules/content/controllers/LayoutOverrideController.php b/backend/modules/content/controllers/LayoutOverrideController.php index 1d8fe4859..3044c40fa 100644 --- a/backend/modules/content/controllers/LayoutOverrideController.php +++ b/backend/modules/content/controllers/LayoutOverrideController.php @@ -15,107 +15,118 @@ */ class LayoutOverrideController extends \app\components\BaseController { - /** - * {@inheritdoc} - */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); + /** + * {@inheritdoc} + */ + public function behaviors() + { + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['update', 'delete'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); + } + + /** + * Lists all LayoutOverride models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new LayoutOverrideSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Displays a single LayoutOverride model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new LayoutOverride model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new LayoutOverride(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Lists all LayoutOverride models. - * @return mixed - */ - public function actionIndex() - { - $searchModel = new LayoutOverrideSearch(); - $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing LayoutOverride model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Displays a single LayoutOverride model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing LayoutOverride model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the LayoutOverride model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return LayoutOverride the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = LayoutOverride::findOne($id)) !== null) { + return $model; } - /** - * Creates a new LayoutOverride model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new LayoutOverride(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } - - /** - * Updates an existing LayoutOverride model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); - } - - /** - * Deletes an existing LayoutOverride model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the LayoutOverride model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return LayoutOverride the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = LayoutOverride::findOne($id)) !== null) { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); - } + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } } diff --git a/backend/modules/content/controllers/NewsController.php b/backend/modules/content/controllers/NewsController.php index 5c8d3bfbc..fa7b3bc22 100644 --- a/backend/modules/content/controllers/NewsController.php +++ b/backend/modules/content/controllers/NewsController.php @@ -17,130 +17,139 @@ class NewsController extends \app\components\BaseController /** * {@inheritdoc} */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[ - 'verbs' => [ - 'class' => VerbFilter::class, - 'actions' => [ - 'discord' => ['POST'], - ], - ], - ]); - } + public function behaviors() + { + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['update', 'delete'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + 'verbs' => [ + 'class' => VerbFilter::class, + 'actions' => [ + 'discord' => ['POST'], + ], + ], + ], parent::behaviors()); + } - /** - * Lists all News models. - * @return mixed - */ - public function actionIndex() - { - $searchModel = new NewsSearch(); - $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } + /** + * Lists all News models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new NewsSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - /** - * Displays a single News model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } - /** - * Creates a new News model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new News(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } + /** + * Displays a single News model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new News model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new News(); - /** - * Updates an existing News model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Deletes an existing News model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing News model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Posts an existing News model to discord webhook - * Upon submission (successful or not), the browser will be redirected to the 'index' page and display an appropriate messages. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDiscord($id) - { - try { - $result=$this->findModel($id)->toDiscord(); - Yii::$app->session->setFlash('success',Yii::t('app','News item got send to the discord news webhook')); - } - catch (\Exception $e) { - Yii::$app->session->setFlash('error',Yii::t('app','Failed to send news item to discord webhook')); - } - return $this->redirect(['index']); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing News model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Posts an existing News model to discord webhook + * Upon submission (successful or not), the browser will be redirected to the 'index' page and display an appropriate messages. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDiscord($id) + { + try { + $result = $this->findModel($id)->toDiscord(); + Yii::$app->session->setFlash('success', Yii::t('app', 'News item got send to the discord news webhook')); + } catch (\Exception $e) { + Yii::$app->session->setFlash('error', Yii::t('app', 'Failed to send news item to discord webhook')); } + return $this->redirect(['index']); + } - /** - * Finds the News model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return News the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = News::findOne($id)) !== null) { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + /** + * Finds the News model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return News the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = News::findOne($id)) !== null) { + return $model; } + + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } } diff --git a/backend/modules/content/controllers/ObjectiveController.php b/backend/modules/content/controllers/ObjectiveController.php index f3fba39c1..497c2c463 100644 --- a/backend/modules/content/controllers/ObjectiveController.php +++ b/backend/modules/content/controllers/ObjectiveController.php @@ -17,107 +17,115 @@ class ObjectiveController extends \app\components\BaseController /** * {@inheritdoc} */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); - } + public function behaviors() + { + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['update', 'delete'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); + } - /** - * Lists all Objective models. - * @return mixed - */ - public function actionIndex() - { - $searchModel=new ObjectiveSearch(); - $dataProvider=$searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } + /** + * Lists all Objective models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new ObjectiveSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - /** - * Displays a single Objective model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } - /** - * Creates a new Objective model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model=new Objective(); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } + /** + * Displays a single Objective model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new Objective model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new Objective(); - /** - * Updates an existing Objective model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model=$this->findModel($id); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Deletes an existing Objective model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing Objective model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Finds the Objective model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Objective the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if(($model=Objective::findOne($id)) !== null) - { - return $model; - } - - throw new NotFoundHttpException('The requested page does not exist.'); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing Objective model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the Objective model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return Objective the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = Objective::findOne($id)) !== null) { + return $model; } + + throw new NotFoundHttpException('The requested page does not exist.'); + } } diff --git a/backend/modules/content/controllers/PagesController.php b/backend/modules/content/controllers/PagesController.php index 4367b5736..e18c88e4a 100644 --- a/backend/modules/content/controllers/PagesController.php +++ b/backend/modules/content/controllers/PagesController.php @@ -8,112 +8,124 @@ use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; use yii\helpers\ArrayHelper; + /** * PagesController implements the CRUD actions for Pages model. */ class PagesController extends \app\components\BaseController { - /** - * {@inheritdoc} - */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); - } + /** + * {@inheritdoc} + */ + public function behaviors() + { + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['update', 'delete'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); + } - /** - * Lists all Pages models. - * @return mixed - */ - public function actionIndex() - { - $searchModel = new PagesSearch(); - $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } + /** + * Lists all Pages models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new PagesSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - /** - * Displays a single Pages model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } - /** - * Creates a new Pages model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new Pages(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } + /** + * Displays a single Pages model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new Pages model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new Pages(); - /** - * Updates an existing Pages model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Deletes an existing Pages model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing Pages model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Finds the Pages model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Pages the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = Pages::findOne($id)) !== null) { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing Pages model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the Pages model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return Pages the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = Pages::findOne($id)) !== null) { + return $model; } + + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } } diff --git a/backend/modules/content/controllers/RuleController.php b/backend/modules/content/controllers/RuleController.php index 5cb141d85..516fde8ea 100644 --- a/backend/modules/content/controllers/RuleController.php +++ b/backend/modules/content/controllers/RuleController.php @@ -17,107 +17,115 @@ class RuleController extends \app\components\BaseController /** * {@inheritdoc} */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); - } + public function behaviors() + { + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['update', 'delete'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); + } - /** - * Lists all Rule models. - * @return mixed - */ - public function actionIndex() - { - $searchModel=new RuleSearch(); - $dataProvider=$searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } + /** + * Lists all Rule models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new RuleSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - /** - * Displays a single Rule model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } - /** - * Creates a new Rule model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model=new Rule(); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } + /** + * Displays a single Rule model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new Rule model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new Rule(); - /** - * Updates an existing Rule model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model=$this->findModel($id); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Deletes an existing Rule model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing Rule model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Finds the Rule model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Rule the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if(($model=Rule::findOne($id)) !== null) - { - return $model; - } - - throw new NotFoundHttpException('The requested page does not exist.'); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing Rule model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the Rule model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return Rule the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = Rule::findOne($id)) !== null) { + return $model; } + + throw new NotFoundHttpException('The requested page does not exist.'); + } } diff --git a/backend/modules/content/controllers/VpnTemplateController.php b/backend/modules/content/controllers/VpnTemplateController.php index df37b5639..4017314f5 100644 --- a/backend/modules/content/controllers/VpnTemplateController.php +++ b/backend/modules/content/controllers/VpnTemplateController.php @@ -15,107 +15,118 @@ */ class VpnTemplateController extends \app\components\BaseController { - /** - * {@inheritdoc} - */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); + /** + * {@inheritdoc} + */ + public function behaviors() + { + return ArrayHelper::merge([ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + '00filtered-actions' => [ + 'actions' => ['update', 'delete'], + 'allow' => true, + 'roles' => ['@'], + ] + ], + ], + ], parent::behaviors()); + } + + /** + * Lists all VpnTemplate models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new VpnTemplateSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Displays a single VpnTemplate model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new VpnTemplate model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new VpnTemplate(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Lists all VpnTemplate models. - * @return mixed - */ - public function actionIndex() - { - $searchModel = new VpnTemplateSearch(); - $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing VpnTemplate model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Displays a single VpnTemplate model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing VpnTemplate model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the VpnTemplate model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return VpnTemplate the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = VpnTemplate::findOne($id)) !== null) { + return $model; } - /** - * Creates a new VpnTemplate model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new VpnTemplate(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } - - /** - * Updates an existing VpnTemplate model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); - } - - /** - * Deletes an existing VpnTemplate model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the VpnTemplate model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return VpnTemplate the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = VpnTemplate::findOne($id)) !== null) { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); - } + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } } diff --git a/backend/modules/frontend/controllers/PlayerController.php b/backend/modules/frontend/controllers/PlayerController.php index 0f4ba589a..30d8f6962 100644 --- a/backend/modules/frontend/controllers/PlayerController.php +++ b/backend/modules/frontend/controllers/PlayerController.php @@ -32,7 +32,7 @@ public function behaviors() 'class' => \yii\filters\AccessControl::class, 'rules' => [ '00filtered-actions' => [ - 'actions' => ['mail', 'approve', 'reject', 'export', 'notify', 'set-deleted', 'disconnect-vpn', 'generate-ssl', 'ajax-search'], + 'actions' => ['mail', 'approve', 'reject', 'export', 'notify', 'set-deleted', 'disconnect-vpn', 'generate-ssl', 'ajax-search','mail-filtered'], 'allow' => true, 'roles' => ['@'], ] diff --git a/backend/modules/settings/controllers/CountryController.php b/backend/modules/settings/controllers/CountryController.php index 0f4a9e51c..5bd83eac0 100644 --- a/backend/modules/settings/controllers/CountryController.php +++ b/backend/modules/settings/controllers/CountryController.php @@ -14,110 +14,121 @@ */ class CountryController extends \app\components\BaseController { - /** - * {@inheritdoc} - */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); + /** + * {@inheritdoc} + */ + public function behaviors() + { + return ArrayHelper::merge(parent::behaviors(), [ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + 'authActions' => [ + 'allow' => true, + 'actions' => ['index', 'view'], + 'roles' => ['@'], + 'matchCallback' => function () { + return \Yii::$app->user->identity->isAdmin; + }, + ], + ], + ], + ]); + } + + /** + * Lists all Country models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new CountrySearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Displays a single Country model. + * @param string $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new Country model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new Country(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Lists all Country models. - * @return mixed - */ - public function actionIndex() - { - $searchModel=new CountrySearch(); - $dataProvider=$searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing Country model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param string $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Displays a single Country model. - * @param string $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing Country model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param string $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the Country model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param string $id + * @return Country the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = Country::findOne($id)) !== null) { + return $model; } - /** - * Creates a new Country model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model=new Country(); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } - - /** - * Updates an existing Country model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param string $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model=$this->findModel($id); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); - } - - /** - * Deletes an existing Country model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param string $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the Country model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param string $id - * @return Country the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if(($model=Country::findOne($id)) !== null) - { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); - } + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } } diff --git a/backend/modules/settings/controllers/DisabledRouteController.php b/backend/modules/settings/controllers/DisabledRouteController.php index b83d419c6..5390a6223 100644 --- a/backend/modules/settings/controllers/DisabledRouteController.php +++ b/backend/modules/settings/controllers/DisabledRouteController.php @@ -17,107 +17,118 @@ class DisabledRouteController extends \app\components\BaseController /** * {@inheritdoc} */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); - } + public function behaviors() + { + return ArrayHelper::merge(parent::behaviors(), [ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + 'authActions' => [ + 'allow' => true, + 'actions' => ['index', 'view'], + 'roles' => ['@'], + 'matchCallback' => function () { + return \Yii::$app->user->identity->isAdmin; + }, + ], + ], + ], + ]); + } - /** - * Lists all DisabledRoute models. - * @return mixed - */ - public function actionIndex() - { - $searchModel=new DisabledRouteSearch(); - $dataProvider=$searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } + /** + * Lists all DisabledRoute models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new DisabledRouteSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - /** - * Displays a single DisabledRoute model. - * @param string $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } - /** - * Creates a new DisabledRoute model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model=new DisabledRoute(); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->route]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } + /** + * Displays a single DisabledRoute model. + * @param string $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new DisabledRoute model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new DisabledRoute(); - /** - * Updates an existing DisabledRoute model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param string $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model=$this->findModel($id); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->route]); - } - - return $this->render('update', [ - 'model' => $model, - ]); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->route]); } - /** - * Deletes an existing DisabledRoute model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param string $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing DisabledRoute model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param string $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->route]); } - /** - * Finds the DisabledRoute model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param string $id - * @return DisabledRoute the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if(($model=DisabledRoute::findOne($id)) !== null) - { - return $model; - } - - throw new NotFoundHttpException('The requested page does not exist.'); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing DisabledRoute model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param string $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the DisabledRoute model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param string $id + * @return DisabledRoute the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = DisabledRoute::findOne($id)) !== null) { + return $model; } + + throw new NotFoundHttpException('The requested page does not exist.'); + } } diff --git a/backend/modules/settings/controllers/OpenvpnController.php b/backend/modules/settings/controllers/OpenvpnController.php index 2cf6ddb77..b14886aac 100644 --- a/backend/modules/settings/controllers/OpenvpnController.php +++ b/backend/modules/settings/controllers/OpenvpnController.php @@ -15,107 +15,121 @@ */ class OpenvpnController extends \app\components\BaseController { - /** - * {@inheritdoc} - */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); + /** + * {@inheritdoc} + */ + public function behaviors() + { + return ArrayHelper::merge(parent::behaviors(), [ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + 'authActions' => [ + 'allow' => true, + 'actions' => ['index', 'view'], + 'roles' => ['@'], + 'matchCallback' => function () { + return \Yii::$app->user->identity->isAdmin; + }, + ], + ], + ], + ]); + } + + /** + * Lists all Openvpn models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new OpenvpnSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Displays a single Openvpn model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new Openvpn model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new Openvpn(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Lists all Openvpn models. - * @return mixed - */ - public function actionIndex() - { - $searchModel = new OpenvpnSearch(); - $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing Openvpn model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Displays a single Openvpn model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing Openvpn model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the Openvpn model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return Openvpn the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = Openvpn::findOne($id)) !== null) { + return $model; } - /** - * Creates a new Openvpn model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new Openvpn(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } - - /** - * Updates an existing Openvpn model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); - } - - /** - * Deletes an existing Openvpn model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the Openvpn model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return Openvpn the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = Openvpn::findOne($id)) !== null) { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); - } + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } } diff --git a/backend/modules/settings/controllers/PlayerDisabledrouteController.php b/backend/modules/settings/controllers/PlayerDisabledrouteController.php index a780accc2..8ed11c0ad 100644 --- a/backend/modules/settings/controllers/PlayerDisabledrouteController.php +++ b/backend/modules/settings/controllers/PlayerDisabledrouteController.php @@ -14,107 +14,121 @@ */ class PlayerDisabledrouteController extends \app\components\BaseController { - /** - * {@inheritdoc} - */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); + /** + * {@inheritdoc} + */ + public function behaviors() + { + return ArrayHelper::merge(parent::behaviors(), [ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + 'authActions' => [ + 'allow' => true, + 'actions' => ['index', 'view'], + 'roles' => ['@'], + 'matchCallback' => function () { + return \Yii::$app->user->identity->isAdmin; + }, + ], + ], + ], + ]); + } + + /** + * Lists all PlayerDisabledroute models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new PlayerDisabledrouteSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Displays a single PlayerDisabledroute model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new PlayerDisabledroute model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new PlayerDisabledroute(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Lists all PlayerDisabledroute models. - * @return mixed - */ - public function actionIndex() - { - $searchModel = new PlayerDisabledrouteSearch(); - $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing PlayerDisabledroute model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Displays a single PlayerDisabledroute model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing PlayerDisabledroute model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the PlayerDisabledroute model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return PlayerDisabledroute the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = PlayerDisabledroute::findOne($id)) !== null) { + return $model; } - /** - * Creates a new PlayerDisabledroute model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new PlayerDisabledroute(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } - - /** - * Updates an existing PlayerDisabledroute model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); - } - - /** - * Deletes an existing PlayerDisabledroute model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); - } - - /** - * Finds the PlayerDisabledroute model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return PlayerDisabledroute the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = PlayerDisabledroute::findOne($id)) !== null) { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); - } + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } } diff --git a/backend/modules/settings/controllers/SysconfigController.php b/backend/modules/settings/controllers/SysconfigController.php index 233701be8..6ff1a10fc 100644 --- a/backend/modules/settings/controllers/SysconfigController.php +++ b/backend/modules/settings/controllers/SysconfigController.php @@ -15,129 +15,137 @@ */ class SysconfigController extends \app\components\BaseController { - /** - * {@inheritdoc} - */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); + /** + * {@inheritdoc} + */ + public function behaviors() + { + return ArrayHelper::merge(parent::behaviors(), [ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + 'authActions' => [ + 'allow' => true, + 'actions' => ['index', 'view'], + 'roles' => ['@'], + 'matchCallback' => function () { + return \Yii::$app->user->identity->isAdmin; + }, + ], + ], + ], + ]); + } + + /** + * Lists all Sysconfig models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new SysconfigSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Creates a new Sysconfig model. + * If creation is successful, the browser will be redirected to the 'index' page. + * @return mixed + */ + public function actionCreate() + { + $model = new Sysconfig(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['index']); } - /** - * Lists all Sysconfig models. - * @return mixed - */ - public function actionIndex() - { - $searchModel=new SysconfigSearch(); - $dataProvider=$searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } - - /** - * Creates a new Sysconfig model. - * If creation is successful, the browser will be redirected to the 'index' page. - * @return mixed - */ - public function actionCreate() - { - $model=new Sysconfig(); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['index']); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } - - /** - * Updates an existing Sysconfig model. - * If update is successful, the browser will be redirected to the 'index' page. - * @param string $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model=$this->findModel($id); - - if($model->load(Yii::$app->request->post()) && $model->save()) - { - Yii::$app->session->setFlash('success', Yii::t('app','{id} updated.',['id'=>$model->id])); - if($id!==$model->id) - { - return $this->redirect(['update','id'=>$model->id]); - } - } - - return $this->render('update', [ - 'model' => $model, - ]); - } - - /** - * Creates/Updates a Sysconfig set model. - * If update is successful, the browser will be redirected to the 'index' page. - * @param string $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionConfigure() - { - $model=new ConfigureForm(); - if($model->load(Yii::$app->request->post()) && $model->save()) - { - Yii::$app->session->setFlash('success',Yii::t('app','Configuration saved')); - return $this->redirect(['configure']); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing Sysconfig model. + * If update is successful, the browser will be redirected to the 'index' page. + * @param string $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + Yii::$app->session->setFlash('success', Yii::t('app', '{id} updated.', ['id' => $model->id])); + if ($id !== $model->id) { + return $this->redirect(['update', 'id' => $model->id]); } - - return $this->render('configure', [ - 'model' => $model, - ]); } - /** - * Deletes an existing Sysconfig model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param string $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Creates/Updates a Sysconfig set model. + * If update is successful, the browser will be redirected to the 'index' page. + * @param string $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionConfigure() + { + $model = new ConfigureForm(); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + Yii::$app->session->setFlash('success', Yii::t('app', 'Configuration saved')); + return $this->redirect(['configure']); } - /** - * Finds the Sysconfig model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param string $id - * @return Sysconfig the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if(($model=Sysconfig::findOne($id)) !== null) - { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app','The requested page does not exist.')); + return $this->render('configure', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing Sysconfig model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param string $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the Sysconfig model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param string $id + * @return Sysconfig the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = Sysconfig::findOne($id)) !== null) { + return $model; } - protected function stripYii() - { - $array=explode("\n", trim(ob_get_clean())); - for($i=0;$i < 3;$i++) array_shift($array); - return array_map('trim', $array); - } + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } + protected function stripYii() + { + $array = explode("\n", trim(ob_get_clean())); + for ($i = 0; $i < 3; $i++) array_shift($array); + return array_map('trim', $array); + } } diff --git a/backend/modules/settings/controllers/UrlRouteController.php b/backend/modules/settings/controllers/UrlRouteController.php index e8a27e15f..446f39a3f 100644 --- a/backend/modules/settings/controllers/UrlRouteController.php +++ b/backend/modules/settings/controllers/UrlRouteController.php @@ -9,112 +9,127 @@ use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; use yii\helpers\ArrayHelper; + /** * UrlRouteController implements the CRUD actions for UrlRoute model. */ class UrlRouteController extends \app\components\BaseController { - /** - * {@inheritdoc} - */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); - } + /** + * {@inheritdoc} + */ + public function behaviors() + { + return ArrayHelper::merge(parent::behaviors(), [ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + 'authActions' => [ + 'allow' => true, + 'actions' => ['index', 'view'], + 'roles' => ['@'], + 'matchCallback' => function () { + return \Yii::$app->user->identity->isAdmin; + }, + ], + ], + ], + ]); + } - /** - * Lists all UrlRoute models. - * @return mixed - */ - public function actionIndex() - { - $searchModel = new UrlRouteSearch(); - $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } + /** + * Lists all UrlRoute models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new UrlRouteSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - /** - * Displays a single UrlRoute model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } - /** - * Creates a new UrlRoute model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model = new UrlRoute(); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('create', [ - 'model' => $model, - ]); - } + /** + * Displays a single UrlRoute model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new UrlRoute model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new UrlRoute(); - /** - * Updates an existing UrlRoute model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model = $this->findModel($id); - - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } - - return $this->render('update', [ - 'model' => $model, - ]); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Deletes an existing UrlRoute model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); - - return $this->redirect(['index']); + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing UrlRoute model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Finds the UrlRoute model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return UrlRoute the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if (($model = UrlRoute::findOne($id)) !== null) { - return $model; - } - - throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing UrlRoute model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the UrlRoute model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return UrlRoute the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = UrlRoute::findOne($id)) !== null) { + return $model; } + + throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.')); + } } diff --git a/backend/modules/settings/controllers/UserController.php b/backend/modules/settings/controllers/UserController.php index cea3f74ba..78d1d6602 100644 --- a/backend/modules/settings/controllers/UserController.php +++ b/backend/modules/settings/controllers/UserController.php @@ -17,105 +17,116 @@ class UserController extends \app\components\BaseController /** * {@inheritdoc} */ - public function behaviors() - { - return ArrayHelper::merge(parent::behaviors(),[]); - } - - /** - * Lists all User models. - * @return mixed - */ - public function actionIndex() - { - $searchModel=new UserSearch(); - $dataProvider=$searchModel->search(Yii::$app->request->queryParams); + public function behaviors() + { + return ArrayHelper::merge(parent::behaviors(), [ + 'access' => [ + 'class' => \yii\filters\AccessControl::class, + 'rules' => [ + 'authActions' => [ + 'allow' => true, + 'actions' => ['index', 'view'], + 'roles' => ['@'], + 'matchCallback' => function () { + return \Yii::$app->user->identity->isAdmin; + }, + ], + ], + ], + ]); + } - return $this->render('index', [ - 'searchModel' => $searchModel, - 'dataProvider' => $dataProvider, - ]); - } - - /** - * Displays a single User model. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionView($id) - { - return $this->render('view', [ - 'model' => $this->findModel($id), - ]); - } + /** + * Lists all User models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new UserSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); - /** - * Creates a new User model. - * If creation is successful, the browser will be redirected to the 'view' page. - * @return mixed - */ - public function actionCreate() - { - $model=new User(); + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } + /** + * Displays a single User model. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } - return $this->render('create', [ - 'model' => $model, - ]); - } + /** + * Creates a new User model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new User(); - /** - * Updates an existing User model. - * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionUpdate($id) - { - $model=$this->findModel($id); - if($model->load(Yii::$app->request->post()) && $model->save()) - { - return $this->redirect(['view', 'id' => $model->id]); - } - return $this->render('update', [ - 'model' => $model, - ]); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } - /** - * Deletes an existing User model. - * If deletion is successful, the browser will be redirected to the 'index' page. - * @param integer $id - * @return mixed - * @throws NotFoundHttpException if the model cannot be found - */ - public function actionDelete($id) - { - $this->findModel($id)->delete(); + return $this->render('create', [ + 'model' => $model, + ]); + } - return $this->redirect(['index']); + /** + * Updates an existing User model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); } + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing User model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); - /** - * Finds the User model based on its primary key value. - * If the model is not found, a 404 HTTP exception will be thrown. - * @param integer $id - * @return User the loaded model - * @throws NotFoundHttpException if the model cannot be found - */ - protected function findModel($id) - { - if(($model=\app\modules\settings\models\User::findOne($id)) !== null) - { - return $model; - } + return $this->redirect(['index']); + } - throw new NotFoundHttpException('The requested page does not exist.'); + /** + * Finds the User model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return User the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = \app\modules\settings\models\User::findOne($id)) !== null) { + return $model; } + + throw new NotFoundHttpException('The requested page does not exist.'); + } } From 42fee4142d88dd3636d157a7a8d66b288adaa743 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:28:29 +0200 Subject: [PATCH 80/87] add a 1 second delay so that the notifications have been send to everyone --- backend/modules/settings/models/Sysconfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/modules/settings/models/Sysconfig.php b/backend/modules/settings/models/Sysconfig.php index 308121e49..605c17b87 100644 --- a/backend/modules/settings/models/Sysconfig.php +++ b/backend/modules/settings/models/Sysconfig.php @@ -70,7 +70,7 @@ public function beforeSave($insert) $Q = sprintf("DROP EVENT IF EXISTS event_end_notification"); \Yii::$app->db->createCommand($Q)->execute(); if (!empty($this->val)) { - $Q = sprintf("CREATE EVENT event_end_notification ON SCHEDULE AT '%s' DO BEGIN INSERT INTO `notification`(player_id,category,title,body,archived) SELECT id,'swal:info',memc_get('sysconfig:event_end_notification_title'),memc_get('sysconfig:event_end_notification_body'),0 FROM player WHERE status=10; DO memc_set('event_finished',1); SELECT 1 INTO OUTFILE '/tmp/event_finished';END", $this->val); + $Q = sprintf("CREATE EVENT event_end_notification ON SCHEDULE AT '%s' DO BEGIN INSERT INTO `notification`(player_id,category,title,body,archived) SELECT id,'swal:info',memc_get('sysconfig:event_end_notification_title'),memc_get('sysconfig:event_end_notification_body'),0 FROM player WHERE status=10; DO memc_set('event_finished',1); SELECT sleep(1) INTO OUTFILE '/tmp/event_finished';END", $this->val); \Yii::$app->db->createCommand($Q)->execute(); $this->val = strtotime($this->val); } else { From a9a7868f69310e446e2146768dc945bee397a2e1 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:28:48 +0200 Subject: [PATCH 81/87] add backend countdown --- backend/views/layouts/main.php | 21 ++++++++++---- backend/web/js/site.js | 51 ++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/backend/views/layouts/main.php b/backend/views/layouts/main.php index 707e5a4dd..75902eb66 100644 --- a/backend/views/layouts/main.php +++ b/backend/views/layouts/main.php @@ -12,12 +12,12 @@ $this->title = Yii::$app->sys->event_name . ' mUI: ' . $this->title; AppAsset::register($this); -$this->registerJsFile('@web/js/hljs/highlight.min.js',[ - 'depends' => [ - \yii\web\JqueryAsset::class - ] +$this->registerJsFile('@web/js/hljs/highlight.min.js', [ + 'depends' => [ + \yii\web\JqueryAsset::class + ] ]); -$this->registerCssFile('@web/js/hljs/styles/a11y-dark.min.css',['depends' => [\yii\web\JqueryAsset::class]]); +$this->registerCssFile('@web/js/hljs/styles/a11y-dark.min.css', ['depends' => [\yii\web\JqueryAsset::class]]); ?> beginPage() ?> @@ -31,6 +31,13 @@ registerCsrfMetaTags() ?> <?= Html::encode($this->title) ?> head() ?> +cache->memcache->get('sysconfig:event_start') !== false && Yii::$app->cache->memcache->get('sysconfig:event_end') !== false): ?> + + @@ -79,6 +86,9 @@

©

+
+

+

@@ -97,6 +107,5 @@ 'markdown-highlighter' ); ?> - endPage() ?> \ No newline at end of file diff --git a/backend/web/js/site.js b/backend/web/js/site.js index f35a68a21..8f2151908 100644 --- a/backend/web/js/site.js +++ b/backend/web/js/site.js @@ -3,3 +3,54 @@ $(function () { $("[data-toggle='tooltip']").tooltip(); $("[data-toggle='popover']").popover(); }); +$(document).ready(function () { + + var ticks = 0; + + var x = setInterval(function () { + ticks++; + + if (typeof countDownDate === 'undefined') + return; + + if (countDownDate === 0) { + clearInterval(x); + return; + } + + // Server-based time, tick-driven + var timeNow = countDownNow + (ticks * 1000); + + var distance = countDownDate - timeNow; + var element = document.getElementById("event_countdown"); + var msg = "The competition ends in: "; + + if (countDownStart > 0 && countDownStart > timeNow) { + distance = countDownStart - timeNow; + msg = "The competition starts in: "; + } + + if (distance < 0) { + clearInterval(x); + if (element) + element.innerHTML = 'The competition is finished'; + return; + } + + var days = Math.floor(distance / (1000 * 60 * 60 * 24)); + var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); + var seconds = Math.floor((distance % (1000 * 60)) / 1000); + + if (element) { + if (days > 0) + element.innerHTML = msg + days + "d " + hours + "h " + minutes + "m " + seconds + "s"; + else if (hours > 0) + element.innerHTML = msg + hours + "h " + minutes + "m " + seconds + "s"; + else if (minutes > 0) + element.innerHTML = msg + minutes + "m " + seconds + "s"; + else + element.innerHTML = msg + seconds + "s"; + } + }, 1000); +}); \ No newline at end of file From 4169af7826df9f32196f6550f18ff7e269e1f29d Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:31:42 +0200 Subject: [PATCH 82/87] introduce event_shutdown script --- contrib/event_shutdown.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/event_shutdown.sh b/contrib/event_shutdown.sh index 6217f0f72..057c3091d 100644 --- a/contrib/event_shutdown.sh +++ b/contrib/event_shutdown.sh @@ -2,5 +2,7 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin:/usr/local/sbin:/usr/local/bin rcctl stop openvpn findingsd heartbeatd inetd cron supervisorctl stop all +backend vpn/killall +backend vpn/logoutall backend target/destroy-instances -ifconfig tun0 down +ifconfig tun0 down \ No newline at end of file From 9d844bed4c1afc9470120167c1dc7aad066cc62f Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:32:11 +0200 Subject: [PATCH 83/87] add 5 second refresh for status and sanitize timeout --- contrib/openvpn_tun0.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/openvpn_tun0.conf b/contrib/openvpn_tun0.conf index 6aea32ff1..516063fc8 100644 --- a/contrib/openvpn_tun0.conf +++ b/contrib/openvpn_tun0.conf @@ -12,7 +12,7 @@ client-config-dir /etc/openvpn/ccd writepid /var/run/openvpn.pid tls-auth /etc/openvpn/private/vpn-ta.key 0 replay-persist /etc/openvpn/replay-persist-file -status /var/log/openvpn/openvpn-status.log +status /var/log/openvpn/openvpn-status.log 5 log-append /var/log/openvpn/openvpn.log #chroot /var/openvpn/chrootjail crl-verify /etc/openvpn/crl.pem @@ -29,7 +29,7 @@ data-ciphers AES-128-GCM auth SHA256 compress verb 1 -keepalive 3 240 +keepalive 3 30 mute-replay-warnings script-security 2 From f4be8ed6f2b97ca457189f7d0bf839b776cffac0 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 13:34:31 +0200 Subject: [PATCH 84/87] improve countdown not to use client time --- frontend/themes/material/layouts/main.php | 3 +- frontend/web/js/libechoctf.js | 56 +++++++++++------------ 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/frontend/themes/material/layouts/main.php b/frontend/themes/material/layouts/main.php index a66aacfcc..80d5972ae 100644 --- a/frontend/themes/material/layouts/main.php +++ b/frontend/themes/material/layouts/main.php @@ -50,6 +50,7 @@ sys->event_start!==false && Yii::$app->sys->event_end!==false):?> @@ -71,7 +72,7 @@
sys->{"footer_logos"}?>
diff --git a/frontend/web/js/libechoctf.js b/frontend/web/js/libechoctf.js index 2741f9940..cf3112a94 100644 --- a/frontend/web/js/libechoctf.js +++ b/frontend/web/js/libechoctf.js @@ -188,8 +188,8 @@ var targetTimeout; var intervalTimeout = 5000; function targetUpdates(id) { - var targetEl = document.getElementById("target_id_"+id); - if(targetEl==null) return; + var targetEl = document.getElementById("target_id_" + id); + if (targetEl == null) return; var request = new XMLHttpRequest(); request.open("GET", `/target/${id}/ip`); request.setRequestHeader('X-Requested-With', 'XMLHttpRequest') @@ -289,51 +289,51 @@ $(document).ready(function () { selector: '[data-toggle="tooltip"]', }); + var ticks = 0; + var x = setInterval(function () { + ticks++; + if (typeof countDownDate === 'undefined') return; - // Get today's date and time - // var now = new Date(); - // Find the distance between now and the count down date + if (countDownDate === 0) { clearInterval(x); return; } - var timeNow = Date.now(); + + var timeNow = countDownNow + (ticks * 1000); + var distance = countDownDate - timeNow; - element = document.getElementById("event_countdown"); - msg = "The competition ends in: " + var element = document.getElementById("event_countdown"); + var msg = "The competition ends in: "; + if (countDownStart > 0 && countDownStart > timeNow) { distance = countDownStart - timeNow; msg = "The competition starts in: "; } + if (distance < 0) { + clearInterval(x); + if (element) + element.innerHTML = 'The competition is finished'; + return; + } - // Time calculations for days, hours, minutes and seconds var days = Math.floor(distance / (1000 * 60 * 60 * 24)); var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); var seconds = Math.floor((distance % (1000 * 60)) / 1000); - // If the count down is finished, write some text - if (distance < 0) { - clearInterval(x); - if (typeof (element) != 'undefined' && element != null) - element.innerHTML = 'The competition is finished'; - } - else { - if (element) { - //console.log(`${countDownStart} ${timeNow} ${distance}`); - if (days > 0) - element.innerHTML = msg + days + "d " + hours + "h " + minutes + "m " + seconds + "s"; - else if (hours > 0) - element.innerHTML = msg + hours + "h " + minutes + "m " + seconds + "s"; - else if (minutes > 0) - element.innerHTML = msg + minutes + "m " + seconds + "s"; - else if (seconds > 0) - element.innerHTML = msg + seconds + "seconds"; - } + if (element) { + if (days > 0) + element.innerHTML = msg + days + "d " + hours + "h " + minutes + "m " + seconds + "s"; + else if (hours > 0) + element.innerHTML = msg + hours + "h " + minutes + "m " + seconds + "s"; + else if (minutes > 0) + element.innerHTML = msg + minutes + "m " + seconds + "s"; + else + element.innerHTML = msg + seconds + "s"; } }, 1000); - }) From b1da1f387254cd54e362e11fe06e7d6f40bae307 Mon Sep 17 00:00:00 2001 From: Pantelis Roditis Date: Wed, 4 Feb 2026 17:07:10 +0200 Subject: [PATCH 85/87] fix error pages --- frontend/web/429.html | 1 - frontend/web/500.html | 1 - frontend/web/501.html | 1 - frontend/web/504.html | 1 - frontend/web/505.html | 1 - frontend/web/chill.html | 1 - 6 files changed, 6 deletions(-) diff --git a/frontend/web/429.html b/frontend/web/429.html index edd87e5a2..114f00e97 100644 --- a/frontend/web/429.html +++ b/frontend/web/429.html @@ -2,7 +2,6 @@ - echoCTF: Error 429