From aff6159881d8e3b168e7c2d7a1efff35b4773846 Mon Sep 17 00:00:00 2001 From: Luke Ward <0x6C77@gmail.com> Date: Tue, 30 Sep 2014 20:14:54 +0100 Subject: [PATCH 001/112] Removed no-margin from contributer header --- html/git.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/git.php b/html/git.php index 32fa565..456b8a2 100644 --- a/html/git.php +++ b/html/git.php @@ -10,7 +10,7 @@ HackThis/hackthis.co.uk HackThis -

Get involved

+

Get involved

Found a bug? Wish the site had feature x? The source code for all HackThis!! projects can be found on GitHub. We encourage you to fork the code and see if you can implement the fix/feature yourself. If you do develop something that you think would be beneficial, then create pull request and we can include it in the site! All users that submit an approved pull request receive a contributor medal, however small the change may be.

Not a developer? There are a lot of other changes that you could submit, for example spelling and grammatical fixes. We are grateful for even the smallest contribution.


From 418e96a244b83224e011cdfad54ed0395be22924 Mon Sep 17 00:00:00 2001 From: flabby Date: Sat, 25 Oct 2014 12:12:44 +0100 Subject: [PATCH 002/112] Added parsing to name in profile --- files/class.profile.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/files/class.profile.php b/files/class.profile.php index f462683..eb46378 100644 --- a/files/class.profile.php +++ b/files/class.profile.php @@ -138,8 +138,11 @@ public function __construct($username, $public=false) { $st->execute(array(':uid' => $this->uid, ':user' => $this->app->user->uid)); $this->friendsList = $st->fetchAll(); + // Parse content + $this->name = $this->app->parse($this->name, false, false); + if (isset($this->about)) { - $this->about_plain = $this->about; + $this->about_plain = $this->app->parse($this->about, false, false); $this->about = $this->app->parse($this->about); } From 5bbd51d0318bead1429c57e327033c9b6560c538 Mon Sep 17 00:00:00 2001 From: Alexey Petrenko Date: Wed, 29 Oct 2014 13:00:27 +0200 Subject: [PATCH 003/112] Fix isPackageInstalled false positives --- install_hackthis_ubuntu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install_hackthis_ubuntu.sh b/install_hackthis_ubuntu.sh index 770ce3b..fb28f25 100755 --- a/install_hackthis_ubuntu.sh +++ b/install_hackthis_ubuntu.sh @@ -17,7 +17,7 @@ Caption function isPackageInstalled { packagePolicyOutput=`apt-cache policy $1` - echo $packagePolicyOutput | grep Installed > /dev/null + echo $packagePolicyOutput | grep -v none | grep Installed > /dev/null return $? } From b1974b9aee8b928a497b14d68b652377f1d63181 Mon Sep 17 00:00:00 2001 From: Doron Linder Date: Tue, 18 Nov 2014 01:32:23 +0200 Subject: [PATCH 004/112] Fixing bug #139 - Moving from message dropdown to full view --- html/files/js/notifications.js | 16 +++++++++++++++- html/inbox/index.php | 18 +++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/html/files/js/notifications.js b/html/files/js/notifications.js index 44a3a39..d9590b7 100644 --- a/html/files/js/notifications.js +++ b/html/files/js/notifications.js @@ -328,7 +328,7 @@ $(function() { $('', {text: "Back to Inbox", class: "toggle-compose more", href: "/inbox"}).appendTo(composeHTML); composeHTML.append(composeForm); - $('', {text: "Full View", class: "more", href: "/inbox/compose"}).appendTo(composeHTML); + $('', {text: "Full View", class: "more full-view-via-storage", href: "/inbox/compose"}).appendTo(composeHTML); container.addClass('show-extra'); @@ -436,6 +436,20 @@ $(function() { } else $error.text("Error sending message"); }, 'json'); + + }).on('click', '.full-view-via-storage', function(e) { + + // Since it's a link we don't want to prevent default + e.stopPropagation(); + + // Moving from composing in the message dropdown to full view. + // Save the recipients and content of the message in the local storage + // The data will not be sent to the server, and will be erased as soon + // as it's copied to the respective fields in the full view form. + if (window.localStorage) { + window.localStorage.recipients = $('#to')[0].value; + window.localStorage.content = $('form.send textarea')[0].value; + } }); $('body').on('click', '.messages-new', function(e) { diff --git a/html/inbox/index.php b/html/inbox/index.php index f1ba4ba..7e2b71a 100644 --- a/html/inbox/index.php +++ b/html/inbox/index.php @@ -190,6 +190,21 @@ ?> + \ No newline at end of file +?> + From 63021d1d06f2739e6d106a09f4bc8879361be618 Mon Sep 17 00:00:00 2001 From: Luke Ward <0x6C77@gmail.com> Date: Fri, 21 Nov 2014 12:03:40 +0000 Subject: [PATCH 005/112] Changed socket URI --- html/files/js/notifications.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/html/files/js/notifications.js b/html/files/js/notifications.js index d9590b7..4c1cc6c 100644 --- a/html/files/js/notifications.js +++ b/html/files/js/notifications.js @@ -1,7 +1,8 @@ var socket = null; if (typeof io !== 'undefined') { - socket = io.connect('https://hackthis.co.uk:8080/', { secure: true }); + socket = io.connect('https://www.hackthis.co.uk:8080/', { secure: true }); } + var favcounter = new FavCounter(); var counter_chat = 0; var counter_notifications = 0; From 05b5501be14774af1a5ea9f375bf20a4f5271b1d Mon Sep 17 00:00:00 2001 From: flabby Date: Fri, 21 Nov 2014 12:38:53 +0000 Subject: [PATCH 006/112] #164 tweaks --- html/files/js/inbox.js | 14 +++++++++++++- html/files/js/notifications.js | 7 ++++--- html/inbox/index.php | 15 --------------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/html/files/js/inbox.js b/html/files/js/inbox.js index b08dac2..fa0d541 100644 --- a/html/files/js/inbox.js +++ b/html/files/js/inbox.js @@ -4,6 +4,18 @@ $(function() { $(".inbox .sticky").css('width', $(".inbox .sticky").width()-2); $(".inbox .sticky").sticky({topSpacing:45}); + // If transferred here from the message dropdown view, fill in the cached values + if (window.localStorage) { + if (window.localStorage.recipients) { + $('#to')[0].value = window.localStorage.recipients; + window.localStorage.removeItem('recipients'); + } + if (window.localStorage.content) { + $('form textarea.editor')[0].value = window.localStorage.content; + window.localStorage.removeItem('content'); + } + } + if ($(".inbox-main ul").length) { if (container.find('.new').length) { @@ -85,4 +97,4 @@ $(function() { conversationFind(container, term); } } -}); \ No newline at end of file +}); diff --git a/html/files/js/notifications.js b/html/files/js/notifications.js index 4c1cc6c..baa5801 100644 --- a/html/files/js/notifications.js +++ b/html/files/js/notifications.js @@ -356,7 +356,7 @@ $(function() { $('', {text: "Back to Inbox", class: "toggle-compose more", href: "/inbox"}).appendTo(messagesHTML); messagesHTML.append(items); - $('', {text: "Full View", class: "more", href: "/inbox/"+id}).appendTo(messagesHTML); + $('', {text: "Full View", class: "more full-view-via-storage", href: "/inbox/"+id}).appendTo(messagesHTML); container.addClass('show-extra'); @@ -439,7 +439,7 @@ $(function() { }, 'json'); }).on('click', '.full-view-via-storage', function(e) { - + console.log('To full view'); // Since it's a link we don't want to prevent default e.stopPropagation(); @@ -448,7 +448,8 @@ $(function() { // The data will not be sent to the server, and will be erased as soon // as it's copied to the respective fields in the full view form. if (window.localStorage) { - window.localStorage.recipients = $('#to')[0].value; + if ($('#to').length) + window.localStorage.recipients = $('#to')[0].value; window.localStorage.content = $('form.send textarea')[0].value; } }); diff --git a/html/inbox/index.php b/html/inbox/index.php index 7e2b71a..04d6797 100644 --- a/html/inbox/index.php +++ b/html/inbox/index.php @@ -190,21 +190,6 @@ ?> - Date: Fri, 21 Nov 2014 12:44:43 +0000 Subject: [PATCH 007/112] Removed debug code in notifications.js --- html/files/js/notifications.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/html/files/js/notifications.js b/html/files/js/notifications.js index baa5801..552e3bb 100644 --- a/html/files/js/notifications.js +++ b/html/files/js/notifications.js @@ -319,7 +319,7 @@ $(function() { e.preventDefault(); e.stopPropagation(); - var container = $('#global-nav .message-container'); + var container = $('#global-nav .message-container');co if (container.hasClass('show-extra')) { container.removeClass('show-extra'); @@ -439,7 +439,6 @@ $(function() { }, 'json'); }).on('click', '.full-view-via-storage', function(e) { - console.log('To full view'); // Since it's a link we don't want to prevent default e.stopPropagation(); From 4fee3f4d586189db0e1703131f15ec9699abb718 Mon Sep 17 00:00:00 2001 From: Luke Ward <0x6C77@gmail.com> Date: Fri, 21 Nov 2014 12:45:35 +0000 Subject: [PATCH 008/112] Fixed spacing in inbox.js --- html/files/js/inbox.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/html/files/js/inbox.js b/html/files/js/inbox.js index fa0d541..d40b388 100644 --- a/html/files/js/inbox.js +++ b/html/files/js/inbox.js @@ -4,17 +4,17 @@ $(function() { $(".inbox .sticky").css('width', $(".inbox .sticky").width()-2); $(".inbox .sticky").sticky({topSpacing:45}); - // If transferred here from the message dropdown view, fill in the cached values - if (window.localStorage) { - if (window.localStorage.recipients) { - $('#to')[0].value = window.localStorage.recipients; - window.localStorage.removeItem('recipients'); - } - if (window.localStorage.content) { - $('form textarea.editor')[0].value = window.localStorage.content; - window.localStorage.removeItem('content'); - } - } + // If transferred here from the message dropdown view, fill in the cached values + if (window.localStorage) { + if (window.localStorage.recipients) { + $('#to')[0].value = window.localStorage.recipients; + window.localStorage.removeItem('recipients'); + } + if (window.localStorage.content) { + $('form textarea.editor')[0].value = window.localStorage.content; + window.localStorage.removeItem('content'); + } + } if ($(".inbox-main ul").length) { From f371543b2a479b506b6c7a6912d61d2cf206a628 Mon Sep 17 00:00:00 2001 From: Luke Ward <0x6C77@gmail.com> Date: Fri, 21 Nov 2014 12:48:07 +0000 Subject: [PATCH 009/112] Removed unneeded code from notifications.js --- html/files/js/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/files/js/notifications.js b/html/files/js/notifications.js index 552e3bb..552f81b 100644 --- a/html/files/js/notifications.js +++ b/html/files/js/notifications.js @@ -360,7 +360,7 @@ $(function() { container.addClass('show-extra'); - $('#global-nav .scroll').mCustomScrollbar();hideNotifications + $('#global-nav .scroll').mCustomScrollbar(); if (container.find('.new').length) { $('#global-nav .scroll').mCustomScrollbar("scrollTo", "li.new:first"); From 55b8a22771268a26a3284d1e0bc2857bd05b7ffe Mon Sep 17 00:00:00 2001 From: Luke Ward <0x6C77@gmail.com> Date: Tue, 2 Dec 2014 10:12:58 +0000 Subject: [PATCH 010/112] Fixed modal position on mobile devices --- html/files/css/interaction.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/html/files/css/interaction.scss b/html/files/css/interaction.scss index c4cd718..930bbe5 100644 --- a/html/files/css/interaction.scss +++ b/html/files/css/interaction.scss @@ -944,11 +944,13 @@ div.lite-pagination { @media (max-width: 768px) { .modal-overlay .modal { + position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: auto; + min-height: 100%; } } From 72b7343c4bbc0ea480d15ff225e737892476040c Mon Sep 17 00:00:00 2001 From: Luke Ward <0x6C77@gmail.com> Date: Tue, 2 Dec 2014 10:13:59 +0000 Subject: [PATCH 011/112] Fixed modal position on mobile devices --- html/files/css/interaction.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/files/css/interaction.scss b/html/files/css/interaction.scss index 930bbe5..c8d976b 100644 --- a/html/files/css/interaction.scss +++ b/html/files/css/interaction.scss @@ -943,6 +943,7 @@ div.lite-pagination { } @media (max-width: 768px) { + .modal-overlay, .modal-overlay .modal { position: fixed; top: 0; @@ -950,7 +951,6 @@ div.lite-pagination { right: 0; bottom: 0; width: auto; - min-height: 100%; } } From 296a9cee7fdfa16c4da3082021e1a727d44e9cea Mon Sep 17 00:00:00 2001 From: flabby Date: Mon, 8 Dec 2014 20:12:56 +0000 Subject: [PATCH 012/112] Live levels class --- files/class.levels.php | 110 ++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/files/class.levels.php b/files/class.levels.php index a6d2e00..f3af05e 100644 --- a/files/class.levels.php +++ b/files/class.levels.php @@ -197,59 +197,79 @@ function levelView($level_id) { $st->execute(array(':lid'=> $level_id, ':uid' => $this->app->user->uid)); } - function check($level) { - if (!isset($level->data['answer'])) - return false; - $answers = json_decode($level->data['answer']); - - $attempted = false; - $correct = false; - foreach($answers AS $answer) { - if (strtolower($answer->method) == 'post') { - if (isset($_POST[$answer->name])) { - $attempted = true; - if ($answer->type && $answer->type == 'regex') { - if (preg_match($answer->value, $_POST[$answer->name])) { - $correct = true; - } else { - $correct = false; - break; - } - } else if ($_POST[$answer->name] === $answer->value) - $correct = true; - else { - $correct = false; - break; - } +function check($level) { + if (!isset($level->data['answer'])) + return false; + + $answers = json_decode($level->data['answer']); + + $attempted = false; + $correct = false; + $incorrect = 0; + foreach($answers AS $answer) { + $valid = false; + + if (strtolower($answer->method) == 'post') { + if (isset($_POST[$answer->name])) { + $attempted = true; + if (isset($answer->type) && $answer->type == 'regex') { + if (preg_match($answer->value, $_POST[$answer->name])) { + $valid = true; + if ($incorrect === 0) { + $correct = true; + } + } else { + $correct = false; } - } else if (strtolower($answer->method) == 'get') { - if (isset($_GET[$answer->name])) { - $attempted = true; - if ($answer->type && $answer->type == 'regex') { - if (preg_match($answer->value, $_GET[$answer->name])) { - $correct = true; - } else { - $correct = false; - break; - } - } else if ($_GET[$answer->name] === $answer->value) - $correct = true; - else { - $correct = false; - break; - } + } else if ($_POST[$answer->name] === $answer->value) { + $valid = true; + if ($incorrect === 0) { + $correct = true; } + } else { + $correct = false; } } - - if ($attempted) { - $level->attempt = $correct; - $this->attempt($level, $correct); + } else if (strtolower($answer->method) == 'get') { + if (isset($_GET[$answer->name])) { + $attempted = true; + if ($answer->type && $answer->type == 'regex') { + if (preg_match($answer->value, $_GET[$answer->name])) { + $valid = true; + if ($incorrect === 0) { + $correct = true; + } + } else { + $correct = false; + } + } else if ($_GET[$answer->name] === $answer->value) { + $valid = true; + if ($incorrect === 0) { + $correct = true; + } + } else { + $correct = false; + } } + } - return $correct; + if (!$valid) { + $incorrect++; } + } + + if ($attempted) { + $level->attempt = $correct; + $this->attempt($level, $correct); + + if ($level->level_id == 53) { + $level->errorMsg = (3 - $incorrect) . ' out of 3 answers correct'; + } + } + + return $correct; +} function attempt($level, $correct=false) { From e5a266d53f0b464601517b2a53bdeb7f3ffd1d1e Mon Sep 17 00:00:00 2001 From: 0x6C77 <0x6C77@gmail.com> Date: Mon, 8 Dec 2014 20:22:16 +0000 Subject: [PATCH 013/112] Bug fix for incorrect level checking code --- files/class.levels.php | 696 ++++++++++++++++++++--------------------- 1 file changed, 348 insertions(+), 348 deletions(-) diff --git a/files/class.levels.php b/files/class.levels.php index f3af05e..b66b5f2 100644 --- a/files/class.levels.php +++ b/files/class.levels.php @@ -1,220 +1,228 @@ app = $app; - } + public function __construct($app) { + $this->app = $app; + } - public function getList($uid=null, $category=null) { + public function getList($uid=null, $category=null) { // Check cache - $levels = json_decode($this->app->cache->get('level_list', 10)); - - if (!$levels) { - $sql = 'SELECT levels.level_id AS `id`, CONCAT(levels_groups.title, " Level ", levels.name) as `title`, levels.name, levels_groups.title as `group`, - LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri`, levels_completed.completed as `total_completed`, - levels_data.value AS `reward` - FROM levels - INNER JOIN levels_groups - ON levels_groups.title = levels.group - LEFT JOIN levels_data - ON levels_data.level_id = levels.level_id AND levels_data.key = "reward" - LEFT JOIN (SELECT COUNT(user_id) AS `completed`, level_id FROM users_levels WHERE completed > 0 AND user_id != 69 GROUP BY level_id) `levels_completed` - ON levels_completed.level_id = levels.level_id - ORDER BY levels_groups.order ASC, levels.level_id ASC'; + $levels = json_decode($this->app->cache->get('level_list', 10)); + + if (!$levels) { + $sql = 'SELECT levels.level_id AS `id`, CONCAT(levels_groups.title, " Level ", levels.name) as `title`, levels.name, levels_groups.title as `group`, + LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri`, levels_completed.completed as `total_completed`, + levels_data.value AS `reward` + FROM levels + INNER JOIN levels_groups + ON levels_groups.title = levels.group + LEFT JOIN levels_data + ON levels_data.level_id = levels.level_id AND levels_data.key = "reward" + LEFT JOIN (SELECT COUNT(user_id) AS `completed`, level_id FROM users_levels WHERE completed > 0 AND user_id != 69 GROUP BY level_id) `levels_completed` + ON levels_completed.level_id = levels.level_id + ORDER BY levels_groups.order ASC, levels.level_id ASC'; - $st = $this->app->db->prepare($sql); - $st->execute(); - $levels = $st->fetchAll(); + $st = $this->app->db->prepare($sql); + $st->execute(); + $levels = $st->fetchAll(); - $this->app->cache->set('level_list', json_encode($levels)); - } + $this->app->cache->set('level_list', json_encode($levels)); + } // Get list of completed levels - $sql = 'SELECT level_id, IF(users_levels.completed > 0, 2, 1) as `completed` FROM users_levels WHERE user_id = :uid'; - $st = $this->app->db->prepare($sql); - $st->bindValue(':uid', $uid?$uid:$this->app->user->uid); - $st->execute(); - $user_levels = $st->fetchAll(); + $sql = 'SELECT level_id, IF(users_levels.completed > 0, 2, 1) as `completed` FROM users_levels WHERE user_id = :uid'; + $st = $this->app->db->prepare($sql); + $st->bindValue(':uid', $uid?$uid:$this->app->user->uid); + $st->execute(); + $user_levels = $st->fetchAll(); // Create list - $list = array(); - foreach ($levels AS &$level) { + $list = array(); + foreach ($levels AS &$level) { // Check filter - if ($category && trim(strtolower(str_replace('+', '', $level->group))) != trim($category)) { - continue; - } + if ($category && trim(strtolower(str_replace('+', '', $level->group))) != trim($category)) { + continue; + } // Assign progress based on $users_levels - $level->progress = 0; - foreach ($user_levels AS $l) { - if ($l->level_id == $level->id) { - $level->progress = $l->completed; - break; - } + $level->progress = 0; + foreach ($user_levels AS $l) { + if ($l->level_id == $level->id) { + $level->progress = $l->completed; + break; } - - if (!array_key_exists($level->group, $list)) { - $list[$level->group] = new stdClass(); - $list[$level->group]->levels = array(); - } - array_push($list[$level->group]->levels, $level); } - return $list; + if (!array_key_exists($level->group, $list)) { + $list[$level->group] = new stdClass(); + $list[$level->group]->levels = array(); + } + array_push($list[$level->group]->levels, $level); } - public function getGroups() { - $st = $this->app->db->prepare('SELECT title FROM levels_groups ORDER BY `order` ASC, `title` ASC'); - $st->execute(); - return $st->fetchAll(); - } + return $list; + } - public function getLevelFromID($id) { - $st = $this->app->db->prepare('SELECT `group`, `name` FROM levels WHERE level_id = :id LIMIT 1'); - $st->bindValue(':id', $id); - $st->execute(); - $res = $st->fetch(); + public function getGroups() { + $st = $this->app->db->prepare('SELECT title FROM levels_groups ORDER BY `order` ASC, `title` ASC'); + $st->execute(); + return $st->fetchAll(); + } - if ($res) - return $this->getLevel($res->group, $res->name, true); - else - return false; - } + public function getLevelFromID($id) { + $st = $this->app->db->prepare('SELECT `group`, `name` FROM levels WHERE level_id = :id LIMIT 1'); + $st->bindValue(':id', $id); + $st->execute(); + $res = $st->fetch(); + + if ($res) + return $this->getLevel($res->group, $res->name, true); + else + return false; + } - public function getLevel($group, $name, $noSkip=false) { - $before_after_sql = 'SELECT `level_id`, `name`, LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri` - FROM levels - INNER JOIN levels_groups - ON levels_groups.title = levels.group - WHERE `group` = :group - ORDER BY level_id'; - - $sql = "SELECT levels.level_id, levels.name, levels_groups.title AS `group`, CONCAT(`group`, ' Level ', levels.name) as `title`, - IF(users_levels.completed > 0, 1, 0) as `completed`, users_levels.completed as `completed_time`, `started`, - IFNULL(users_levels.attempts, 0) as `attempts`, - levels_before.uri AS `level_before_uri`, levels_after.uri AS `level_after_uri` - FROM levels - INNER JOIN levels_groups - ON levels_groups.title = levels.group - LEFT JOIN ({$before_after_sql} DESC) levels_before - ON levels_before.level_id < levels.level_id - LEFT JOIN ({$before_after_sql} ASC) levels_after - ON levels_after.level_id > levels.level_id + public function getLevel($group, $name, $noSkip=false) { + $before_after_sql = 'SELECT `level_id`, `name`, LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri` + FROM levels + INNER JOIN levels_groups + ON levels_groups.title = levels.group + WHERE `group` = :group + ORDER BY level_id'; + + $sql = "SELECT levels.level_id, levels.name, levels_groups.title AS `group`, CONCAT(`group`, ' Level ', levels.name) as `title`, + IF(users_levels.completed > 0, 1, 0) as `completed`, users_levels.completed as `completed_time`, `started`, + IFNULL(users_levels.attempts, 0) as `attempts`, + levels_before.uri AS `level_before_uri`, levels_after.uri AS `level_after_uri` + FROM levels + INNER JOIN levels_groups + ON levels_groups.title = levels.group + LEFT JOIN ({$before_after_sql} DESC) levels_before + ON levels_before.level_id < levels.level_id + LEFT JOIN ({$before_after_sql} ASC) levels_after + ON levels_after.level_id > levels.level_id + LEFT JOIN users_levels + ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id + WHERE levels.name = :level AND levels.group = :group"; + + $st = $this->app->db->prepare($sql); + $st->execute(array(':level'=>$name, ':group'=>$group, ':uid'=>$this->app->user->uid)); + $level = $st->fetch(); + + if ($level) { + //Check if user has access + if (isset($level->level_before_uri) && strtolower($level->group) == 'main') { + $sql = 'SELECT IF(users_levels.completed > 0, 1, 0) as `completed` FROM levels LEFT JOIN users_levels ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id - WHERE levels.name = :level AND levels.group = :group"; - - $st = $this->app->db->prepare($sql); - $st->execute(array(':level'=>$name, ':group'=>$group, ':uid'=>$this->app->user->uid)); - $level = $st->fetch(); + WHERE `group` = :group AND levels.level_id < :level_id + ORDER BY levels.level_id DESC + LIMIT 1'; + + $st = $this->app->db->prepare($sql); + $st->execute(array(':level_id'=>$level->level_id, ':group'=>$group, ':uid'=>$this->app->user->uid)); + $previous = $st->fetch(); - if ($level) { - //Check if user has access - if (isset($level->level_before_uri) && strtolower($level->group) == 'main') { - $sql = 'SELECT IF(users_levels.completed > 0, 1, 0) as `completed` FROM levels - LEFT JOIN users_levels - ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id - WHERE `group` = :group AND levels.level_id < :level_id - ORDER BY levels.level_id DESC - LIMIT 1'; - - $st = $this->app->db->prepare($sql); - $st->execute(array(':level_id'=>$level->level_id, ':group'=>$group, ':uid'=>$this->app->user->uid)); - $previous = $st->fetch(); - - if (!$noSkip && (!$previous || !$previous->completed)) { - header("Location: $level->level_before_uri?skipped"); - die(); - } + if (!$noSkip && (!$previous || !$previous->completed)) { + header("Location: $level->level_before_uri?skipped"); + die(); } + } - $this->levelView($level->level_id); - } else - return false; + $this->levelView($level->level_id); + } else + return false; //Build level data - $sql = 'SELECT `key`, `value`, users.username - FROM levels_data - LEFT JOIN users - ON levels_data.value = users.user_id AND levels_data.key = "author" - WHERE level_id = :lid'; - $st = $this->app->db->prepare($sql); - $st->execute(array(':lid'=>$level->level_id)); - $data = $st->fetchAll(); - - $level->data = array(); - - foreach($data as $d) { + $sql = 'SELECT `key`, `value`, users.username + FROM levels_data + LEFT JOIN users + ON levels_data.value = users.user_id AND levels_data.key = "author" + WHERE level_id = :lid'; + $st = $this->app->db->prepare($sql); + $st->execute(array(':lid'=>$level->level_id)); + $data = $st->fetchAll(); + + $level->data = array(); + + foreach($data as $d) { //Find all non-value entries - foreach($d as $k=>$v) { - if ($v && $k !== 'key' && $k !== 'value') - $d->value = $v; - } - - $level->data[$d->key] = $d->value; + foreach($d as $k=>$v) { + if ($v && $k !== 'key' && $k !== 'value') + $d->value = $v; } - if (isset($level->data['code'])) { - $level->data['code'] = json_decode($level->data['code']); - } + $level->data[$d->key] = $d->value; + } + + if (isset($level->data['code'])) { + $level->data['code'] = json_decode($level->data['code']); + } // Set page details - $this->app->page->title = ucwords($level->title); + $this->app->page->title = ucwords($level->title); // Get stats - $sql = "SELECT COUNT(user_id) AS `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69"; - $st = $this->app->db->prepare($sql); - $st->execute(array(':lid'=>$level->level_id)); - $result = $st->fetch(); - $level->count = $result->completed; + $sql = "SELECT COUNT(user_id) AS `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69"; + $st = $this->app->db->prepare($sql); + $st->execute(array(':lid'=>$level->level_id)); + $result = $st->fetch(); + $level->count = $result->completed; // // // Get latest // $sql = "SELECT username, completed FROM users_levels INNER JOIN users ON users.user_id = users_levels.user_id WHERE completed > 0 AND level_id = :lid AND users.user_id != 69 ORDER BY completed DESC LIMIT 1"; - $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` DESC LIMIT 1) `a` ON `a`.user_id = users.user_id;"; - $st = $this->app->db->prepare($sql); - $st->execute(array(':lid'=>$level->level_id)); - $result = $st->fetch(); - $level->last_completed = $result->completed; - $level->last_user = $result->username; + $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` DESC LIMIT 1) `a` ON `a`.user_id = users.user_id;"; + $st = $this->app->db->prepare($sql); + $st->execute(array(':lid'=>$level->level_id)); + $result = $st->fetch(); + $level->last_completed = $result->completed; + $level->last_user = $result->username; // // // Get first // $sql = "SELECT username, completed FROM users_levels INNER JOIN users ON users.user_id = users_levels.user_id WHERE completed > 0 AND level_id = :lid AND users.user_id != 69 ORDER BY completed ASC LIMIT 1"; - $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` ASC LIMIT 1) `a` ON `a`.user_id = users.user_id;"; - $st = $this->app->db->prepare($sql); - $st->execute(array(':lid'=>$level->level_id)); - $result = $st->fetch(); - $level->first_completed = $result->completed; - $level->first_user = $result->username; - - return $level; - } - - function levelView($level_id) { - $st = $this->app->db->prepare('INSERT IGNORE INTO users_levels (`user_id`, `level_id`) VALUES (:uid, :lid)'); - $st->execute(array(':lid'=> $level_id, ':uid' => $this->app->user->uid)); - } - - -function check($level) { - if (!isset($level->data['answer'])) - return false; + $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` ASC LIMIT 1) `a` ON `a`.user_id = users.user_id;"; + $st = $this->app->db->prepare($sql); + $st->execute(array(':lid'=>$level->level_id)); + $result = $st->fetch(); + $level->first_completed = $result->completed; + $level->first_user = $result->username; + + return $level; + } - $answers = json_decode($level->data['answer']); + function levelView($level_id) { + $st = $this->app->db->prepare('INSERT IGNORE INTO users_levels (`user_id`, `level_id`) VALUES (:uid, :lid)'); + $st->execute(array(':lid'=> $level_id, ':uid' => $this->app->user->uid)); + } - $attempted = false; - $correct = false; - $incorrect = 0; - foreach($answers AS $answer) { - $valid = false; - if (strtolower($answer->method) == 'post') { - if (isset($_POST[$answer->name])) { - $attempted = true; - if (isset($answer->type) && $answer->type == 'regex') { - if (preg_match($answer->value, $_POST[$answer->name])) { + function check($level) { + if (!isset($level->data['answer'])) + return false; + + $answers = json_decode($level->data['answer']); + + $attempted = false; + $correct = false; + $incorrect = 0; + foreach($answers AS $answer) { + $valid = false; + + if (strtolower($answer->method) == 'post') { + if (isset($_POST[$answer->name])) { + $attempted = true; + if (isset($answer->type) && $answer->type == 'regex') { + if (preg_match($answer->value, $_POST[$answer->name])) { + $valid = true; + if ($incorrect === 0) { + $correct = true; + } + } else { + $correct = false; + } + } else if ($_POST[$answer->name] === $answer->value) { $valid = true; if ($incorrect === 0) { $correct = true; @@ -222,20 +230,20 @@ function check($level) { } else { $correct = false; } - } else if ($_POST[$answer->name] === $answer->value) { - $valid = true; - if ($incorrect === 0) { - $correct = true; - } - } else { - $correct = false; } - } - } else if (strtolower($answer->method) == 'get') { - if (isset($_GET[$answer->name])) { - $attempted = true; - if ($answer->type && $answer->type == 'regex') { - if (preg_match($answer->value, $_GET[$answer->name])) { + } else if (strtolower($answer->method) == 'get') { + if (isset($_GET[$answer->name])) { + $attempted = true; + if ($answer->type && $answer->type == 'regex') { + if (preg_match($answer->value, $_GET[$answer->name])) { + $valid = true; + if ($incorrect === 0) { + $correct = true; + } + } else { + $correct = false; + } + } else if ($_GET[$answer->name] === $answer->value) { $valid = true; if ($incorrect === 0) { $correct = true; @@ -243,227 +251,219 @@ function check($level) { } else { $correct = false; } - } else if ($_GET[$answer->name] === $answer->value) { - $valid = true; - if ($incorrect === 0) { - $correct = true; - } - } else { - $correct = false; } } - } - if (!$valid) { - $incorrect++; + if (!$valid) { + $incorrect++; + } } - } - if ($attempted) { - $level->attempt = $correct; - $this->attempt($level, $correct); + if ($attempted) { + $level->attempt = $correct; + $this->attempt($level, $correct); - if ($level->level_id == 53) { - $level->errorMsg = (3 - $incorrect) . ' out of 3 answers correct'; + if ($level->level_id == 53) { + $level->errorMsg = (3 - $incorrect) . ' out of 3 answers correct'; + } } - } - return $correct; -} + return $correct; + } - function attempt($level, $correct=false) { - if (!$level->completed) { - $level->attempts = $level->attempts + 1; - $level->completed_time = 'now'; - if ($correct) { - $level->completed = true; - $level->count++; - $level->last_user = $this->app->user->username; - $level->last_completed = "now"; + function attempt($level, $correct=false) { + if (!$level->completed) { + $level->attempts = $level->attempts + 1; + $level->completed_time = 'now'; + if ($correct) { + $level->completed = true; + $level->count++; + $level->last_user = $this->app->user->username; + $level->last_completed = "now"; //Update user score (temporary) - $this->app->user->score = $this->app->user->score + $level->data['reward']; - $st = $this->app->db->prepare('UPDATE users_levels SET completed = NOW(), attempts=attempts+1 WHERE level_id = :lid AND user_id = :uid'); - $st->execute(array(':lid'=> $level->level_id, ':uid' => $this->app->user->uid)); + $this->app->user->score = $this->app->user->score + $level->data['reward']; + $st = $this->app->db->prepare('UPDATE users_levels SET completed = NOW(), attempts=attempts+1 WHERE level_id = :lid AND user_id = :uid'); + $st->execute(array(':lid'=> $level->level_id, ':uid' => $this->app->user->uid)); // Setup GA event - $this->app->ssga->set_event('level', 'completed', $level->level_id, $this->app->user->uid); - $this->app->ssga->send(); + $this->app->ssga->set_event('level', 'completed', $level->level_id, $this->app->user->uid); + $this->app->ssga->send(); // Send feed thingy - $this->app->feed->call($this->app->user->username, 'level', ucwords($level->group.' '.$level->name), '/levels/'.strtolower($level->group).'/'.strtolower($level->name)); - + $this->app->feed->call($this->app->user->username, 'level', ucwords($level->group.' '.$level->name), '/levels/'.strtolower($level->group).'/'.strtolower($level->name)); + // Update WeChall - file_get_contents("http://wechall.net/remoteupdate.php?sitename=ht&username=".$this->app->user->username); - } else { + file_get_contents("http://wechall.net/remoteupdate.php?sitename=ht&username=".$this->app->user->username); + } else { // Record attempt - $st = $this->app->db->prepare('UPDATE users_levels SET attempts=attempts+1 WHERE level_id = :lid AND user_id = :uid'); - $st->execute(array(':lid'=> $level->level_id, ':uid' => $this->app->user->uid)); - } + $st = $this->app->db->prepare('UPDATE users_levels SET attempts=attempts+1 WHERE level_id = :lid AND user_id = :uid'); + $st->execute(array(':lid'=> $level->level_id, ':uid' => $this->app->user->uid)); } } + } - function user_data($level_id, $data=null) { - if ($data !== null) { - $st = $this->app->db->prepare('INSERT INTO users_levels_data (`user_id`, `level_id`, `data`) VALUES (:uid, :lid, :data) ON DUPLICATE KEY UPDATE `data` = :data, `time` = now()'); - return $st->execute(array(':lid' => $level_id, ':uid' => $this->app->user->uid, ':data' => $data)); - } else { - $st = $this->app->db->prepare('SELECT * FROM users_levels_data WHERE `user_id` = :uid AND `level_id` = :lid'); - $st->execute(array(':lid' => $level_id, ':uid' => $this->app->user->uid)); - return $st->fetch(); - } + function user_data($level_id, $data=null) { + if ($data !== null) { + $st = $this->app->db->prepare('INSERT INTO users_levels_data (`user_id`, `level_id`, `data`) VALUES (:uid, :lid, :data) ON DUPLICATE KEY UPDATE `data` = :data, `time` = now()'); + return $st->execute(array(':lid' => $level_id, ':uid' => $this->app->user->uid, ':data' => $data)); + } else { + $st = $this->app->db->prepare('SELECT * FROM users_levels_data WHERE `user_id` = :uid AND `level_id` = :lid'); + $st->execute(array(':lid' => $level_id, ':uid' => $this->app->user->uid)); + return $st->fetch(); } + } // ADMIN FUNCTIONS - function addCategory($title) { - if (!$this->app->user->admin_site_priv) - return false; - - $st = $this->app->db->prepare('INSERT INTO levels_groups (`title`) VALUES (:title)'); - return $st->execute(array(':title'=> $title)); - } - - function editLevel($id, $new = false) { - if (!$this->app->user->admin_site_priv) - return false; + function addCategory($title) { + if (!$this->app->user->admin_site_priv) + return false; + $st = $this->app->db->prepare('INSERT INTO levels_groups (`title`) VALUES (:title)'); + return $st->execute(array(':title'=> $title)); + } - $changes = array(); + function editLevel($id, $new = false) { + if (!$this->app->user->admin_site_priv) + return false; - if (!$new) { - if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) - return false; - if (isset($_POST['category']) && strlen($_POST['category'])) { - $group = $_POST['category']; + $changes = array(); - $st = $this->app->db->prepare('UPDATE IGNORE levels SET `group` = :g WHERE level_id = :id LIMIT 1'); - $res = $st->execute(array(':id'=> $id, ':g'=>$group)); - } - } + if (!$new) { + if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) + return false; - if (isset($_POST['reward']) && is_numeric($_POST['reward'])) { - $changes['reward'] = $_POST['reward']; - } - if (isset($_POST['description']) && strlen($_POST['description'])) { - $changes['description'] = $_POST['description']; - } - if (isset($_POST['hint']) && strlen($_POST['hint'])) { - $changes['hint'] = $_POST['hint']; - } - if (isset($_POST['solution']) && strlen($_POST['solution'])) { - $changes['solution'] = $_POST['solution']; - } + if (isset($_POST['category']) && strlen($_POST['category'])) { + $group = $_POST['category']; - foreach($changes AS $change=>$value) { - $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); - $res = $st->execute(array(':id'=> $id, ':k'=>$change, ':v'=>$value)); - if (!$res) - return false; + $st = $this->app->db->prepare('UPDATE IGNORE levels SET `group` = :g WHERE level_id = :id LIMIT 1'); + $res = $st->execute(array(':id'=> $id, ':g'=>$group)); } + } - return true; + if (isset($_POST['reward']) && is_numeric($_POST['reward'])) { + $changes['reward'] = $_POST['reward']; + } + if (isset($_POST['description']) && strlen($_POST['description'])) { + $changes['description'] = $_POST['description']; + } + if (isset($_POST['hint']) && strlen($_POST['hint'])) { + $changes['hint'] = $_POST['hint']; + } + if (isset($_POST['solution']) && strlen($_POST['solution'])) { + $changes['solution'] = $_POST['solution']; } - function newLevel() { - if (!$this->app->user->admin_site_priv) + foreach($changes AS $change=>$value) { + $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); + $res = $st->execute(array(':id'=> $id, ':k'=>$change, ':v'=>$value)); + if (!$res) return false; + } - if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) - return false; + return true; + } + + function newLevel() { + if (!$this->app->user->admin_site_priv) + return false; + + if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) + return false; // Create level - try { - $st = $this->app->db->prepare('INSERT INTO levels (`name`, `group`) VALUES (:name, :group)'); - $status = $st->execute(array(':name'=> $_POST['name'], ':group' => $_POST['category'])); - } catch(PDOExecption $e) { - return false; - } + try { + $st = $this->app->db->prepare('INSERT INTO levels (`name`, `group`) VALUES (:name, :group)'); + $status = $st->execute(array(':name'=> $_POST['name'], ':group' => $_POST['category'])); + } catch(PDOExecption $e) { + return false; + } - if (!$status) - return false; + if (!$status) + return false; - $id = $this->app->db->lastInsertId(); + $id = $this->app->db->lastInsertId(); // Insert data - $this->editLevel($id, true); + $this->editLevel($id, true); // Return level id - return $id; - } + return $id; + } - function editLevelForm($id) { - if (!$this->app->user->admin_site_priv) - return false; + function editLevelForm($id) { + if (!$this->app->user->admin_site_priv) + return false; - if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) - return false; + if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) + return false; - $form = null; + $form = null; // Is it JSON? - if (isset($_POST['form_method'])) { - $form = array(); - $form['method'] = $_POST['form_method']; - $form['fields'] = array(); - - $f_types = $_POST['form_type']; - $f_names = $_POST['form_name']; - $f_labels = $_POST['form_label']; - - foreach($f_types as $key => $value) { - echo $value . "
"; - if ($f_names[$key] && $f_labels[$key]) { - $field = new stdClass; - $field->type = $value; - $field->name = $f_names[$key]; - $field->label = $f_labels[$key]; - - array_push($form['fields'],$field); - } + if (isset($_POST['form_method'])) { + $form = array(); + $form['method'] = $_POST['form_method']; + $form['fields'] = array(); + + $f_types = $_POST['form_type']; + $f_names = $_POST['form_name']; + $f_labels = $_POST['form_label']; + + foreach($f_types as $key => $value) { + echo $value . "
"; + if ($f_names[$key] && $f_labels[$key]) { + $field = new stdClass; + $field->type = $value; + $field->name = $f_names[$key]; + $field->label = $f_labels[$key]; + + array_push($form['fields'],$field); } - - if (count($form['fields'])) - $form = json_encode($form); - } else { - if (isset($_POST['form'])) - $form = $_POST['form']; } - if ($form) { - $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); - $status = $st->execute(array(':id'=> $id, ':k' => 'form', ':v' => $form)); - } + if (count($form['fields'])) + $form = json_encode($form); + } else { + if (isset($_POST['form'])) + $form = $_POST['form']; + } + + if ($form) { + $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); + $status = $st->execute(array(':id'=> $id, ':k' => 'form', ':v' => $form)); + } // Do answers - $answers = array(); - - $a_methods = $_POST['answer_method']; - $a_names = $_POST['answer_name']; - $a_values = $_POST['answer_value']; - - foreach($a_methods as $key => $value) { - if ($a_names[$key] && $a_values[$key]) { - $answer = new stdClass; - $answer->method = $value; - $answer->name = $a_names[$key]; - $answer->value = $a_values[$key]; - - array_push($answers, $answer); - } + $answers = array(); + + $a_methods = $_POST['answer_method']; + $a_names = $_POST['answer_name']; + $a_values = $_POST['answer_value']; + + foreach($a_methods as $key => $value) { + if ($a_names[$key] && $a_values[$key]) { + $answer = new stdClass; + $answer->method = $value; + $answer->name = $a_names[$key]; + $answer->value = $a_values[$key]; + + array_push($answers, $answer); } + } - if (count($answers)) { - $answers = json_encode($answers); - if ($answers) { - $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); - $status = $st->execute(array(':id'=> $id, ':k' => 'answer', ':v' => $answers)); - } + if (count($answers)) { + $answers = json_encode($answers); + if ($answers) { + $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); + $status = $st->execute(array(':id'=> $id, ':k' => 'answer', ':v' => $answers)); } - - return true; } + + return true; } +} ?> From 861604e7195ab883dd45464fcbf204647c148a7c Mon Sep 17 00:00:00 2001 From: Luke Ward <0x6C77@gmail.com> Date: Mon, 5 Jan 2015 16:40:37 +0000 Subject: [PATCH 014/112] Added block for users with score 0 and karma < 0 --- files/class.forum.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/files/class.forum.php b/files/class.forum.php index a57823f..b720c95 100644 --- a/files/class.forum.php +++ b/files/class.forum.php @@ -968,6 +968,18 @@ function validatePost($body, $edit=false) { $this->error = "You have been banned from posting messages"; return false; } + + $st = $this->app->db->prepare('SELECT user_id, karma.karma + FROM users + LEFT JOIN (SELECT SUM(karma) AS karma, forum_posts.author FROM users_forum INNER JOIN forum_posts ON users_forum.post_id = forum_posts.post_id AND forum_posts.deleted = 0 GROUP BY forum_posts.author) karma + ON karma.author = u.user_id + WHERE user_id = :uid AND (karma < 0 AND score <= 0) + LIMIT 1'); + $st->execute(array(':uid'=>$this->app->user->uid)); + if ($res = $st->fetch()) { + $this->error = "You have been temporarily banned from posting messages"; + return false; + } //check post length if (str_word_count($body) < 2) { From 3f81c81ed4223fc6a50f92c55793541da82e8d5a Mon Sep 17 00:00:00 2001 From: Flabby Date: Mon, 5 Jan 2015 16:43:57 +0000 Subject: [PATCH 015/112] Fixed level checks with actual answers --- files/class.levels.php | 116 +++++++++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 46 deletions(-) diff --git a/files/class.levels.php b/files/class.levels.php index a6d2e00..5888795 100644 --- a/files/class.levels.php +++ b/files/class.levels.php @@ -197,59 +197,83 @@ function levelView($level_id) { $st->execute(array(':lid'=> $level_id, ':uid' => $this->app->user->uid)); } - function check($level) { - if (!isset($level->data['answer'])) - return false; - $answers = json_decode($level->data['answer']); - - $attempted = false; - $correct = false; - foreach($answers AS $answer) { - if (strtolower($answer->method) == 'post') { - if (isset($_POST[$answer->name])) { - $attempted = true; - if ($answer->type && $answer->type == 'regex') { - if (preg_match($answer->value, $_POST[$answer->name])) { - $correct = true; - } else { - $correct = false; - break; - } - } else if ($_POST[$answer->name] === $answer->value) - $correct = true; - else { - $correct = false; - break; - } +function check($level) { + if (!isset($level->data['answer'])) + return false; + + $answers = json_decode($level->data['answer']); + + $attempted = false; + $correct = false; + $incorrect = 0; + foreach($answers AS $answer) { + $valid = false; + + if (strtolower($answer->method) == 'post') { + if (isset($_POST[$answer->name])) { + $attempted = true; + if (isset($answer->type) && $answer->type == 'regex') { + if (preg_match($answer->value, $_POST[$answer->name])) { + $valid = true; + if ($incorrect === 0) { + $correct = true; + } + } else { + $correct = false; } - } else if (strtolower($answer->method) == 'get') { - if (isset($_GET[$answer->name])) { - $attempted = true; - if ($answer->type && $answer->type == 'regex') { - if (preg_match($answer->value, $_GET[$answer->name])) { - $correct = true; - } else { - $correct = false; - break; - } - } else if ($_GET[$answer->name] === $answer->value) - $correct = true; - else { - $correct = false; - break; - } + } else if ($_POST[$answer->name] === $answer->value) { + $valid = true; + if ($incorrect === 0) { + $correct = true; } + } else { + $correct = false; } - } + } else { + $correct = false; + } + } else if (strtolower($answer->method) == 'get') { + if (isset($_GET[$answer->name])) { + $attempted = true; + if ($answer->type && $answer->type == 'regex') { + if (preg_match($answer->value, $_GET[$answer->name])) { + $valid = true; + if ($incorrect === 0) { + $correct = true; + } + } else { + $correct = false; + } + } else if ($_GET[$answer->name] === $answer->value) { + $valid = true; + if ($incorrect === 0) { + $correct = true; + } + } else { + $correct = false; + } + } else { + $correct = false; + } + } - if ($attempted) { - $level->attempt = $correct; - $this->attempt($level, $correct); - } + if (!$valid) { + $incorrect++; + } + } + + if ($attempted) { + $level->attempt = $correct; + $this->attempt($level, $correct); - return $correct; + if ($level->level_id == 53) { + $level->errorMsg = (3 - $incorrect) . ' out of 3 answers correct'; } + } + + return $correct; +} function attempt($level, $correct=false) { From 5dd37eae5db66cb4fc0d92bc7aa819311a23e9ec Mon Sep 17 00:00:00 2001 From: Flabby Date: Mon, 5 Jan 2015 16:44:27 +0000 Subject: [PATCH 016/112] Fixed modal position --- html/files/css/interaction.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/html/files/css/interaction.scss b/html/files/css/interaction.scss index c4cd718..80e8d08 100644 --- a/html/files/css/interaction.scss +++ b/html/files/css/interaction.scss @@ -943,11 +943,12 @@ div.lite-pagination { } @media (max-width: 768px) { + .modal-overlay, .modal-overlay .modal { + position: absolute; top: 0; left: 0; right: 0; - bottom: 0; width: auto; } } From a931fc5918cbbae410d4ec58351978b32942d7c8 Mon Sep 17 00:00:00 2001 From: Flabby Date: Fri, 20 Feb 2015 20:16:09 +0000 Subject: [PATCH 017/112] Updated API access --- files/class.api.php | 134 +++++++++++++++++++++++++++--------------- html/example.htaccess | 12 +++- 2 files changed, 99 insertions(+), 47 deletions(-) diff --git a/files/class.api.php b/files/class.api.php index 3c262c9..a6a6f41 100644 --- a/files/class.api.php +++ b/files/class.api.php @@ -1,6 +1,9 @@ privileges) && $this->app->user->loggedIn) { - $this->privileges = "inherit"; + $this->privileges = array('user.profile'); } if (!isset($this->privileges)) { @@ -27,14 +30,82 @@ public function handleRequest($method, $data) { $this->respond(400); } + // Check privileges + $this->hasPrivilege($method); + switch ($method) { + case 'irc.log': $this->logIrc(); break; + case 'user.login': $this->user('login'); break; case 'user.profile': $this->user('profile'); break; } $this->respond(400); } - public function respond($status, $data=null) { + + //===================================================== + // REQUEST HANDLERS + //===================================================== + + //----------------------------------------------------- + // User + //----------------------------------------------------- + private function user($request) { + $response = new stdClass(); + + switch ($request) { + case 'profile': $response->profile = $this->userProfile(); break; + case 'login': $response = $this->userLogin(); break; + } + + $this->respond(200, $response); + } + + private function userProfile() { + $profile = new profile($_GET['user'], true); ; + + unset($profile->email); + + return $profile; + } + + private function userLogin() { + // No idea yet + } + + + //----------------------------------------------------- + // IRC + //----------------------------------------------------- + private function logIrc() { + if (!isset($_POST['nick']) || !isset($_POST['chan']) || !isset($_POST['msg'])) + throw new Exception('Missing data fields'); + + $_POST['msg'] = preg_replace('/\x01/', '', $_POST['msg']); + + $st = $this->app->db->prepare('INSERT INTO irc_logs (`nick`, `channel`, `log`) + VALUES (:nick, :chan, :msg)'); + $result = $st->execute(array(':nick' => $_POST['nick'], ':chan' => $_POST['chan'], ':msg' => $_POST['msg'])); + + + // Calculate stats + $st = $this->app->db->prepare('INSERT INTO irc_stats (`nick`, `lines`, `words`, `chars`) + VALUES (:nick, :lines, :words, :chars) + ON DUPLICATE KEY UPDATE `lines`=`lines`+:lines, `words`=`words`+:words, `chars`=`chars`+:chars, `time`=NOW()'); + + $st->bindValue(':nick', $_POST['nick'], PDO::PARAM_INT); + $st->bindValue(':lines', 1, PDO::PARAM_INT); + $st->bindValue(':words', str_word_count($_POST['msg']), PDO::PARAM_INT); + $st->bindValue(':chars', strlen($_POST['msg']), PDO::PARAM_INT); + $result = $st->execute(); + + $this->respond(200); + } + + //===================================================== + // HELPER FUNCTIONS + //===================================================== + private function respond($status, $data=null) { if (!$data) { $data = new stdClass(); } @@ -66,6 +137,11 @@ public function respond($status, $data=null) { } private function checkKey($key) { + // if ($this->app->config('api') == $key) { + // $this->privileges = array('irc.*', 'user.profile'); + // return true; + // } + $st = $this->app->db->prepare('SELECT privileges FROM api_clients WHERE `key` = :key LIMIT 1'); $st->execute(array(':key' => $key)); $result = $st->fetch(); @@ -78,55 +154,21 @@ private function checkKey($key) { } - private function user($request) { - $response = new stdClass(); + private function hasPrivilege($privilege) { + // get subject + $subject = strtok($privilege, '.'); - if ($request == 'profile') { - $response->profile = new profile($_GET['user'], true); + if ($subject = 'public') { + return true; } - $this->respond(200, $response); - } - - + $globalPrivilege = $subject . '.*'; - - - public function process() { - if (!isset($_GET['action'])) - throw new Exception('Invalid request'); - - switch ($_GET['action']) { - case 'irc.log': $this->logIrc(); break; - default: throw new Exception('Invalid request'); + if (!in_array($privilege, $this->privileges) && + !in_array($globalPrivilege, $this->privileges)) { + $this->respond(403); } } - - - /* IRC */ - public function logIrc() { - if (!isset($_POST['nick']) || !isset($_POST['chan']) || !isset($_POST['msg'])) - throw new Exception('Missing data fields'); - - $_POST['msg'] = preg_replace('/\x01/', '', $_POST['msg']); - - $st = $this->app->db->prepare('INSERT INTO irc_logs (`nick`, `channel`, `log`) - VALUES (:nick, :chan, :msg)'); - $result = $st->execute(array(':nick' => $_POST['nick'], ':chan' => $_POST['chan'], ':msg' => $_POST['msg'])); - - - // Calculate stats - $st = $this->app->db->prepare('INSERT INTO irc_stats (`nick`, `lines`, `words`, `chars`) - VALUES (:nick, :lines, :words, :chars) - ON DUPLICATE KEY UPDATE `lines`=`lines`+:lines, `words`=`words`+:words, `chars`=`chars`+:chars, `time`=NOW()'); - - $st->bindValue(':nick', $_POST['nick'], PDO::PARAM_INT); - $st->bindValue(':lines', 1, PDO::PARAM_INT); - $st->bindValue(':words', str_word_count($_POST['msg']), PDO::PARAM_INT); - $st->bindValue(':chars', strlen($_POST['msg']), PDO::PARAM_INT); - $result = $st->execute(); - } - } -?> \ No newline at end of file +?> diff --git a/html/example.htaccess b/html/example.htaccess index a4511b6..dc96ddb 100644 --- a/html/example.htaccess +++ b/html/example.htaccess @@ -63,7 +63,8 @@ RewriteRule ^levels/main/extras/ssap.xml$ /levels/extras/ssap.xml [L] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^user/(.+)/friends$ /user/index.php?user=$1&friends&%{QUERY_STRING} [L] -RewriteRule ^user/(.+)/userbar$ /user/index.php?user=$1&image&%{QUERY_STRING} [L] +RewriteRule ^user/(.+)/userbar.png$ /user/userbar.php?user=$1 [L] +RewriteRule ^user/userbar.png$ /user/userbar.php [L] RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f @@ -73,6 +74,15 @@ RewriteRule ^user/(.+)$ /user/index.php?user=$1&%{QUERY_STRING} RewriteRule ^inbox/([0-9]+)$ /inbox/?view=$1&%{QUERY_STRING} [L] RewriteRule ^inbox/compose$ /inbox/?compose&%{QUERY_STRING} [L] +# API +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^api/?$ /handle_api.php?%{QUERY_STRING} + +# INBOX +RewriteRule ^inbox/([0-9]+)$ /inbox/?view=$1&%{QUERY_STRING} [L] +RewriteRule ^inbox/compose$ /inbox/?compose&%{QUERY_STRING} [L] + # ------------------------------------------------------------------------------ # | Proper MIME types for all files | From cc348d73ad31d4748668cb8c483d67a709f174ee Mon Sep 17 00:00:00 2001 From: Flabby Date: Fri, 20 Feb 2015 23:08:44 +0000 Subject: [PATCH 018/112] Added ability to remove flags from admin panel. Flags are now marked as valid or invalid and not simply removed --- files/class.admin.php | 2 +- files/class.forum.php | 51 ++++++++++++++++++--- files/templates/admin_forum_post_flags.html | 2 +- html/admin/forum.php | 12 ++++- html/files/js/admin_forum.js | 10 ++-- 5 files changed, 60 insertions(+), 17 deletions(-) diff --git a/files/class.admin.php b/files/class.admin.php index 52c5433..2710cc1 100644 --- a/files/class.admin.php +++ b/files/class.admin.php @@ -41,7 +41,7 @@ public function getLatestForumFlags($limit = true) { ON forum_posts.thread_id = forum_threads.thread_id INNER JOIN users ON users.user_id = forum_posts.author - WHERE forum_posts.deleted = 0 AND forum_threads.deleted = 0 + WHERE forum_posts.deleted = 0 AND forum_threads.deleted = 0 AND forum_posts_flags.response = 0 GROUP BY forum_posts_flags.post_id ORDER BY `flags` DESC, `latest` DESC"; if ($limit) $sql .= " LIMIT 5"; diff --git a/files/class.forum.php b/files/class.forum.php index b720c95..03f81e2 100644 --- a/files/class.forum.php +++ b/files/class.forum.php @@ -102,6 +102,8 @@ public function getSections($parent=null) { } public function printThreadPost($post, $first=false, $last=false, $admin=false) { + if (!$post) return; + $post->first = $first; $post->last = $last; @@ -927,13 +929,13 @@ public function flagPost($post_id, $reason, $extra) { return $st->execute(array(':post_id'=>$post_id, ':uid'=>$this->app->user->uid, ':reason'=>$reason, ':extra'=>$extra)); } - public function removeFlags($post_id, $reward=false) { + public function removeFlags($post_id, $reward=false, $flag_id = null) { if (!$this->app->user->admin_forum_priv) return false; // If reward give all users who flagged a medal - if ($reward) { - $st = $this->app->db->prepare("SELECT user_id FROM forum_posts_flags WHERE post_id = :post_id"); + if ($post_id && $reward) { + $st = $this->app->db->prepare("SELECT user_id FROM forum_posts_flags WHERE post_id = :post_id AND response = 0"); $st->execute(array(':post_id'=>$post_id)); if ($result = $st->fetchAll()) { foreach($result AS $res) { @@ -942,15 +944,26 @@ public function removeFlags($post_id, $reward=false) { } } - $st = $this->app->db->prepare("DELETE FROM forum_posts_flags WHERE post_id = :post_id"); - return $st->execute(array(':post_id'=>$post_id)); + if ($reward) { + $response = 1; + } else { + $response = -1; + } + + if ($post_id) { + $st = $this->app->db->prepare("UPDATE forum_posts_flags SET response = :response WHERE post_id = :post_id"); + return $st->execute(array(':response'=>$response, ':post_id'=>$post_id)); + } else { + $st = $this->app->db->prepare("UPDATE forum_posts_flags SET response = :response WHERE flag_id = :flag_id"); + return $st->execute(array(':response'=>$response, ':flag_id'=>$flag_id)); + } } public function getPostFlags($post_id) { if (!$this->app->user->admin_forum_priv) return false; - $st = $this->app->db->prepare("SELECT username, reason, details FROM forum_posts_flags INNER JOIN users ON users.user_id = forum_posts_flags.user_id where post_id = :post_id"); + $st = $this->app->db->prepare("SELECT flag_id AS id, username, reason, details FROM forum_posts_flags INNER JOIN users ON users.user_id = forum_posts_flags.user_id where post_id = :post_id AND response = 0"); $st->execute(array(':post_id'=>$post_id)); return $st->fetchAll(); } @@ -972,7 +985,7 @@ function validatePost($body, $edit=false) { $st = $this->app->db->prepare('SELECT user_id, karma.karma FROM users LEFT JOIN (SELECT SUM(karma) AS karma, forum_posts.author FROM users_forum INNER JOIN forum_posts ON users_forum.post_id = forum_posts.post_id AND forum_posts.deleted = 0 GROUP BY forum_posts.author) karma - ON karma.author = u.user_id + ON karma.author = users.user_id WHERE user_id = :uid AND (karma < 0 AND score <= 0) LIMIT 1'); $st->execute(array(':uid'=>$this->app->user->uid)); @@ -1017,5 +1030,29 @@ function validatePost($body, $edit=false) { return true; } + + public function getStats() { + $stats = $this->app->cache->get('forum_stats', 5); + + if ($stats) + return json_decode($stats); + + $stats = new stdClass(); + + $st = $this->app->db->query("SELECT count(*) AS `count` FROM forum_threads WHERE deleted = 0"); + $result = $st->fetch(); + $stats->threads = $result->count; + + $st = $this->app->db->query("SELECT count(*) AS `count` FROM forum_posts WHERE deleted = 0"); + $result = $st->fetch(); + $stats->posts = $result->count; + + $result = $this->app->db->query('SELECT `author` FROM forum_posts WHERE deleted = 0 GROUP BY author'); + $stats->members = $result->rowCount(); + + $this->app->cache->set('forum_stats', json_encode($stats)); + + return $stats; + } } ?> diff --git a/files/templates/admin_forum_post_flags.html b/files/templates/admin_forum_post_flags.html index 5d0a5b8..25cc6a7 100644 --- a/files/templates/admin_forum_post_flags.html +++ b/files/templates/admin_forum_post_flags.html @@ -3,7 +3,7 @@

Flags

{% endif %} \ No newline at end of file diff --git a/html/admin/forum.php b/html/admin/forum.php index 2884279..2cbd5cf 100644 --- a/html/admin/forum.php +++ b/html/admin/forum.php @@ -4,6 +4,14 @@ $page_title = 'Admin - Forum'; define("PAGE_PRIV", "admin_forum"); + require_once('init.php'); + + // Remove post flag + if (isset($_GET['remove'])) { + $app->forum->removeFlags(false, true, $_GET['remove']); + die(); + } + require_once('header.php'); if (!isset($_GET['id'])) { @@ -35,7 +43,7 @@ die(); } - $thread = $app->forum->getThread($post->thread_id, 1, 50, true); + $thread = $app->forum->getThread($post->thread_id, 1, 250, true); if (!$thread) { $app->utils->message('Thread not found'); require_once('footer.php'); @@ -64,7 +72,7 @@


- twig->render('admin_forum_post_flags.html', array('flags' => $flags)); ?> + twig->render('admin_forum_post_flags.html', array('post' => $post->post_id, 'flags' => $flags)); ?>

Post

\ No newline at end of file diff --git a/files/elements/widgets/login_google_auth.php b/files/elements/widgets/login_google_auth.php new file mode 100644 index 0000000..f1cc746 --- /dev/null +++ b/files/elements/widgets/login_google_auth.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/files/vendor/GoogleAuthenticator.php b/files/vendor/GoogleAuthenticator.php new file mode 100644 index 0000000..9376e81 --- /dev/null +++ b/files/vendor/GoogleAuthenticator.php @@ -0,0 +1,208 @@ +_getBase32LookupTable(); + unset($validChars[32]); + + $secret = ''; + for ($i = 0; $i < $secretLength; $i++) { + $secret .= $validChars[array_rand($validChars)]; + } + return $secret; + } + + /** + * Calculate the code, with given secret and point in time + * + * @param string $secret + * @param int|null $timeSlice + * @return string + */ + public function getCode($secret, $timeSlice = null) + { + if ($timeSlice === null) { + $timeSlice = floor(time() / 30); + } + + $secretkey = $this->_base32Decode($secret); + + // Pack time into binary string + $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); + // Hash it with users secret key + $hm = hash_hmac('SHA1', $time, $secretkey, true); + // Use last nipple of result as index/offset + $offset = ord(substr($hm, -1)) & 0x0F; + // grab 4 bytes of the result + $hashpart = substr($hm, $offset, 4); + + // Unpak binary value + $value = unpack('N', $hashpart); + $value = $value[1]; + // Only 32 bits + $value = $value & 0x7FFFFFFF; + + $modulo = pow(10, $this->_codeLength); + return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT); + } + + /** + * Get QR-Code URL for image, from google charts + * + * @param string $name + * @param string $secret + * @param string $title + * @return string + */ + public function getQRCodeGoogleUrl($name, $secret, $title = null) { + $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.''); + if(isset($title)) { + $urlencoded .= urlencode('&issuer='.urlencode($title)); + } + return 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl='.$urlencoded.''; + } + + /** + * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now + * + * @param string $secret + * @param string $code + * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) + * @param int|null $currentTimeSlice time slice if we want use other that time() + * @return bool + */ + public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) + { + if ($currentTimeSlice === null) { + $currentTimeSlice = floor(time() / 30); + } + + for ($i = -$discrepancy; $i <= $discrepancy; $i++) { + $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); + if ($calculatedCode == $code ) { + return true; + } + } + + return false; + } + + /** + * Set the code length, should be >=6 + * + * @param int $length + * @return PHPGangsta_GoogleAuthenticator + */ + public function setCodeLength($length) + { + $this->_codeLength = $length; + return $this; + } + + /** + * Helper class to decode base32 + * + * @param $secret + * @return bool|string + */ + protected function _base32Decode($secret) + { + if (empty($secret)) return ''; + + $base32chars = $this->_getBase32LookupTable(); + $base32charsFlipped = array_flip($base32chars); + + $paddingCharCount = substr_count($secret, $base32chars[32]); + $allowedValues = array(6, 4, 3, 1, 0); + if (!in_array($paddingCharCount, $allowedValues)) return false; + for ($i = 0; $i < 4; $i++){ + if ($paddingCharCount == $allowedValues[$i] && + substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) return false; + } + $secret = str_replace('=','', $secret); + $secret = str_split($secret); + $binaryString = ""; + for ($i = 0; $i < count($secret); $i = $i+8) { + $x = ""; + if (!in_array($secret[$i], $base32chars)) return false; + for ($j = 0; $j < 8; $j++) { + $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); + } + $eightBits = str_split($x, 8); + for ($z = 0; $z < count($eightBits); $z++) { + $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:""; + } + } + return $binaryString; + } + + /** + * Helper class to encode base32 + * + * @param string $secret + * @param bool $padding + * @return string + */ + protected function _base32Encode($secret, $padding = true) + { + if (empty($secret)) return ''; + + $base32chars = $this->_getBase32LookupTable(); + + $secret = str_split($secret); + $binaryString = ""; + for ($i = 0; $i < count($secret); $i++) { + $binaryString .= str_pad(base_convert(ord($secret[$i]), 10, 2), 8, '0', STR_PAD_LEFT); + } + $fiveBitBinaryArray = str_split($binaryString, 5); + $base32 = ""; + $i = 0; + while ($i < count($fiveBitBinaryArray)) { + $base32 .= $base32chars[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)]; + $i++; + } + if ($padding && ($x = strlen($binaryString) % 40) != 0) { + if ($x == 8) $base32 .= str_repeat($base32chars[32], 6); + elseif ($x == 16) $base32 .= str_repeat($base32chars[32], 4); + elseif ($x == 24) $base32 .= str_repeat($base32chars[32], 3); + elseif ($x == 32) $base32 .= $base32chars[32]; + } + return $base32; + } + + /** + * Get array with all 32 characters for decoding from/encoding to base32 + * + * @return array + */ + protected function _getBase32LookupTable() + { + return array( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23 + 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31 + '=' // padding char + ); + } +} \ No newline at end of file diff --git a/html/index.php b/html/index.php index 5b049e7..904d176 100644 --- a/html/index.php +++ b/html/index.php @@ -104,7 +104,13 @@ ?>

Login

- +

Register

diff --git a/html/settings/2-step.php b/html/settings/2-step.php new file mode 100644 index 0000000..6fec146 --- /dev/null +++ b/html/settings/2-step.php @@ -0,0 +1,63 @@ +page->title = 'Settings - 2 Step Authentication'; + + require_once('header.php'); + + $tab = '2-step'; + include('elements/tabs_settings.php'); + + $ga = new gauth(); + + $st = $app->db->prepare('SELECT g_auth, g_secret FROM users WHERE user_id = :uid'); + $st->execute(array(':uid' => $app->user->uid)); + $step = $st->fetch(); +?> + +

2 Step Authentication

+

2-Step Authentication adds an extra layer of security to your HackThis Account, drastically reducing the chances of having your account stolen. To break into an account with 2-Step Authentication, bad guys would not only have to know your username and password, they'd also have to get a hold of your phone.

+ +

Google Authenticator

+

Google Authenticator is a product developed by Google which allows the user to make use of TOTP.
When enabled you will be asked for a code from your Google Authenicator app on your mobile device when logging into HackThis. It is available for Apple and Android devices

+ +g_auth != 1) && !isset($_GET['google'])) { +?> +

Enable Google Authenticator

+getQRCodeGoogleUrl('HackThis - '.$app->user->username, $step->g_secret); +?> +

Your Google Authenticator QR Code

+

+

Disable Google Authenticator

+g_auth != 1)) { + $secret = $ga->createSecret(); + $st = $app->db->prepare('UPDATE users SET g_auth = 1, g_secret = :secret WHERE user_id = :uid LIMIT 1'); + $status = $st->execute(array(':secret' => $secret, ':uid' => $app->user->uid)); + $qrCodeUrl = $ga->getQRCodeGoogleUrl('HackThis - '.$app->user->username, $secret); +?> + +

Please scan the below QR code using your Google Authenticator App to add the account

+

+

Disable Google Authenticator

+ +g_auth == 1)) { + $st = $app->db->prepare('UPDATE users SET g_auth = 0, g_secret = NULL WHERE user_id = :uid LIMIT 1'); + $status = $st->execute(array(':uid' => $app->user->uid)); +?> + +

Google Authenticator Disabled
It is now ok for you to remove your HackThis account from your Google Authenticator app.

+

Enable Google Authenticator

+ + \ No newline at end of file diff --git a/sql/schema.sql b/sql/schema.sql index 21f0e69..c932147 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -13,6 +13,8 @@ CREATE TABLE IF NOT EXISTS users ( `email` varchar(128) NOT NULL, `verified` tinyint(1) NOT NULL DEFAULT 0, `score` mediumint(6) NOT NULL DEFAULT 0, + `g_auth` tinyint(1), + `g_secret` varchar(255), PRIMARY KEY (`user_id`), UNIQUE KEY (`username`), UNIQUE KEY (`email`) From 891772cd5dc5801f58a316220215166effa724ac Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Fri, 12 Jun 2015 15:19:14 +0000 Subject: [PATCH 053/112] Fixed errors with 2FA --- files/class.user.php | 1 + files/vendor/{GoogleAuthenticator.php => gauth.php} | 4 ++-- html/settings/2-step.php | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) rename files/vendor/{GoogleAuthenticator.php => gauth.php} (99%) diff --git a/files/class.user.php b/files/class.user.php index 95ceded..cbb9ffa 100644 --- a/files/class.user.php +++ b/files/class.user.php @@ -492,6 +492,7 @@ public function oauth($provider, $id) { private function googleAuth($authCode) { // setup Google Auth class + require('vendor/gauth.php'); $ga = new gauth(); $st = $this->app->db->prepare('SELECT g_secret FROM users WHERE user_id = :uid'); $st->execute(array(':uid' => $_SESSION['g_auth'])); diff --git a/files/vendor/GoogleAuthenticator.php b/files/vendor/gauth.php similarity index 99% rename from files/vendor/GoogleAuthenticator.php rename to files/vendor/gauth.php index 9376e81..abe64f2 100644 --- a/files/vendor/GoogleAuthenticator.php +++ b/files/vendor/gauth.php @@ -9,7 +9,7 @@ * @link http://www.phpgangsta.de/ */ -class PHPGangsta_GoogleAuthenticator +class gauth { protected $_codeLength = 6; @@ -205,4 +205,4 @@ protected function _getBase32LookupTable() '=' // padding char ); } -} \ No newline at end of file +} diff --git a/html/settings/2-step.php b/html/settings/2-step.php index 6fec146..86bd64d 100644 --- a/html/settings/2-step.php +++ b/html/settings/2-step.php @@ -9,7 +9,8 @@ $tab = '2-step'; include('elements/tabs_settings.php'); - + + require('vendor/gauth.php'); $ga = new gauth(); $st = $app->db->prepare('SELECT g_auth, g_secret FROM users WHERE user_id = :uid'); @@ -60,4 +61,4 @@ \ No newline at end of file +?> From 3ab3152c528998ecedc28c25741401ef41042988 Mon Sep 17 00:00:00 2001 From: Luke Date: Fri, 12 Jun 2015 16:22:01 +0100 Subject: [PATCH 054/112] Tweaked wording on enter 2FA code login box --- files/elements/widgets/login_google_auth.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/files/elements/widgets/login_google_auth.php b/files/elements/widgets/login_google_auth.php index f1cc746..ab4ab82 100644 --- a/files/elements/widgets/login_google_auth.php +++ b/files/elements/widgets/login_google_auth.php @@ -1,11 +1,6 @@ From f670a94a81f8ba0233a15eec71df6480f93247bc Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 15 Jun 2015 11:28:03 +0100 Subject: [PATCH 057/112] Added extra forum checks Don't allow users to post in the first 10 mins of being a member and don't allow users to post on more than 2 threads, per hour for the first 24 hours --- files/class.forum.php | 67 ++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/files/class.forum.php b/files/class.forum.php index 03f81e2..0b2cde9 100644 --- a/files/class.forum.php +++ b/files/class.forum.php @@ -416,14 +416,7 @@ public function newThread($section, $title, $body) { if (!$title || strlen($title) < 3) return "Title must be longer than three characters"; - // Check for spam - pretty much always new threads - $regex = "/(". implode('|', $this->banned) .")/i"; - $matched = preg_match_all($regex, $title, $matches) + preg_match_all($regex, $body, $matches); - if ($matched > 2) { - // Could implement http://www.stopforumspam.com and ban user - return "Banned words found in content"; - } - + // Post validation happens within transaction $section_id = $section->id; $slug = $section->slug . '/' . $this->app->utils->generateSlug($title); @@ -743,9 +736,6 @@ public function newPost($thread_id, $body) { VALUES (:uid, :thread_id, 1) ON DUPLICATE KEY UPDATE `watching` = 1"); $st->execute(array(':thread_id'=>$thread_id, ':uid'=>$this->app->user->uid)); - - - // Check for forum medal $st = $this->app->db->prepare('SELECT COUNT(post_id) AS posts FROM forum_posts @@ -981,18 +971,6 @@ function validatePost($body, $edit=false) { $this->error = "You have been banned from posting messages"; return false; } - - $st = $this->app->db->prepare('SELECT user_id, karma.karma - FROM users - LEFT JOIN (SELECT SUM(karma) AS karma, forum_posts.author FROM users_forum INNER JOIN forum_posts ON users_forum.post_id = forum_posts.post_id AND forum_posts.deleted = 0 GROUP BY forum_posts.author) karma - ON karma.author = users.user_id - WHERE user_id = :uid AND (karma < 0 AND score <= 0) - LIMIT 1'); - $st->execute(array(':uid'=>$this->app->user->uid)); - if ($res = $st->fetch()) { - $this->error = "You have been temporarily banned from posting messages"; - return false; - } //check post length if (str_word_count($body) < 2) { @@ -1000,7 +978,50 @@ function validatePost($body, $edit=false) { return false; } + // Check for spam - pretty much always new threads + $regex = "/(". implode('|', $this->banned) .")/i"; + $matched = preg_match_all($regex, $body, $matches); + if ($matched > 2) { + // Could implement http://www.stopforumspam.com and ban user + $this->error = "Banned words found in content"; + return false; + } + if (!$edit) { + // Don't allow users to post in the first 10 mins of being a member + $st = $this->app->db->prepare('select TIMESTAMPDIFF(MINUTE, joined, NOW()) AS `joining` from users_activity WHERE user_id = :uid'); + $st->execute(array(':uid'=>$this->app->user->uid)); + if ($res = $st->fetch() && $res->joined < 15) { + $this->error = "New users can not post in the forum for the first 15 minutes. You have been registered for {$res->joined} minutes."; + return false; + } + + $minutes_joined = $res->joined; + $hours_joined = ceil($minutes_joined / 60); + + if ($hours_joined < 24) { + // Don't allow users to post on more than 2 threads, per hour for the first 24 hours + $st = $this->app->db->prepare('select COUNT(DISTINCT(`thread_id`)) AS `threads` FROM forum_posts where author = :uid'); + $st->execute(array(':uid'=>$this->app->user->uid)); + if ($res = $st->fetch() && $res->threads > $hours_joined * 2) { + $this->error = "New users are only allowed to post in two threads, per hour, for the first 24 hours."; + return false; + } + } + + // Don't allow user to post if they have karma < 0 and score <= 0 + $st = $this->app->db->prepare('SELECT user_id, karma.karma + FROM users + LEFT JOIN (SELECT SUM(karma) AS karma, forum_posts.author FROM users_forum INNER JOIN forum_posts ON users_forum.post_id = forum_posts.post_id AND forum_posts.deleted = 0 GROUP BY forum_posts.author) karma + ON karma.author = users.user_id + WHERE user_id = :uid AND (karma < 0 AND score <= 0) + LIMIT 1'); + $st->execute(array(':uid'=>$this->app->user->uid)); + if ($res = $st->fetch()) { + $this->error = "You have been temporarily banned from posting messages"; + return false; + } + //check when last post was made // The time left calculation was previously done using From cf977ec311187f5f5c7f520375fed0cc8226a320 Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Mon, 15 Jun 2015 11:38:16 +0100 Subject: [PATCH 058/112] Fixed mistake in forum verification --- files/class.forum.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/class.forum.php b/files/class.forum.php index 0b2cde9..bc6cd56 100644 --- a/files/class.forum.php +++ b/files/class.forum.php @@ -989,7 +989,7 @@ function validatePost($body, $edit=false) { if (!$edit) { // Don't allow users to post in the first 10 mins of being a member - $st = $this->app->db->prepare('select TIMESTAMPDIFF(MINUTE, joined, NOW()) AS `joining` from users_activity WHERE user_id = :uid'); + $st = $this->app->db->prepare('SELECT TIMESTAMPDIFF(MINUTE, joined, NOW()) AS `joined` from users_activity WHERE user_id = :uid'); $st->execute(array(':uid'=>$this->app->user->uid)); if ($res = $st->fetch() && $res->joined < 15) { $this->error = "New users can not post in the forum for the first 15 minutes. You have been registered for {$res->joined} minutes."; @@ -1001,7 +1001,7 @@ function validatePost($body, $edit=false) { if ($hours_joined < 24) { // Don't allow users to post on more than 2 threads, per hour for the first 24 hours - $st = $this->app->db->prepare('select COUNT(DISTINCT(`thread_id`)) AS `threads` FROM forum_posts where author = :uid'); + $st = $this->app->db->prepare('SELECT COUNT(DISTINCT(`thread_id`)) AS `threads` FROM forum_posts where author = :uid'); $st->execute(array(':uid'=>$this->app->user->uid)); if ($res = $st->fetch() && $res->threads > $hours_joined * 2) { $this->error = "New users are only allowed to post in two threads, per hour, for the first 24 hours."; From 59b0df7442a1857a97b87dd17fd2ea509c240b8b Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Mon, 15 Jun 2015 11:43:06 +0100 Subject: [PATCH 059/112] Moved fetch outside if, fixing problem restricting all users posting in the fourm --- files/class.forum.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/files/class.forum.php b/files/class.forum.php index bc6cd56..26eaf8c 100644 --- a/files/class.forum.php +++ b/files/class.forum.php @@ -991,7 +991,9 @@ function validatePost($body, $edit=false) { // Don't allow users to post in the first 10 mins of being a member $st = $this->app->db->prepare('SELECT TIMESTAMPDIFF(MINUTE, joined, NOW()) AS `joined` from users_activity WHERE user_id = :uid'); $st->execute(array(':uid'=>$this->app->user->uid)); - if ($res = $st->fetch() && $res->joined < 15) { + $res = $st->fetch(); + + if ($res->joined < 15) { $this->error = "New users can not post in the forum for the first 15 minutes. You have been registered for {$res->joined} minutes."; return false; } @@ -1003,7 +1005,9 @@ function validatePost($body, $edit=false) { // Don't allow users to post on more than 2 threads, per hour for the first 24 hours $st = $this->app->db->prepare('SELECT COUNT(DISTINCT(`thread_id`)) AS `threads` FROM forum_posts where author = :uid'); $st->execute(array(':uid'=>$this->app->user->uid)); - if ($res = $st->fetch() && $res->threads > $hours_joined * 2) { + $res = $st->fetch(); + + if ($res->threads > $hours_joined * 2) { $this->error = "New users are only allowed to post in two threads, per hour, for the first 24 hours."; return false; } From b0a96ec52a131588452987e7b0a2052e27257234 Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Tue, 16 Jun 2015 15:25:03 +0100 Subject: [PATCH 060/112] Make user.googleAuth function more flexible --- files/class.user.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/files/class.user.php b/files/class.user.php index cbb9ffa..172ebf9 100644 --- a/files/class.user.php +++ b/files/class.user.php @@ -490,19 +490,23 @@ public function oauth($provider, $id) { } } - private function googleAuth($authCode) { + public function googleAuth($authCode, $uid=null) { + if (!$uid) { + $uid = $_SESSION['g_auth']; + } + // setup Google Auth class require('vendor/gauth.php'); $ga = new gauth(); $st = $this->app->db->prepare('SELECT g_secret FROM users WHERE user_id = :uid'); - $st->execute(array(':uid' => $_SESSION['g_auth'])); + $st->execute(array(':uid' => $uid)); $secret = $st->fetch(); // verify Google code $checkResult = $ga->verifyCode($secret->g_secret, $authCode, 2); // 2 = 2*30sec clock tolerance if ($checkResult) { - $this->uid = $_SESSION['g_auth']; + $this->uid = $uid; // if ok unset the session and log in unset($_SESSION['g_auth']); @@ -512,12 +516,16 @@ private function googleAuth($authCode) { $this->app->ssga->set_event('user', 'login', 'GAuth', $this->uid); $this->app->ssga->send(); $this->createSession(); + + return true; } else { unset($_SESSION['g_auth']); $app->user->loggedIn = false; $app->user->g_auth = false; $this->login_error = 'Incorrect Authenticator code'; + + return false; } } From fa3bd7e3d89a2b0e81a191e49a884f48fb45f20d Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Tue, 16 Jun 2015 15:25:21 +0100 Subject: [PATCH 061/112] Disable old users from using expired password hashes --- files/class.user.php | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/files/class.user.php b/files/class.user.php index 172ebf9..b7c2b72 100644 --- a/files/class.user.php +++ b/files/class.user.php @@ -255,6 +255,7 @@ public function login($user, $pass) { if($row->g_auth == 1) { // set Google Auth session and don't log in $_SESSION['g_auth'] = $this->uid; + $this->g_auth = $this->uid; } else { $this->loggedIn = true; @@ -270,7 +271,6 @@ public function login($user, $pass) { } // User isn't valid in LDAP, double check with MySQL backup - $st = $this->app->db->prepare('SELECT u.user_id, u.password, u.old_password, g_auth, IFNULL(priv.site_priv, 1) as site_priv FROM users u LEFT JOIN users_priv priv @@ -283,36 +283,10 @@ public function login($user, $pass) { $this->login_error = 'Invalid login details'; if ($row) { if ($row->old_password == 1) { - $user = strtolower($user); - $userhash = md5($user[0]."h97".md5(md5($pass))."t77Ds"); - - if ($row->password === $userhash) { - // Store new password - $hash = crypt($pass, $this->salt()); - $st = $this->app->db->prepare('UPDATE users SET password = :hash, old_password = 0 WHERE user_id = :uid LIMIT 1'); - $status = $st->execute(array(':uid' => $row->user_id, ':hash' => $hash)); - - if (!$row->site_priv) { - $this->login_error = 'Account has been banned'; - return false; - } - - $this->uid = $row->user_id; - - // Check if Google Auth is enabled - if($row->g_auth == 1) { - // set Google Auth session and don't log in - $_SESSION['g_auth'] = $this->uid; - } else { - $this->loggedIn = true; - - // Setup GA event - $this->app->ssga->set_event('user', 'login', 'default', $this->uid); - $this->app->ssga->send(); - - $this->createSession(); - } - } + $this->app->ssga->set_event('user', 'login', 'old_password', $this->uid); + $this->app->ssga->send(); + $this->login_error = 'Your password has expired, click here to generate a new one.'; + return false; } else { if ($row->password == crypt($pass, $row->password)) { From e2fbebecff1ade5a4a3d3fece10e801747a08564 Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Mon, 22 Jun 2015 10:45:17 +0000 Subject: [PATCH 062/112] Added online status indicator to levels that need it --- files/class.levels.php | 18 +++++++++++++++++- files/elements/level_editor/edit_level.php | 8 ++++++-- files/elements/levels/header.php | 5 ++++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/files/class.levels.php b/files/class.levels.php index 6ed5a7d..323a4b3 100644 --- a/files/class.levels.php +++ b/files/class.levels.php @@ -171,6 +171,20 @@ public function getLevel($group, $name, $noSkip=false) { $result = $st->fetch(); $level->count = $result->completed; + // If level has uptime code, check level status + if ($level->data['uptime']) { + $level->online = $this->app->cache->get('level_uptime_' . $level->data['uptime'], 5); + + if (!$level->online) { + $status = file_get_contents("https://api.uptimerobot.com/getMonitors?apiKey=".$level->data['uptime']."&format=json&noJsonCallback=1"); + $status = json_decode($status); + $level->online = $status->monitors->monitor[0]->status == 2 ? 'online' : 'offline'; + + $this->app->cache->set('level_uptime_' . $level->data['uptime'], $level->online); + } + } + + // // // Get latest // $sql = "SELECT username, completed FROM users_levels INNER JOIN users ON users.user_id = users_levels.user_id WHERE completed > 0 AND level_id = :lid AND users.user_id != 69 ORDER BY completed DESC LIMIT 1"; $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` DESC LIMIT 1) `a` ON `a`.user_id = users.user_id;"; @@ -333,7 +347,6 @@ function editLevel($id, $new = false) { if (!$this->app->user->admin_site_priv) return false; - $changes = array(); if (!$new) { @@ -351,6 +364,9 @@ function editLevel($id, $new = false) { if (isset($_POST['reward']) && is_numeric($_POST['reward'])) { $changes['reward'] = $_POST['reward']; } + if (isset($_POST['uptime']) && strlen($_POST['uptime'])) { + $changes['uptime'] = $_POST['uptime']; + } if (isset($_POST['description']) && strlen($_POST['description'])) { $changes['description'] = $_POST['description']; } diff --git a/files/elements/level_editor/edit_level.php b/files/elements/level_editor/edit_level.php index 0e26e91..0e4319e 100644 --- a/files/elements/level_editor/edit_level.php +++ b/files/elements/level_editor/edit_level.php @@ -57,7 +57,7 @@

- +
    levels->getGroups(); @@ -74,6 +74,10 @@
+
+
+ +
@@ -108,4 +112,4 @@ " name="token"> - \ No newline at end of file + diff --git a/files/elements/levels/header.php b/files/elements/levels/header.php index b66e944..082d534 100644 --- a/files/elements/levels/header.php +++ b/files/elements/levels/header.php @@ -25,6 +25,9 @@ '>completed?'Completed':'Incomplete';?>
+online): ?> +
'>Level online;?>
+ data['description']) && (!isset($level->attempt) || $level->attempt !== true)): @@ -43,4 +46,4 @@ $app->utils->message('Invalid details'); } ?> -
\ No newline at end of file +
From 5b19cf36b59011bae3896e2df083a137d1c074bd Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Mon, 22 Jun 2015 12:27:04 +0000 Subject: [PATCH 063/112] Remove ability to use old password hashes --- files/class.user.php | 88 ++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/files/class.user.php b/files/class.user.php index cbb9ffa..d81d3e6 100644 --- a/files/class.user.php +++ b/files/class.user.php @@ -40,7 +40,10 @@ public function __construct($app) { //Check if user is registering in if (isset($_GET['register'])) { - $this->reg_error = $this->register(); + $reg_error = $this->register(); + if (!is_numeric($reg_error)) { + $this->reg_error = $reg_error; + } } // Check if user is logged in @@ -255,6 +258,7 @@ public function login($user, $pass) { if($row->g_auth == 1) { // set Google Auth session and don't log in $_SESSION['g_auth'] = $this->uid; + $this->g_auth = $this->uid; } else { $this->loggedIn = true; @@ -283,36 +287,10 @@ public function login($user, $pass) { $this->login_error = 'Invalid login details'; if ($row) { if ($row->old_password == 1) { - $user = strtolower($user); - $userhash = md5($user[0]."h97".md5(md5($pass))."t77Ds"); - - if ($row->password === $userhash) { - // Store new password - $hash = crypt($pass, $this->salt()); - $st = $this->app->db->prepare('UPDATE users SET password = :hash, old_password = 0 WHERE user_id = :uid LIMIT 1'); - $status = $st->execute(array(':uid' => $row->user_id, ':hash' => $hash)); - - if (!$row->site_priv) { - $this->login_error = 'Account has been banned'; - return false; - } - - $this->uid = $row->user_id; - - // Check if Google Auth is enabled - if($row->g_auth == 1) { - // set Google Auth session and don't log in - $_SESSION['g_auth'] = $this->uid; - } else { - $this->loggedIn = true; - - // Setup GA event - $this->app->ssga->set_event('user', 'login', 'default', $this->uid); - $this->app->ssga->send(); - - $this->createSession(); - } - } + $this->app->ssga->set_event('user', 'login', 'old_password', $this->uid); + $this->app->ssga->send(); + $this->login_error = 'Your password has expired, click here to generate a new one.'; + return false; } else { if ($row->password == crypt($pass, $row->password)) { @@ -490,19 +468,23 @@ public function oauth($provider, $id) { } } - private function googleAuth($authCode) { + public function googleAuth($authCode, $uid=null) { + if (!$uid) { + $uid = $_SESSION['g_auth']; + } + // setup Google Auth class require('vendor/gauth.php'); $ga = new gauth(); $st = $this->app->db->prepare('SELECT g_secret FROM users WHERE user_id = :uid'); - $st->execute(array(':uid' => $_SESSION['g_auth'])); + $st->execute(array(':uid' => $uid)); $secret = $st->fetch(); // verify Google code $checkResult = $ga->verifyCode($secret->g_secret, $authCode, 2); // 2 = 2*30sec clock tolerance if ($checkResult) { - $this->uid = $_SESSION['g_auth']; + $this->uid = $uid; // if ok unset the session and log in unset($_SESSION['g_auth']); @@ -512,12 +494,16 @@ private function googleAuth($authCode) { $this->app->ssga->set_event('user', 'login', 'GAuth', $this->uid); $this->app->ssga->send(); $this->createSession(); + + return true; } else { unset($_SESSION['g_auth']); $app->user->loggedIn = false; $app->user->g_auth = false; $this->login_error = 'Incorrect Authenticator code'; + + return false; } } @@ -583,9 +569,21 @@ private function regenerateRememberToken() { setcookie('autologin', $token, time()+60*60*24*7, '/', 'hackthis.co.uk', true, true); } - public function register() { + public function register($username=null, $password=null, $email=null) { + if (!$username) { + $username = $_POST['reg_username']; + } + if (!$password) { + $password = $_POST['reg_password']; + $password2 = $_POST['reg_password_2']; + } else { + $password2 = $password; + } + if (!$email) { + $email = $_POST['reg_email']; + } + //Input check - $username = $_POST['reg_username']; if (!$this->app->utils->check_user($username)) return "Invalid username"; @@ -595,15 +593,13 @@ public function register() { if ($st->fetch(PDO::FETCH_ASSOC)) return "Username already in use"; - $pass = $_POST['reg_password']; - if (!isset($pass) || strlen($pass) < 5) + if (!isset($password) || strlen($password) < 5) return "Invalid password"; - if ($pass !== $_POST['reg_password_2']) + if ($password !== $password2) return "Passwords don't match"; - $hash = crypt($pass, $this->salt()); + $hash = crypt($password, $this->salt()); - $email = $_POST['reg_email']; if (!$this->app->utils->check_email($email)) return "Invalid email address"; @@ -615,7 +611,7 @@ public function register() { // Check if IP has created more than 10 accounts $st = $this->app->db->prepare('SELECT count(*) AS `count` FROM users_registration WHERE ip=?'); - $ip = ip2long($_SERVER['REMOTE_ADDR']); + $ip = ip2long($_SERVER['REMOTE_ADDR']); $st->bindParam(1, $ip); $st->execute(); $res = $st->fetch(); @@ -635,7 +631,7 @@ public function register() { $uid = $this->app->db->lastInsertId(); // Add to LDAP - $this->app->ldap->createUser($uid, $username, $pass); + $this->app->ldap->createUser($uid, $username, $password); // Login user $this->loggedIn = true; @@ -659,6 +655,8 @@ public function register() { $result = $st->execute(array(':u' => $uid, ':i' => ip2long($_SERVER['REMOTE_ADDR']))); $this->createSession(); + + return $uid; } public function logout() { @@ -718,8 +716,8 @@ public function delete($password, $token) { $this->app->db->commit(); - // Remove session - $this->logout(); + // Remove session + $this->logout(); return true; } catch (PDOException $e) { From 0ec28f1979b64066261d824243c469b20c975ec6 Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Mon, 22 Jun 2015 12:31:08 +0000 Subject: [PATCH 064/112] Fixed merge --- files/class.user.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/files/class.user.php b/files/class.user.php index 1159e44..980348c 100644 --- a/files/class.user.php +++ b/files/class.user.php @@ -258,11 +258,7 @@ public function login($user, $pass) { if($row->g_auth == 1) { // set Google Auth session and don't log in $_SESSION['g_auth'] = $this->uid; -<<<<<<< HEAD - $this->g_auth = $this->uid; -======= $this->g_auth = $this->uid; ->>>>>>> fa3bd7e3d89a2b0e81a191e49a884f48fb45f20d } else { $this->loggedIn = true; From 3bfb8c8e8aed054a448f336e0d2ae4f127d02db6 Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Tue, 23 Jun 2015 12:00:51 +0100 Subject: [PATCH 065/112] Fixed warnings on level pages --- files/class.levels.php | 762 ++++++++++++++++--------------- files/elements/levels/header.php | 2 +- 2 files changed, 392 insertions(+), 372 deletions(-) diff --git a/files/class.levels.php b/files/class.levels.php index 323a4b3..17d09d9 100644 --- a/files/class.levels.php +++ b/files/class.levels.php @@ -1,234 +1,256 @@ app = $app; - } + public function __construct($app) { + $this->app = $app; + } - public function getList($uid=null, $category=null) { + public function getList($uid=null, $category=null) { // Check cache - $levels = json_decode($this->app->cache->get('level_list', 10)); - - if (!$levels) { - $sql = 'SELECT levels.level_id AS `id`, CONCAT(levels_groups.title, " Level ", levels.name) as `title`, levels.name, levels_groups.title as `group`, - LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri`, levels_completed.completed as `total_completed`, - levels_data.value AS `reward` - FROM levels - INNER JOIN levels_groups - ON levels_groups.title = levels.group - LEFT JOIN levels_data - ON levels_data.level_id = levels.level_id AND levels_data.key = "reward" - LEFT JOIN (SELECT COUNT(user_id) AS `completed`, level_id FROM users_levels WHERE completed > 0 AND user_id != 69 GROUP BY level_id) `levels_completed` - ON levels_completed.level_id = levels.level_id - ORDER BY levels_groups.order ASC, levels.level_id ASC'; + $levels = json_decode($this->app->cache->get('level_list', 10)); + + if (!$levels) { + $sql = 'SELECT levels.level_id AS `id`, CONCAT(levels_groups.title, " Level ", levels.name) as `title`, levels.name, levels_groups.title as `group`, + LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri`, levels_completed.completed as `total_completed`, + levels_data.value AS `reward` + FROM levels + INNER JOIN levels_groups + ON levels_groups.title = levels.group + LEFT JOIN levels_data + ON levels_data.level_id = levels.level_id AND levels_data.key = "reward" + LEFT JOIN (SELECT COUNT(user_id) AS `completed`, level_id FROM users_levels WHERE completed > 0 AND user_id != 69 GROUP BY level_id) `levels_completed` + ON levels_completed.level_id = levels.level_id + ORDER BY levels_groups.order ASC, levels.level_id ASC'; - $st = $this->app->db->prepare($sql); - $st->execute(); - $levels = $st->fetchAll(); + $st = $this->app->db->prepare($sql); + $st->execute(); + $levels = $st->fetchAll(); - $this->app->cache->set('level_list', json_encode($levels)); - } + $this->app->cache->set('level_list', json_encode($levels)); + } // Get list of completed levels - $sql = 'SELECT level_id, IF(users_levels.completed > 0, 2, 1) as `completed` FROM users_levels WHERE user_id = :uid'; - $st = $this->app->db->prepare($sql); - $st->bindValue(':uid', $uid?$uid:$this->app->user->uid); - $st->execute(); - $user_levels = $st->fetchAll(); + $sql = 'SELECT level_id, IF(users_levels.completed > 0, 2, 1) as `completed` FROM users_levels WHERE user_id = :uid'; + $st = $this->app->db->prepare($sql); + $st->bindValue(':uid', $uid?$uid:$this->app->user->uid); + $st->execute(); + $user_levels = $st->fetchAll(); // Create list - $list = array(); - foreach ($levels AS &$level) { + $list = array(); + foreach ($levels AS &$level) { // Check filter - if ($category && trim(strtolower(str_replace('+', '', $level->group))) != trim($category)) { - continue; - } + if ($category && trim(strtolower(str_replace('+', '', $level->group))) != trim($category)) { + continue; + } // Assign progress based on $users_levels - $level->progress = 0; - foreach ($user_levels AS $l) { - if ($l->level_id == $level->id) { - $level->progress = $l->completed; - break; + $level->progress = 0; + foreach ($user_levels AS $l) { + if ($l->level_id == $level->id) { + $level->progress = $l->completed; + break; + } } - } - if (!array_key_exists($level->group, $list)) { - $list[$level->group] = new stdClass(); - $list[$level->group]->levels = array(); + if (!array_key_exists($level->group, $list)) { + $list[$level->group] = new stdClass(); + $list[$level->group]->levels = array(); + } + array_push($list[$level->group]->levels, $level); } - array_push($list[$level->group]->levels, $level); + + return $list; } - return $list; - } + public function getGroups() { + $st = $this->app->db->prepare('SELECT title FROM levels_groups ORDER BY `order` ASC, `title` ASC'); + $st->execute(); + return $st->fetchAll(); + } - public function getGroups() { - $st = $this->app->db->prepare('SELECT title FROM levels_groups ORDER BY `order` ASC, `title` ASC'); - $st->execute(); - return $st->fetchAll(); - } + public function getLevelFromID($id) { + $st = $this->app->db->prepare('SELECT `group`, `name` FROM levels WHERE level_id = :id LIMIT 1'); + $st->bindValue(':id', $id); + $st->execute(); + $res = $st->fetch(); - public function getLevelFromID($id) { - $st = $this->app->db->prepare('SELECT `group`, `name` FROM levels WHERE level_id = :id LIMIT 1'); - $st->bindValue(':id', $id); - $st->execute(); - $res = $st->fetch(); + if ($res) { + return $this->getLevel($res->group, $res->name, true); + } else{ + return false; + } + } - if ($res) - return $this->getLevel($res->group, $res->name, true); - else - return false; - } + public function getLevel($group, $name, $noSkip=false) { + $before_after_sql = 'SELECT `level_id`, `name`, LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri` + FROM levels + INNER JOIN levels_groups + ON levels_groups.title = levels.group + WHERE `group` = :group + ORDER BY level_id'; + + $sql = "SELECT levels.level_id, levels.name, levels_groups.title AS `group`, CONCAT(`group`, ' Level ', levels.name) as `title`, + IF(users_levels.completed > 0, 1, 0) as `completed`, users_levels.completed as `completed_time`, `started`, + IFNULL(users_levels.attempts, 0) as `attempts`, + levels_before.uri AS `level_before_uri`, levels_after.uri AS `level_after_uri` + FROM levels + INNER JOIN levels_groups + ON levels_groups.title = levels.group + LEFT JOIN ({$before_after_sql} DESC) levels_before + ON levels_before.level_id < levels.level_id + LEFT JOIN ({$before_after_sql} ASC) levels_after + ON levels_after.level_id > levels.level_id + LEFT JOIN users_levels + ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id + WHERE levels.name = :level AND levels.group = :group"; + + $st = $this->app->db->prepare($sql); + $st->execute(array(':level'=>$name, ':group'=>$group, ':uid'=>$this->app->user->uid)); + $level = $st->fetch(); - public function getLevel($group, $name, $noSkip=false) { - $before_after_sql = 'SELECT `level_id`, `name`, LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri` - FROM levels - INNER JOIN levels_groups - ON levels_groups.title = levels.group - WHERE `group` = :group - ORDER BY level_id'; - - $sql = "SELECT levels.level_id, levels.name, levels_groups.title AS `group`, CONCAT(`group`, ' Level ', levels.name) as `title`, - IF(users_levels.completed > 0, 1, 0) as `completed`, users_levels.completed as `completed_time`, `started`, - IFNULL(users_levels.attempts, 0) as `attempts`, - levels_before.uri AS `level_before_uri`, levels_after.uri AS `level_after_uri` - FROM levels - INNER JOIN levels_groups - ON levels_groups.title = levels.group - LEFT JOIN ({$before_after_sql} DESC) levels_before - ON levels_before.level_id < levels.level_id - LEFT JOIN ({$before_after_sql} ASC) levels_after - ON levels_after.level_id > levels.level_id - LEFT JOIN users_levels - ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id - WHERE levels.name = :level AND levels.group = :group"; - - $st = $this->app->db->prepare($sql); - $st->execute(array(':level'=>$name, ':group'=>$group, ':uid'=>$this->app->user->uid)); - $level = $st->fetch(); - - if ($level) { + if ($level) { //Check if user has access - if (isset($level->level_before_uri) && strtolower($level->group) == 'main') { - $sql = 'SELECT IF(users_levels.completed > 0, 1, 0) as `completed` FROM levels - LEFT JOIN users_levels - ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id - WHERE `group` = :group AND levels.level_id < :level_id - ORDER BY levels.level_id DESC - LIMIT 1'; - - $st = $this->app->db->prepare($sql); - $st->execute(array(':level_id'=>$level->level_id, ':group'=>$group, ':uid'=>$this->app->user->uid)); - $previous = $st->fetch(); + if (isset($level->level_before_uri) && strtolower($level->group) == 'main') { + $sql = 'SELECT IF(users_levels.completed > 0, 1, 0) as `completed` FROM levels + LEFT JOIN users_levels + ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id + WHERE `group` = :group AND levels.level_id < :level_id + ORDER BY levels.level_id DESC + LIMIT 1'; + + $st = $this->app->db->prepare($sql); + $st->execute(array(':level_id'=>$level->level_id, ':group'=>$group, ':uid'=>$this->app->user->uid)); + $previous = $st->fetch(); - if (!$noSkip && (!$previous || !$previous->completed)) { - header("Location: $level->level_before_uri?skipped"); - die(); + if (!$noSkip && (!$previous || !$previous->completed)) { + header("Location: $level->level_before_uri?skipped"); + die(); + } } - } - $this->levelView($level->level_id); - } else - return false; - - //Build level data - $sql = 'SELECT `key`, `value`, users.username - FROM levels_data - LEFT JOIN users - ON levels_data.value = users.user_id AND levels_data.key = "author" - WHERE level_id = :lid'; - $st = $this->app->db->prepare($sql); - $st->execute(array(':lid'=>$level->level_id)); - $data = $st->fetchAll(); - - $level->data = array(); - - foreach($data as $d) { - //Find all non-value entries - foreach($d as $k=>$v) { - if ($v && $k !== 'key' && $k !== 'value') - $d->value = $v; + $this->levelView($level->level_id); + } else { + return false; } - $level->data[$d->key] = $d->value; - } + // Check cache for level data + $cacheKey = 'level_data_' . $level->level_id; + $cache = $this->app->cache->get('level_data_' . $cacheKey, 5); - if (isset($level->data['code'])) { - $level->data['code'] = json_decode($level->data['code']); - } + if ($cache) { + return json_decode($cache); + } else { + // Build level data + $sql = 'SELECT `key`, `value`, users.username + FROM levels_data + LEFT JOIN users + ON levels_data.value = users.user_id AND levels_data.key = "author" + WHERE level_id = :lid'; - // Set page details - $this->app->page->title = ucwords($level->title); + $st = $this->app->db->prepare($sql); + $st->execute(array(':lid'=>$level->level_id)); + $data = $st->fetchAll(); - // Get stats - $sql = "SELECT COUNT(user_id) AS `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69"; - $st = $this->app->db->prepare($sql); - $st->execute(array(':lid'=>$level->level_id)); - $result = $st->fetch(); - $level->count = $result->completed; + $level->data = array(); - // If level has uptime code, check level status - if ($level->data['uptime']) { - $level->online = $this->app->cache->get('level_uptime_' . $level->data['uptime'], 5); + foreach($data as $d) { + //Find all non-value entries + foreach($d as $k=>$v) { + if ($v && $k !== 'key' && $k !== 'value') + $d->value = $v; + } - if (!$level->online) { - $status = file_get_contents("https://api.uptimerobot.com/getMonitors?apiKey=".$level->data['uptime']."&format=json&noJsonCallback=1"); - $status = json_decode($status); - $level->online = $status->monitors->monitor[0]->status == 2 ? 'online' : 'offline'; + $level->data[$d->key] = $d->value; + } - $this->app->cache->set('level_uptime_' . $level->data['uptime'], $level->online); + if (isset($level->data['code']) && $level->data['code']) { + $level->data['code'] = json_decode($level->data['code']); } - } - - - // // // Get latest - // $sql = "SELECT username, completed FROM users_levels INNER JOIN users ON users.user_id = users_levels.user_id WHERE completed > 0 AND level_id = :lid AND users.user_id != 69 ORDER BY completed DESC LIMIT 1"; - $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` DESC LIMIT 1) `a` ON `a`.user_id = users.user_id;"; - $st = $this->app->db->prepare($sql); - $st->execute(array(':lid'=>$level->level_id)); - $result = $st->fetch(); - $level->last_completed = $result->completed; - $level->last_user = $result->username; - - // // // Get first - // $sql = "SELECT username, completed FROM users_levels INNER JOIN users ON users.user_id = users_levels.user_id WHERE completed > 0 AND level_id = :lid AND users.user_id != 69 ORDER BY completed ASC LIMIT 1"; - $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` ASC LIMIT 1) `a` ON `a`.user_id = users.user_id;"; - $st = $this->app->db->prepare($sql); - $st->execute(array(':lid'=>$level->level_id)); - $result = $st->fetch(); - $level->first_completed = $result->completed; - $level->first_user = $result->username; - - return $level; - } - function levelView($level_id) { - $st = $this->app->db->prepare('INSERT IGNORE INTO users_levels (`user_id`, `level_id`) VALUES (:uid, :lid)'); - $st->execute(array(':lid'=> $level_id, ':uid' => $this->app->user->uid)); - } + // Set page details + $this->app->page->title = ucwords($level->title); + + // Get stats + $sql = "SELECT COUNT(user_id) AS `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69"; + $st = $this->app->db->prepare($sql); + $st->execute(array(':lid'=>$level->level_id)); + $result = $st->fetch(); + $level->count = $result->completed; + + // If level has uptime code, check level status + if (isset($level->data['uptime']) && $level->data['uptime']) { + // Second cache used for high completion levels, limits the online check from happening every time someone completes the level + $level->online = $this->app->cache->get('level_uptime_' . $level->data['uptime'], 5); + + if (!$level->online) { + $status = file_get_contents("https://api.uptimerobot.com/getMonitors?apiKey=".$level->data['uptime']."&format=json&noJsonCallback=1"); + $status = json_decode($status); + $level->online = $status->monitors->monitor[0]->status == 2 ? 'online' : 'offline'; + + $this->app->cache->set('level_uptime_' . $level->data['uptime'], $level->online); + } + } + + // Get last user to complete level + $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` DESC LIMIT 1) `a` ON `a`.user_id = users.user_id;"; + $st = $this->app->db->prepare($sql); + $st->execute(array(':lid'=>$level->level_id)); + $result = $st->fetch(); + $level->last_completed = $result->completed; + $level->last_user = $result->username; + + // Get first user to complete level + $sql = "SELECT username, completed FROM users INNER JOIN (SELECT `user_id`, `completed` FROM users_levels WHERE completed > 0 AND level_id = :lid AND user_id != 69 ORDER BY `completed` ASC LIMIT 1) `a` ON `a`.user_id = users.user_id;"; + $st = $this->app->db->prepare($sql); + $st->execute(array(':lid'=>$level->level_id)); + $result = $st->fetch(); + $level->first_completed = $result->completed; + $level->first_user = $result->username; + // Cache level data + $this->app->cache->set($cacheKey, json_encode($level)); - function check($level) { - if (!isset($level->data['answer'])) - return false; + return $level; + } + } - $answers = json_decode($level->data['answer']); + // Mark that the user has viewed a level + function levelView($level_id) { + $st = $this->app->db->prepare('INSERT IGNORE INTO users_levels (`user_id`, `level_id`) VALUES (:uid, :lid)'); + $st->execute(array(':lid'=> $level_id, ':uid' => $this->app->user->uid)); + } - $attempted = false; - $correct = false; - $incorrect = 0; - foreach($answers AS $answer) { - $valid = false; + // Check if supplied answer is correct + function check($level) { + if (!isset($level->data['answer'])) + return false; - if (strtolower($answer->method) == 'post') { - if (isset($_POST[$answer->name])) { - $attempted = true; - if (isset($answer->type) && $answer->type == 'regex') { - if (preg_match($answer->value, $_POST[$answer->name])) { + $answers = json_decode($level->data['answer']); + + $attempted = false; + $correct = false; + $incorrect = 0; + + foreach($answers AS $answer) { + $valid = false; + + if (strtolower($answer->method) == 'post') { + if (isset($_POST[$answer->name])) { + $attempted = true; + if (isset($answer->type) && $answer->type == 'regex') { + if (preg_match($answer->value, $_POST[$answer->name])) { + $valid = true; + if ($incorrect === 0) { + $correct = true; + } + } else { + $correct = false; + } + } else if ($_POST[$answer->name] === $answer->value) { $valid = true; if ($incorrect === 0) { $correct = true; @@ -236,22 +258,22 @@ function check($level) { } else { $correct = false; } - } else if ($_POST[$answer->name] === $answer->value) { - $valid = true; - if ($incorrect === 0) { - $correct = true; - } } else { $correct = false; } - } else { - $correct = false; - } - } else if (strtolower($answer->method) == 'get') { - if (isset($_GET[$answer->name])) { - $attempted = true; - if ($answer->type && $answer->type == 'regex') { - if (preg_match($answer->value, $_GET[$answer->name])) { + } else if (strtolower($answer->method) == 'get') { + if (isset($_GET[$answer->name])) { + $attempted = true; + if ($answer->type && $answer->type == 'regex') { + if (preg_match($answer->value, $_GET[$answer->name])) { + $valid = true; + if ($incorrect === 0) { + $correct = true; + } + } else { + $correct = false; + } + } else if ($_GET[$answer->name] === $answer->value) { $valid = true; if ($incorrect === 0) { $correct = true; @@ -259,231 +281,229 @@ function check($level) { } else { $correct = false; } - } else if ($_GET[$answer->name] === $answer->value) { - $valid = true; - if ($incorrect === 0) { - $correct = true; - } } else { $correct = false; } - } else { - $correct = false; - } - } + } - if (!$valid) { - $incorrect++; + if (!$valid) { + $incorrect++; + } } - } - if ($attempted) { - $level->attempt = $correct; - $this->attempt($level, $correct); + if ($attempted) { + $level->attempt = $correct; + $this->attempt($level, $correct); - if ($level->level_id == 53) { - $level->errorMsg = (3 - $incorrect) . ' out of 3 answers correct'; + if ($level->level_id == 53) { + $level->errorMsg = (3 - $incorrect) . ' out of 3 answers correct'; + } } - } - - return $correct; - } + return $correct; + } - function attempt($level, $correct=false) { - if (!$level->completed) { - $level->attempts = $level->attempts + 1; - $level->completed_time = 'now'; - if ($correct) { - $level->completed = true; - $level->count++; - $level->last_user = $this->app->user->username; - $level->last_completed = "now"; + // Record attempt to complete level + function attempt($level, $correct=false) { + if (!$level->completed) { + $level->attempts = $level->attempts + 1; + $level->completed_time = 'now'; + if ($correct) { + $level->completed = true; + $level->count++; + $level->last_user = $this->app->user->username; + $level->last_completed = "now"; + //Update user score (temporary) - $this->app->user->score = $this->app->user->score + $level->data['reward']; - $st = $this->app->db->prepare('UPDATE users_levels SET completed = NOW(), attempts=attempts+1 WHERE level_id = :lid AND user_id = :uid'); - $st->execute(array(':lid'=> $level->level_id, ':uid' => $this->app->user->uid)); + $this->app->user->score = $this->app->user->score + $level->data['reward']; + $st = $this->app->db->prepare('UPDATE users_levels SET completed = NOW(), attempts=attempts+1 WHERE level_id = :lid AND user_id = :uid'); + $st->execute(array(':lid'=> $level->level_id, ':uid' => $this->app->user->uid)); // Setup GA event - $this->app->ssga->set_event('level', 'completed', $level->level_id, $this->app->user->uid); - $this->app->ssga->send(); + $this->app->ssga->set_event('level', 'completed', $level->level_id, $this->app->user->uid); + $this->app->ssga->send(); // Send feed thingy - $this->app->feed->call($this->app->user->username, 'level', ucwords($level->group.' '.$level->name), '/levels/'.strtolower($level->group).'/'.strtolower($level->name)); - + $this->app->feed->call($this->app->user->username, 'level', ucwords($level->group.' '.$level->name), '/levels/'.strtolower($level->group).'/'.strtolower($level->name)); + // Update WeChall - file_get_contents("http://wechall.net/remoteupdate.php?sitename=ht&username=".$this->app->user->username); - } else { + file_get_contents("http://wechall.net/remoteupdate.php?sitename=ht&username=".$this->app->user->username); + } else { // Record attempt - $st = $this->app->db->prepare('UPDATE users_levels SET attempts=attempts+1 WHERE level_id = :lid AND user_id = :uid'); - $st->execute(array(':lid'=> $level->level_id, ':uid' => $this->app->user->uid)); + $st = $this->app->db->prepare('UPDATE users_levels SET attempts=attempts+1 WHERE level_id = :lid AND user_id = :uid'); + $st->execute(array(':lid'=> $level->level_id, ':uid' => $this->app->user->uid)); + } } } - } - function user_data($level_id, $data=null) { - if ($data !== null) { - $st = $this->app->db->prepare('INSERT INTO users_levels_data (`user_id`, `level_id`, `data`) VALUES (:uid, :lid, :data) ON DUPLICATE KEY UPDATE `data` = :data, `time` = now()'); - return $st->execute(array(':lid' => $level_id, ':uid' => $this->app->user->uid, ':data' => $data)); - } else { - $st = $this->app->db->prepare('SELECT * FROM users_levels_data WHERE `user_id` = :uid AND `level_id` = :lid'); - $st->execute(array(':lid' => $level_id, ':uid' => $this->app->user->uid)); - return $st->fetch(); + function user_data($level_id, $data=null) { + if ($data !== null) { + $st = $this->app->db->prepare('INSERT INTO users_levels_data (`user_id`, `level_id`, `data`) VALUES (:uid, :lid, :data) ON DUPLICATE KEY UPDATE `data` = :data, `time` = now()'); + return $st->execute(array(':lid' => $level_id, ':uid' => $this->app->user->uid, ':data' => $data)); + } else { + $st = $this->app->db->prepare('SELECT * FROM users_levels_data WHERE `user_id` = :uid AND `level_id` = :lid'); + $st->execute(array(':lid' => $level_id, ':uid' => $this->app->user->uid)); + return $st->fetch(); + } } - } - // ADMIN FUNCTIONS - function addCategory($title) { - if (!$this->app->user->admin_site_priv) - return false; + function addCategory($title) { + if (!$this->app->user->admin_site_priv) { + return false; + } - $st = $this->app->db->prepare('INSERT INTO levels_groups (`title`) VALUES (:title)'); - return $st->execute(array(':title'=> $title)); - } + $st = $this->app->db->prepare('INSERT INTO levels_groups (`title`) VALUES (:title)'); + return $st->execute(array(':title'=> $title)); + } - function editLevel($id, $new = false) { - if (!$this->app->user->admin_site_priv) - return false; + function editLevel($id, $new = false) { + if (!$this->app->user->admin_site_priv) { + return false; + } - $changes = array(); + $changes = array(); - if (!$new) { - if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) - return false; + if (!$new) { + if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) + return false; - if (isset($_POST['category']) && strlen($_POST['category'])) { - $group = $_POST['category']; + if (isset($_POST['category']) && strlen($_POST['category'])) { + $group = $_POST['category']; - $st = $this->app->db->prepare('UPDATE IGNORE levels SET `group` = :g WHERE level_id = :id LIMIT 1'); - $res = $st->execute(array(':id'=> $id, ':g'=>$group)); + $st = $this->app->db->prepare('UPDATE IGNORE levels SET `group` = :g WHERE level_id = :id LIMIT 1'); + $res = $st->execute(array(':id'=> $id, ':g'=>$group)); + } } - } - if (isset($_POST['reward']) && is_numeric($_POST['reward'])) { - $changes['reward'] = $_POST['reward']; - } - if (isset($_POST['uptime']) && strlen($_POST['uptime'])) { - $changes['uptime'] = $_POST['uptime']; - } - if (isset($_POST['description']) && strlen($_POST['description'])) { - $changes['description'] = $_POST['description']; - } - if (isset($_POST['hint']) && strlen($_POST['hint'])) { - $changes['hint'] = $_POST['hint']; - } - if (isset($_POST['solution']) && strlen($_POST['solution'])) { - $changes['solution'] = $_POST['solution']; - } + if (isset($_POST['reward']) && is_numeric($_POST['reward'])) { + $changes['reward'] = $_POST['reward']; + } + if (isset($_POST['uptime']) && strlen($_POST['uptime'])) { + $changes['uptime'] = $_POST['uptime']; + } + if (isset($_POST['description']) && strlen($_POST['description'])) { + $changes['description'] = $_POST['description']; + } + if (isset($_POST['hint']) && strlen($_POST['hint'])) { + $changes['hint'] = $_POST['hint']; + } + if (isset($_POST['solution']) && strlen($_POST['solution'])) { + $changes['solution'] = $_POST['solution']; + } - foreach($changes AS $change=>$value) { - $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); - $res = $st->execute(array(':id'=> $id, ':k'=>$change, ':v'=>$value)); - if (!$res) - return false; - } + foreach($changes AS $change=>$value) { + $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); + $res = $st->execute(array(':id'=> $id, ':k'=>$change, ':v'=>$value)); + if (!$res) + return false; + } - return true; - } + return true; + } - function newLevel() { - if (!$this->app->user->admin_site_priv) - return false; + function newLevel() { + if (!$this->app->user->admin_site_priv) + return false; - if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) - return false; + if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) + return false; // Create level - try { - $st = $this->app->db->prepare('INSERT INTO levels (`name`, `group`) VALUES (:name, :group)'); - $status = $st->execute(array(':name'=> $_POST['name'], ':group' => $_POST['category'])); - } catch(PDOExecption $e) { - return false; - } + try { + $st = $this->app->db->prepare('INSERT INTO levels (`name`, `group`) VALUES (:name, :group)'); + $status = $st->execute(array(':name'=> $_POST['name'], ':group' => $_POST['category'])); + } catch(PDOExecption $e) { + return false; + } - if (!$status) - return false; + if (!$status) + return false; - $id = $this->app->db->lastInsertId(); + $id = $this->app->db->lastInsertId(); // Insert data - $this->editLevel($id, true); + $this->editLevel($id, true); // Return level id - return $id; - } + return $id; + } - function editLevelForm($id) { - if (!$this->app->user->admin_site_priv) - return false; + function editLevelForm($id) { + if (!$this->app->user->admin_site_priv) { + return false; + } - if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) - return false; + if (!$this->app->checkCSRFKey("level-editor", $_POST['token'])) { + return false; + } - $form = null; + $form = null; // Is it JSON? - if (isset($_POST['form_method'])) { - $form = array(); - $form['method'] = $_POST['form_method']; - $form['fields'] = array(); - - $f_types = $_POST['form_type']; - $f_names = $_POST['form_name']; - $f_labels = $_POST['form_label']; - - foreach($f_types as $key => $value) { - echo $value . "
"; - if ($f_names[$key] && $f_labels[$key]) { - $field = new stdClass; - $field->type = $value; - $field->name = $f_names[$key]; - $field->label = $f_labels[$key]; - - array_push($form['fields'],$field); + if (isset($_POST['form_method'])) { + $form = array(); + $form['method'] = $_POST['form_method']; + $form['fields'] = array(); + + $f_types = $_POST['form_type']; + $f_names = $_POST['form_name']; + $f_labels = $_POST['form_label']; + + foreach($f_types as $key => $value) { + echo $value . "
"; + if ($f_names[$key] && $f_labels[$key]) { + $field = new stdClass; + $field->type = $value; + $field->name = $f_names[$key]; + $field->label = $f_labels[$key]; + + array_push($form['fields'],$field); + } } - } - if (count($form['fields'])) - $form = json_encode($form); - } else { - if (isset($_POST['form'])) - $form = $_POST['form']; - } + if (count($form['fields'])) { + $form = json_encode($form); + } + } else { + if (isset($_POST['form'])) { + $form = $_POST['form']; + } + } - if ($form) { - $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); - $status = $st->execute(array(':id'=> $id, ':k' => 'form', ':v' => $form)); - } + if ($form) { + $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); + $status = $st->execute(array(':id'=> $id, ':k' => 'form', ':v' => $form)); + } // Do answers - $answers = array(); - - $a_methods = $_POST['answer_method']; - $a_names = $_POST['answer_name']; - $a_values = $_POST['answer_value']; - - foreach($a_methods as $key => $value) { - if ($a_names[$key] && $a_values[$key]) { - $answer = new stdClass; - $answer->method = $value; - $answer->name = $a_names[$key]; - $answer->value = $a_values[$key]; - - array_push($answers, $answer); + $answers = array(); + + $a_methods = $_POST['answer_method']; + $a_names = $_POST['answer_name']; + $a_values = $_POST['answer_value']; + + foreach($a_methods as $key => $value) { + if ($a_names[$key] && $a_values[$key]) { + $answer = new stdClass; + $answer->method = $value; + $answer->name = $a_names[$key]; + $answer->value = $a_values[$key]; + + array_push($answers, $answer); + } } - } - if (count($answers)) { - $answers = json_encode($answers); - if ($answers) { - $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); - $status = $st->execute(array(':id'=> $id, ':k' => 'answer', ':v' => $answers)); + if (count($answers)) { + $answers = json_encode($answers); + if ($answers) { + $st = $this->app->db->prepare('INSERT INTO levels_data (`level_id`, `key`, `value`) VALUES (:id, :k, :v) ON DUPLICATE KEY UPDATE `value` = :v'); + $status = $st->execute(array(':id'=> $id, ':k' => 'answer', ':v' => $answers)); + } } - } - return true; + return true; + } } -} ?> diff --git a/files/elements/levels/header.php b/files/elements/levels/header.php index 082d534..9cead84 100644 --- a/files/elements/levels/header.php +++ b/files/elements/levels/header.php @@ -25,7 +25,7 @@ '>completed?'Completed':'Incomplete';?>
-online): ?> +online) && $level->online): ?>
'>Level online;?>
From 6dba153f824d31ed5ff53b18c55f971e7f0d7496 Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Tue, 23 Jun 2015 12:08:43 +0100 Subject: [PATCH 066/112] Fixed warnings on levels --- files/class.levels.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/class.levels.php b/files/class.levels.php index 17d09d9..d48c6f1 100644 --- a/files/class.levels.php +++ b/files/class.levels.php @@ -264,7 +264,7 @@ function check($level) { } else if (strtolower($answer->method) == 'get') { if (isset($_GET[$answer->name])) { $attempted = true; - if ($answer->type && $answer->type == 'regex') { + if (isset($answer->type) && $answer->type == 'regex') { if (preg_match($answer->value, $_GET[$answer->name])) { $valid = true; if ($incorrect === 0) { From 926412f1b7a8040b68f61e21a0bffe27f03c69a3 Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Tue, 23 Jun 2015 12:10:28 +0100 Subject: [PATCH 067/112] Fixed warning on forum pages --- html/forum/index.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/html/forum/index.php b/html/forum/index.php index aa2cc7d..b4dc837 100644 --- a/html/forum/index.php +++ b/html/forum/index.php @@ -19,16 +19,18 @@ if (isset($_GET['slug'])) { // Get id from slug preg_match('/\/([0-9]+)[a-z0-9\s-]+$/s', $_GET['slug'], $matches); - $id = $matches[1]; - - // Section or thread? - $thread = $forum->isThread($id); - if ($thread) { - if (isset($_GET['edit'])) - include('edit.php'); - else - include('view.php'); - die(); + if ($matches) { + $id = $matches[1]; + + // Section or thread? + $thread = $forum->isThread($id); + if ($thread) { + if (isset($_GET['edit'])) + include('edit.php'); + else + include('view.php'); + die(); + } } $section = $forum->getSection($_GET['slug']); From 18c96dc81136b24410ceefc4814147f5d5d584ed Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Tue, 23 Jun 2015 14:35:29 +0100 Subject: [PATCH 068/112] fix(cache): cache wasn't loading the file contents of cache files --- files/class.cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/class.cache.php b/files/class.cache.php index 224e086..9256e35 100644 --- a/files/class.cache.php +++ b/files/class.cache.php @@ -11,7 +11,7 @@ function get($file, $freshness=null) { $fh = @fopen($file, "rb"); if (!$fh) return false; - $data = fread($fh, filesize($file)); + $data = stream_get_contents($fh, filesize($file)); fclose($fh); } From e26eb405649b7fcde62569906b1db357bdb040be Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Tue, 23 Jun 2015 17:24:16 +0100 Subject: [PATCH 069/112] fix(levels): caching wasn't working correctly --- files/class.levels.php | 4 ++-- html/levels/level.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/class.levels.php b/files/class.levels.php index d48c6f1..1b835ab 100644 --- a/files/class.levels.php +++ b/files/class.levels.php @@ -142,7 +142,7 @@ public function getLevel($group, $name, $noSkip=false) { $cache = $this->app->cache->get('level_data_' . $cacheKey, 5); if ($cache) { - return json_decode($cache); + return unserialize($cache); } else { // Build level data $sql = 'SELECT `key`, `value`, users.username @@ -212,7 +212,7 @@ public function getLevel($group, $name, $noSkip=false) { $level->first_user = $result->username; // Cache level data - $this->app->cache->set($cacheKey, json_encode($level)); + $this->app->cache->set($cacheKey, serialize($level)); return $level; } diff --git a/html/levels/level.php b/html/levels/level.php index 97a4fe8..a7e2e12 100644 --- a/html/levels/level.php +++ b/html/levels/level.php @@ -29,7 +29,7 @@ } //Check if user completed level - if (isset($currentLevel->data['form']) && $page = realpath($app->config('path') . '/files/elements/levels/'.basename($currentLevel->data['form']).'_logic.php')) { + if (isset($currentLevel->data['form']) && $page = realpath($app->config['path'] . '/files/elements/levels/'.basename($currentLevel->data['form']).'_logic.php')) { include($page); } else { $app->levels->check($currentLevel); From 070ad4f95b7e3d616768bfd3a1c83b603f13518d Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Tue, 23 Jun 2015 17:25:48 +0100 Subject: [PATCH 070/112] fix(levels): cache wasn't working --- files/class.levels.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/class.levels.php b/files/class.levels.php index 1b835ab..7d4dbd4 100644 --- a/files/class.levels.php +++ b/files/class.levels.php @@ -139,7 +139,7 @@ public function getLevel($group, $name, $noSkip=false) { // Check cache for level data $cacheKey = 'level_data_' . $level->level_id; - $cache = $this->app->cache->get('level_data_' . $cacheKey, 5); + $cache = $this->app->cache->get($cacheKey, 5); if ($cache) { return unserialize($cache); From bae7a3d8ac00e5cc449f11de5fccab3fca459aef Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Fri, 26 Jun 2015 15:41:58 +0100 Subject: [PATCH 071/112] fix(levels): level caching --- files/class.levels.php | 110 ++++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 35 deletions(-) diff --git a/files/class.levels.php b/files/class.levels.php index 7d4dbd4..a5688e4 100644 --- a/files/class.levels.php +++ b/files/class.levels.php @@ -86,33 +86,28 @@ public function getLevelFromID($id) { } public function getLevel($group, $name, $noSkip=false) { - $before_after_sql = 'SELECT `level_id`, `name`, LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri` - FROM levels - INNER JOIN levels_groups - ON levels_groups.title = levels.group - WHERE `group` = :group - ORDER BY level_id'; - - $sql = "SELECT levels.level_id, levels.name, levels_groups.title AS `group`, CONCAT(`group`, ' Level ', levels.name) as `title`, - IF(users_levels.completed > 0, 1, 0) as `completed`, users_levels.completed as `completed_time`, `started`, - IFNULL(users_levels.attempts, 0) as `attempts`, - levels_before.uri AS `level_before_uri`, levels_after.uri AS `level_after_uri` - FROM levels - INNER JOIN levels_groups - ON levels_groups.title = levels.group - LEFT JOIN ({$before_after_sql} DESC) levels_before - ON levels_before.level_id < levels.level_id - LEFT JOIN ({$before_after_sql} ASC) levels_after - ON levels_after.level_id > levels.level_id - LEFT JOIN users_levels - ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id - WHERE levels.name = :level AND levels.group = :group"; + // Check cache for level data + $cacheKey = 'level_data_' . $group . '_' . $name; + $cache = $this->app->cache->get($cacheKey, 5); - $st = $this->app->db->prepare($sql); - $st->execute(array(':level'=>$name, ':group'=>$group, ':uid'=>$this->app->user->uid)); - $level = $st->fetch(); + if ($cache): + $level = unserialize($cache); + + $sql = "SELECT + IF(users_levels.completed > 0, 1, 0) as `completed`, + users_levels.completed as `completed_time`, + users_levels.started, + IFNULL(users_levels.attempts, 0) as `attempts` + FROM users_levels + WHERE level_id = :levelid AND user_id = :uid"; + + $st = $this->app->db->prepare($sql); + $st->execute(array(':levelid' => $level->level_id, ':uid' => $this->app->user->uid)); + $tmpLevel = $st->fetch(); + + // Merge users stats into cache response + $level = (object) array_merge((array) $level, (array) $tmpLevel); - if ($level) { //Check if user has access if (isset($level->level_before_uri) && strtolower($level->group) == 'main') { $sql = 'SELECT IF(users_levels.completed > 0, 1, 0) as `completed` FROM levels @@ -133,17 +128,61 @@ public function getLevel($group, $name, $noSkip=false) { } $this->levelView($level->level_id); - } else { - return false; - } - // Check cache for level data - $cacheKey = 'level_data_' . $level->level_id; - $cache = $this->app->cache->get($cacheKey, 5); + return $level; + + else: + $before_after_sql = 'SELECT `level_id`, `name`, LOWER(CONCAT("/levels/", CONCAT_WS("/", levels_groups.title, levels.name))) as `uri` + FROM levels + INNER JOIN levels_groups + ON levels_groups.title = levels.group + WHERE `group` = :group + ORDER BY level_id'; + + $sql = "SELECT levels.level_id, levels.name, levels_groups.title AS `group`, CONCAT(`group`, ' Level ', levels.name) as `title`, + IF(users_levels.completed > 0, 1, 0) as `completed`, users_levels.completed as `completed_time`, `started`, + IFNULL(users_levels.attempts, 0) as `attempts`, + levels_before.uri AS `level_before_uri`, levels_after.uri AS `level_after_uri` + FROM levels + INNER JOIN levels_groups + ON levels_groups.title = levels.group + LEFT JOIN ({$before_after_sql} DESC) levels_before + ON levels_before.level_id < levels.level_id + LEFT JOIN ({$before_after_sql} ASC) levels_after + ON levels_after.level_id > levels.level_id + LEFT JOIN users_levels + ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id + WHERE levels.name = :level AND levels.group = :group"; + + $st = $this->app->db->prepare($sql); + $st->execute(array(':level'=>$name, ':group'=>$group, ':uid'=>$this->app->user->uid)); + $level = $st->fetch(); + + if ($level) { + //Check if user has access + if (isset($level->level_before_uri) && strtolower($level->group) == 'main') { + $sql = 'SELECT IF(users_levels.completed > 0, 1, 0) as `completed` FROM levels + LEFT JOIN users_levels + ON users_levels.user_id = :uid AND users_levels.level_id = levels.level_id + WHERE `group` = :group AND levels.level_id < :level_id + ORDER BY levels.level_id DESC + LIMIT 1'; + + $st = $this->app->db->prepare($sql); + $st->execute(array(':level_id'=>$level->level_id, ':group'=>$group, ':uid'=>$this->app->user->uid)); + $previous = $st->fetch(); + + if (!$noSkip && (!$previous || !$previous->completed)) { + header("Location: $level->level_before_uri?skipped"); + die(); + } + } + + $this->levelView($level->level_id); + } else { + return false; + } - if ($cache) { - return unserialize($cache); - } else { // Build level data $sql = 'SELECT `key`, `value`, users.username FROM levels_data @@ -215,7 +254,8 @@ public function getLevel($group, $name, $noSkip=false) { $this->app->cache->set($cacheKey, serialize($level)); return $level; - } + + endif; // End cache check } // Mark that the user has viewed a level From b44a010f421b6455d6252f86a72767f093b2af13 Mon Sep 17 00:00:00 2001 From: 0x6C77 Date: Mon, 29 Jun 2015 10:50:56 +0100 Subject: [PATCH 072/112] fix(settings): fixing warning --- files/elements/wysiwyg.php | 2 +- html/settings/account.php | 2 +- html/settings/index.php | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/files/elements/wysiwyg.php b/files/elements/wysiwyg.php index 40d1fa2..ecc729c 100644 --- a/files/elements/wysiwyg.php +++ b/files/elements/wysiwyg.php @@ -1,5 +1,5 @@ custom_js) { + if ($minifier && is_object($minifier) && $minifier->custom_js) { array_push($minifier->custom_js, 'wysiwyg.js'); } ?> diff --git a/html/settings/account.php b/html/settings/account.php index bc71396..83e1d3d 100644 --- a/html/settings/account.php +++ b/html/settings/account.php @@ -7,7 +7,7 @@ if (isset($_GET['password'])) { $changed = $app->user->changePassword($_POST['newpassword1'], $_POST['newpassword1']); - } else if (isset($_GET['delete'])) { + } else if (isset($_GET['delete']) && isset($_POST['delete']) && isset($_POST['token'])) { $status = $app->user->delete($_POST['delete'], $_POST['token']); if ($status === true) { diff --git a/html/settings/index.php b/html/settings/index.php index 9ffa9ba..8165a41 100644 --- a/html/settings/index.php +++ b/html/settings/index.php @@ -16,6 +16,7 @@ } // Check for update + $updated = false; if (isset($_GET['save'])) { if (isset($_POST['name']) && isset($_POST['email']) && isset($_POST['gender']) && isset($_POST['dob'])) { $changes = array_map('trim', $_POST); @@ -48,19 +49,19 @@ } } + $templateData = array('profile' => $profile); + if (isset($_GET['done'])) { - $goodMsg = 'Profile updated'; + $templateData['goodMsg'] = 'Profile updated'; } else if ($updated) { - $errorMsg = $updated; + $templateData['errorMsg'] = $updated; } require_once('header.php'); $tab = 'profile'; include('elements/tabs_settings.php'); - - echo $app->twig->render('settings_profile.html', array('profile' => $profile, 'goodMsg' => $goodMsg, 'errorMsg' => $errorMsg)); - + echo $app->twig->render('settings_profile.html', $templateData); require_once('footer.php'); ?> \ No newline at end of file From 9dd6b54fcc0befcd7ddf4fc6d50d9737a163c2b6 Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Sun, 19 Jul 2015 20:34:01 +0000 Subject: [PATCH 073/112] Added fields to SQL to track details on moderator flags --- sql/schema.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sql/schema.sql b/sql/schema.sql index c932147..432c12d 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -247,7 +247,7 @@ CREATE TABLE IF NOT EXISTS levels ( CREATE TABLE IF NOT EXISTS levels_data ( `level_id` tinyint(3) UNSIGNED NOT NULL AUTO_INCREMENT, - `key` enum('author', 'reward', 'form', 'answer', 'articles', 'hint', 'description', 'solution', 'code') NOT NULL, + `key` enum('author', 'reward', 'form', 'answer', 'articles', 'hint', 'description', 'solution', 'code', 'uptime') NOT NULL, `value` text NOT NULL, PRIMARY KEY (`level_id`, `key`), FOREIGN KEY (`level_id`) REFERENCES levels (`level_id`) @@ -488,6 +488,8 @@ CREATE TABLE IF NOT EXISTS mod_reports ( `about` int(7) NOT NULL, `subject` varchar(64), `body` text, + `visible` tinyint(1) DEFAULT 1, + `time` timestamp DEFAULT CURRENT_TIMESTAMP PRIMARY KEY (`report_id`), FOREIGN KEY (`user_id`) REFERENCES users (`user_id`) ) ENGINE=InnoDB; From e2be11b30cdfca3af868395f9deab08a61919272 Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Sun, 19 Jul 2015 20:40:43 +0000 Subject: [PATCH 074/112] feat(admin): Improved admin access to user access controls --- files/class.admin.php | 109 ++++++++++++-- files/class.api.php | 159 +++++++++++++++++--- files/class.profile.php | 21 ++- files/templates/admin_article_comments.html | 23 +++ files/templates/admin_logs.html | 21 +++ files/templates/admin_user_manager.html | 36 ++--- files/templates/profile.html | 16 +- html/files/css/main.scss | 3 +- html/files/js/admin.js | 83 +++++++++- html/files/js/admin_forum.js | 9 +- 10 files changed, 400 insertions(+), 80 deletions(-) create mode 100644 files/templates/admin_article_comments.html create mode 100644 files/templates/admin_logs.html diff --git a/files/class.admin.php b/files/class.admin.php index 2710cc1..10378c9 100644 --- a/files/class.admin.php +++ b/files/class.admin.php @@ -18,6 +18,7 @@ public function __construct($app) { $this->app = $app; } + /******* TICKETS *******/ public function getUnreadTickets() { $sql = "SELECT `mod_contact`.*, COUNT(a.message_id) AS `replies` FROM `mod_contact` LEFT JOIN `mod_contact` a @@ -32,20 +33,17 @@ public function getUnreadTickets() { return $count; } - public function getLatestForumFlags($limit = true) { - $sql = "SELECT MAX(forum_posts_flags.time) AS `latest`, COUNT(forum_posts_flags.post_id) AS `flags`, forum_posts_flags.reason, users.username, forum_threads.thread_id, forum_threads.slug, forum_threads.title, forum_posts.post_id, forum_posts.body - FROM forum_posts_flags - INNER JOIN forum_posts - ON forum_posts_flags.post_id = forum_posts.post_id - INNER JOIN forum_threads - ON forum_posts.thread_id = forum_threads.thread_id - INNER JOIN users - ON users.user_id = forum_posts.author - WHERE forum_posts.deleted = 0 AND forum_threads.deleted = 0 AND forum_posts_flags.response = 0 - GROUP BY forum_posts_flags.post_id - ORDER BY `flags` DESC, `latest` DESC"; - if ($limit) $sql .= " LIMIT 5"; + + /******* LOGS *******/ + public function getModeratorLogs($limit = true) { + $sql = "SELECT `report_id`, `type`, `subject`, username + FROM mod_reports + INNER JOIN `users` + ON `users`.user_id = `mod_reports`.user_id + ORDER BY `report_id` DESC"; + if ($limit) $sql .= " LIMIT 5"; + $st = $this->app->db->prepare($sql); $st->execute(); $result = $st->fetchAll(); @@ -53,6 +51,9 @@ public function getLatestForumFlags($limit = true) { return $result; } + + + /******* ARTICLES *******/ public function getLatestArticleSubmissions($limit = true) { $sql = "SELECT articles_draft.article_id, articles_draft.title, articles_draft.time, articles_categories.title AS `category`, users.username FROM articles_draft @@ -71,10 +72,48 @@ public function getLatestArticleSubmissions($limit = true) { return $result; } + public function getLatestArticleComments() { + $sql = "SELECT users.username, articles.title, articles_comments.time, articles_comments.comment + FROM articles_comments + INNER JOIN users + ON users.user_id = articles_comments.user_id + INNER JOIN articles + ON articles.article_id = articles_comments.article_id + WHERE articles_comments.deleted IS NULL + ORDER BY `time` DESC + LIMIT 5"; + + $st = $this->app->db->prepare($sql); + $st->execute(); + $result = $st->fetchAll(); + + return $result; + } - // Forum + /******* FORUM *******/ + public function getLatestForumFlags($limit = true) { + $sql = "SELECT MAX(forum_posts_flags.time) AS `latest`, COUNT(forum_posts_flags.post_id) AS `flags`, forum_posts_flags.reason, users.username, forum_threads.thread_id, forum_threads.slug, forum_threads.title, forum_posts.post_id, forum_posts.body + FROM forum_posts_flags + INNER JOIN forum_posts + ON forum_posts_flags.post_id = forum_posts.post_id + INNER JOIN forum_threads + ON forum_posts.thread_id = forum_threads.thread_id + INNER JOIN users + ON users.user_id = forum_posts.author + WHERE forum_posts.deleted = 0 AND forum_threads.deleted = 0 AND forum_posts_flags.response = 0 + GROUP BY forum_posts_flags.post_id + ORDER BY `flags` DESC, `latest` DESC"; + if ($limit) $sql .= " LIMIT 5"; + + $st = $this->app->db->prepare($sql); + $st->execute(); + $result = $st->fetchAll(); + + return $result; + } + public function removeForumThread($thread_id, $reason, $extra) { // Delete post $deleted = $this->app->forum->deleteThread($thread_id); @@ -147,5 +186,47 @@ public function removeForumPost($post_id, $reason, $extra) { return true; } + + + + /******* USER MANAGEMENT *******/ + public function getModerators() { + $query = "SELECT username, users.user_id AS `uid`, users_priv.* + FROM users_priv + INNER JOIN users + ON users.user_id = users_priv.user_id + WHERE users_priv.site_priv > 1 OR + users_priv.pm_priv > 1 OR + users_priv.forum_priv > 1 OR + users_priv.pub_priv > 1"; + + $st = $this->app->db->prepare($query); + $st->execute(); + $result = $st->fetchAll(); + + return $result; + } + + public function setModeratorPriv($user_id, $priv, $priv_value) { + // Check user has privilages + if ($this->app->user->site_priv < 2 || $user_id == '69') { + echo "No"; + return; + } + + if ($priv != 'site' && $priv != 'pm' && $priv != 'forum' && $priv != 'pub') { + return; + } + + $priv = $priv.'_priv'; + + $this->app->db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + + $st = $this->app->db->prepare("INSERT INTO users_priv (`user_id`, ".$priv.") VALUES (:uid, :priv_value) ON DUPLICATE KEY UPDATE ".$priv."=:priv_value"); + $status = $st->execute(array(':uid'=>$user_id, ':priv_value'=>$priv_value)); + + print_r($status); + } + } ?> \ No newline at end of file diff --git a/files/class.api.php b/files/class.api.php index a6a6f41..dee2691 100644 --- a/files/class.api.php +++ b/files/class.api.php @@ -17,7 +17,26 @@ public function __construct($app, $key) { * When no API key is present, use users privileges */ if (!isset($this->privileges) && $this->app->user->loggedIn) { - $this->privileges = array('user.profile'); + // Check if user is banned from the site + if ($this->app->user->site_priv < 1) { + $this->respond(401); + } + + // Check the CSRF is being used + if (!isset($_GET['ajax_csrf_token']) || $_GET['ajax_csrf_token'] != $this->app->user->csrf_basic) { + $this->respond(400); + } + + $this->privileges = array(); + + array_push($this->privileges, 'user.profile'); + + if ($this->app->user->site_priv > 1) { + array_push($this->privileges, 'user.admin.*'); + } + if ($this->app->user->forum_priv > 1 || $this->app->user->site_priv > 1) { + array_push($this->privileges, 'forum.admin.*'); + } } if (!isset($this->privileges)) { @@ -33,13 +52,25 @@ public function handleRequest($method, $data) { // Check privileges $this->hasPrivilege($method); - switch ($method) { - case 'irc.log': $this->logIrc(); break; - case 'user.login': $this->user('login'); break; - case 'user.profile': $this->user('profile'); break; + $subject = explode('.', $method); + $method = substr($method, strlen($subject[0])+1); + + switch ($subject[0]) { + case 'irc': $this->logIrc(); break; + case 'user': $this->user($method); break; + default: $this->respond(400); } - $this->respond(400); + // switch ($method) { + // case 'irc.log': $this->logIrc(); break; + // case 'user.login': $this->user('login'); break; + // case 'user.login.gauth': $this->user('login.gauth'); break; + // case 'user.register': $this->user('register'); break; + // case 'user.profile': $this->user('profile'); break; + // case 'user.admin.priv': $this->user('admin.priv'); break; + // case 'user.admin.priv': $this->user('admin.priv'); break; + // default: $this->respond(400); + // } } @@ -54,15 +85,21 @@ private function user($request) { $response = new stdClass(); switch ($request) { - case 'profile': $response->profile = $this->userProfile(); break; case 'login': $response = $this->userLogin(); break; + case 'login.gauth': $response = $this->userLoginGAuth(); break; + case 'register': $response = $this->userRegister(); break; + case 'profile': $response->profile = $this->userProfile(); break; + + case 'admin.priv': $this->userAdminPriv(); break; + case 'admin.medal': $response->status = $this->userAdminMedal(); break; + default: $this->respond(400); } $this->respond(200, $response); } private function userProfile() { - $profile = new profile($_GET['user'], true); ; + $profile = new profile($_GET['user'], true); unset($profile->email); @@ -70,7 +107,91 @@ private function userProfile() { } private function userLogin() { - // No idea yet + $response = new stdClass(); + + $response->valid = $this->app->user->login($_POST['username'], $_POST['password']); + + if ($response->valid) { + $this->app->user->get_details(); + $response->uid = $this->app->user->uid; + $response->username = $this->app->user->username; + } else { + if ($this->app->user->g_auth) { + $response->error = "Google Authentication code required"; + $response->g_auth = $this->app->user->g_auth; + } else { + $response->error = $this->app->user->login_error; + } + } + + + return $response; + } + + private function userLoginGAuth() { + $response = new stdClass(); + + if(is_numeric($_POST['code']) && is_numeric($_POST['gauth'])) { + $response->valid = $this->app->user->googleAuth($_POST['code'], $_POST['gauth']); + } else { + $response->valid = false; + } + + if ($response->valid) { + $this->app->user->get_details(); + $response->uid = $this->app->user->uid; + $response->username = $this->app->user->username; + } else { + $response->error = $this->app->user->login_error; + } + + return $response; + } + + private function userRegister() { + $username = $_POST['username']; + $password = $_POST['password']; + $email = $_POST['email']; + + $response = new stdClass(); + + $registration = $this->app->user->register($username, $password, $email); + + if (is_numeric($registration)) { + $response->valid = true; + + $this->app->user->get_details(); + $response->uid = $this->app->user->uid; + $response->username = $this->app->user->username; + } else { + $response->valid = false; + $response->error = $registration; + } + + return $response; + } + + private function userAdminPriv() { + $user_id = $_POST['uid']; + $priv = $_POST['priv']; + $priv_value = $_POST['priv_value']; + $this->app->admin->setModeratorPriv($user_id, $priv, $priv_value); + } + + private function userAdminMedal() { + $user_id = $_POST['uid']; + $medal = $_POST['medal']; + $medal_value = $_POST['medal_value']; + + if ($medal == 'contributor' || $medal == 'helper') { + if ($medal_value == 1) { + return $this->app->user->awardMedal($medal, 4, $user_id); + } else { + return $this->app->user->removeMedal($medal, 4, $user_id); + } + } + + return "error"; } @@ -78,7 +199,7 @@ private function userLogin() { // IRC //----------------------------------------------------- private function logIrc() { - if (!isset($_POST['nick']) || !isset($_POST['chan']) || !isset($_POST['msg'])) + if (!isset($_POST['nick']) || !isset($_POST['chan']) || !isset($_POST['msg'])) throw new Exception('Missing data fields'); $_POST['msg'] = preg_replace('/\x01/', '', $_POST['msg']); @@ -137,11 +258,6 @@ private function respond($status, $data=null) { } private function checkKey($key) { - // if ($this->app->config('api') == $key) { - // $this->privileges = array('irc.*', 'user.profile'); - // return true; - // } - $st = $this->app->db->prepare('SELECT privileges FROM api_clients WHERE `key` = :key LIMIT 1'); $st->execute(array(':key' => $key)); $result = $st->fetch(); @@ -156,14 +272,14 @@ private function checkKey($key) { private function hasPrivilege($privilege) { // get subject - $subject = strtok($privilege, '.'); + $subject = explode('.', $privilege); - if ($subject = 'public') { - return true; + if ($subject[1] == 'admin') { + $globalPrivilege = $subject[0] . '.admin.*'; + } else { + $globalPrivilege = $subject[0] . '.*'; } - $globalPrivilege = $subject . '.*'; - if (!in_array($privilege, $this->privileges) && !in_array($globalPrivilege, $this->privileges)) { $this->respond(403); @@ -172,3 +288,6 @@ private function hasPrivilege($privilege) { } ?> + + + diff --git a/files/class.profile.php b/files/class.profile.php index 0b03961..0f5d576 100644 --- a/files/class.profile.php +++ b/files/class.profile.php @@ -58,7 +58,7 @@ public function __construct($username, $public=false) { } unset($this->gravatar); - if (!$this->app->user->admin_site_priv) { + if (!$this->app->admin) { unset($this->site_priv); unset($this->pm_priv); unset($this->forum_priv); @@ -73,6 +73,7 @@ public function __construct($username, $public=false) { activity.last_active, friends.status AS friends, friends.user_id AS friend, profile.gravatar, IF (profile.gravatar = 1, u.email , profile.img) as `image`, IF(priv.site_priv = 2, true, false) AS admin, IF(priv.forum_priv = 2, true, false) AS moderator, + priv.*, forum_posts.posts, articles.articles, (donated.user_id IS NOT NULL) AS donator, (users_blocks.user_id IS NOT NULL) AS blocked, (users_blocks_me.user_id IS NOT NULL) AS blockedMe, karma.karma FROM users u LEFT JOIN users_profile profile @@ -107,12 +108,9 @@ public function __construct($username, $public=false) { if (isset($this->image)) { $gravatar = isset($this->gravatar) && $this->gravatar == 1; $this->image = profile::getImg($this->image, 198, $gravatar); - } else + } else { $this->image = profile::getImg(null, 198); - - - if ($public) - return true; + } $st = $this->app->db->prepare('SELECT users_medals.medal_id, medals.label, medals.description, medals_colours.colour @@ -125,6 +123,17 @@ public function __construct($username, $public=false) { $st->execute(array(':uid' => $this->uid)); $this->medals = $st->fetchAll(); + if (!$this->app->user->admin) { + unset($this->site_priv); + unset($this->pm_priv); + unset($this->forum_priv); + unset($this->pub_priv); + } + + // Limit the amount of information public users can see + if ($public) + return true; + $st = $this->app->db->prepare('SELECT u.user_id as uid, u.username, users_friends.status, u.score, profile.gravatar, IF (profile.gravatar = 1, u.email , profile.img) as `image` FROM users_friends as friends INNER JOIN users u diff --git a/files/templates/admin_article_comments.html b/files/templates/admin_article_comments.html new file mode 100644 index 0000000..787bd43 --- /dev/null +++ b/files/templates/admin_article_comments.html @@ -0,0 +1,23 @@ +
+

Latest article comments

+ + + + + + + + + + + {% for article in articles %} + + + + + + + {% endfor %} + +
AuthorArticleCategorySubmitted
{{ article.username }}{{ article.title }}{{ article.comment }}
+
\ No newline at end of file diff --git a/files/templates/admin_logs.html b/files/templates/admin_logs.html new file mode 100644 index 0000000..b1afb3b --- /dev/null +++ b/files/templates/admin_logs.html @@ -0,0 +1,21 @@ +
+

Moderator logs

+ + + + + + + + + + {% for log in logs %} + + + + + + {% endfor %} + +
ModeratorActionTime
{{ log.username }}{{ log.subject }}
+
\ No newline at end of file diff --git a/files/templates/admin_user_manager.html b/files/templates/admin_user_manager.html index f52d6d6..599e66b 100644 --- a/files/templates/admin_user_manager.html +++ b/files/templates/admin_user_manager.html @@ -1,4 +1,4 @@ -
+

Users

@@ -8,35 +8,19 @@

Users

-
+

Moderators

\ No newline at end of file diff --git a/files/templates/profile.html b/files/templates/profile.html index 6dab281..2476ff8 100644 --- a/files/templates/profile.html +++ b/files/templates/profile.html @@ -28,13 +28,19 @@

{{ profile.username }}

- {% if profile.admin %} - Administrator - {% elseif profile.moderator %} - Moderator + {% if moderator %} + Site + PM + Forum + Publish + {% else %} + {% if profile.admin %} + Administrator + {% elseif profile.moderator %} + Moderator + {% endif %} {% endif %} -
diff --git a/html/files/css/main.scss b/html/files/css/main.scss index 97addc6..a01c6af 100644 --- a/html/files/css/main.scss +++ b/html/files/css/main.scss @@ -373,7 +373,8 @@ blockquote { } } &.medal-error { - background: darken($red, 10%); + background: $red; + color: white; } } diff --git a/html/files/js/admin.js b/html/files/js/admin.js index 073a586..c485342 100644 --- a/html/files/js/admin.js +++ b/html/files/js/admin.js @@ -6,23 +6,44 @@ $(function() {
  • #${uid}
  • \
  • ${email}
  • \
  • Score: ${score}
  • \ -
  • Levels: N/A
  • \ +
  • \ + Contributor\ + Helper\ +
  • \ \ -
    \ - Site\ - PM\ - Forum\ - Publish\ +
    \ + Site\ + PM\ + Forum\ + Publish\
    '; /* DASHBOARD */ - $('.admin-module-user-manager input').on('keyup', function() { + $('.admin-module-user-manager input').on('keydown', function(e) { + if (e.which == 13) { // ignoring enter + e.preventDefault(); + return; + } + }); + + $('.admin-module-user-manager input').on('keyup', function(e) { var $this = $(this); + delay(function() { $('.user-details').html("Loading..."); var term = $this.val(); $.getJSON('/api/?method=user.profile&user='+term, function(data) { if (data.profile && data.profile.username) { + + // Check if they have medals + $.each(data.profile.medals, function() { + if (this.label == 'Contributor') { + data.profile.medal_contributor = true; + } else if (this.label == 'helper') { + data.profile.medal_helper = true; + } + }); + $('.user-details').html($(tmpl_user_details).tmpl(data.profile)); } else { $('.user-details').html("User not found"); @@ -31,6 +52,54 @@ $(function() { }, 500); }); + // User controls + $('.admin-module-user-manager-editable, .admin-module-moderators-editable').on('click', 'a.medal', function(e) { + e.preventDefault(); + + var $this = $(this), + value; + + // Manage privilages + if ($this.data('priv')) { + if ($this.hasClass('medal-gold')) { + $this.removeClass('medal-gold'); + value = 1; + } else if ($this.hasClass('medal-error')) { + $this.removeClass('medal-error'); + $this.addClass('medal-gold'); + value = 2; + } else { + $this.addClass('medal-error'); + value = 0; + } + + var data = {}; + data.uid = $this.parent().data('uid'); + data.priv = $this.data('priv'); + data.priv_value = value; + console.log(data); + + $.post('/api/?method=user.admin.priv', data); + } else if ($this.data('medal')) { + // Manage medals + if ($this.hasClass('medal-green')) { + $this.removeClass('medal-green'); + value = 0; + } else { + $this.addClass('medal-green'); + value = 1; + } + + var data = {}; + data.uid = $this.parent().data('uid'); + data.medal = $this.data('medal'); + data.medal_value = value; + console.log(data); + + $.post('/api/?method=user.admin.medal', data); + } + }); + var delay = (function(){ var timer = 0; return function(callback, ms){ diff --git a/html/files/js/admin_forum.js b/html/files/js/admin_forum.js index e4dd35c..9b3a944 100644 --- a/html/files/js/admin_forum.js +++ b/html/files/js/admin_forum.js @@ -101,9 +101,16 @@ $(function() { \ '; + var postEditTmpl = '\ +
    \ + \ + \ +
    \ +
    '; + var modal_thread_edit = 'hello', modal_thread_delete = $(threadDeleteTmpl).tmpl()[0].outerHTML, - modal_post_edit = 'hello', + modal_post_edit = $(postEditTmpl).tmpl()[0].outerHTML, modal_post_delete = $(postDeleteTmpl).tmpl()[0].outerHTML; $('.thread-edit, .thread-delete, .post-edit, .post-delete').on('click', function(e) { From eed342df578ff82f61aec3b87a339cff539bb746 Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Wed, 22 Jul 2015 09:55:08 +0000 Subject: [PATCH 075/112] fix(admin) timestamps in moderator logs --- files/class.admin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/class.admin.php b/files/class.admin.php index 10378c9..a54c9e6 100644 --- a/files/class.admin.php +++ b/files/class.admin.php @@ -37,7 +37,7 @@ public function getUnreadTickets() { /******* LOGS *******/ public function getModeratorLogs($limit = true) { - $sql = "SELECT `report_id`, `type`, `subject`, username + $sql = "SELECT `report_id`, `type`, `subject`, username, `time` FROM mod_reports INNER JOIN `users` ON `users`.user_id = `mod_reports`.user_id @@ -229,4 +229,4 @@ public function setModeratorPriv($user_id, $priv, $priv_value) { } } -?> \ No newline at end of file +?> From beef0124ba47e84f95ffd872dcb11aea683f2c5f Mon Sep 17 00:00:00 2001 From: Doron Linder Date: Fri, 31 Jul 2015 01:17:22 +0300 Subject: [PATCH 076/112] Fixing mod_reports table definition to pass installation --- sql/schema.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/schema.sql b/sql/schema.sql index 432c12d..100571b 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -489,7 +489,7 @@ CREATE TABLE IF NOT EXISTS mod_reports ( `subject` varchar(64), `body` text, `visible` tinyint(1) DEFAULT 1, - `time` timestamp DEFAULT CURRENT_TIMESTAMP + `time` timestamp DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`report_id`), FOREIGN KEY (`user_id`) REFERENCES users (`user_id`) ) ENGINE=InnoDB; From 63b32a5508060b670a23bc82d701d1dc60c4d201 Mon Sep 17 00:00:00 2001 From: Doron Linder Date: Fri, 31 Jul 2015 01:20:31 +0300 Subject: [PATCH 077/112] Adding cdns to content security policy so that site will run locally (hackthis-10af.kxcdn.com cdn.socket.io and d3t63m1rxnixd2.cloudfront.net) --- files/init.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/init.php b/files/init.php index 21253c2..f823a12 100644 --- a/files/init.php +++ b/files/init.php @@ -13,9 +13,9 @@ // Content Security Policy $csp_rules = " - default-src 'self' https://hackthis.co.uk:8080 wss://hackthis.co.uk:8080 https://themes.googleusercontent.com https://*.facebook.com https://fonts.gstatic.com; - script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.googleapis.com https://*.google-analytics.com https://hackthis.co.uk:8080 https://cdnjs.cloudflare.com https://*.twitter.com https://*.api.twitter.com https://pagead2.googlesyndication.com *.newrelic.com https://www.google.com https://ssl.gstatic.com https://members.internetdefenseleague.org; - style-src 'self' 'unsafe-inline' https://*.googleapis.com; + default-src 'self' https://hackthis.co.uk:8080 wss://hackthis.co.uk:8080 https://themes.googleusercontent.com https://*.facebook.com https://fonts.gstatic.com https://hackthis-10af.kxcdn.com; + script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.googleapis.com https://*.google-analytics.com https://hackthis.co.uk:8080 https://cdnjs.cloudflare.com https://*.twitter.com https://*.api.twitter.com https://pagead2.googlesyndication.com *.newrelic.com https://www.google.com https://ssl.gstatic.com https://members.internetdefenseleague.org https://hackthis-10af.kxcdn.com https://cdn.socket.io https://d3t63m1rxnixd2.cloudfront.net; + style-src 'self' 'unsafe-inline' https://*.googleapis.com https://hackthis-10af.kxcdn.com; img-src *; object-src 'self' https://*.youtube.com https://*.ytimg.com; frame-src 'self' https://googleads.g.doubleclick.net https://*.youtube-nocookie.com https://*.vimeo.com https://kiwiirc.com https://www.google.com"; From 9c2fe120778610f3c1fdf19d2fb3bc5009418d61 Mon Sep 17 00:00:00 2001 From: Doron Linder Date: Fri, 31 Jul 2015 01:38:35 +0300 Subject: [PATCH 078/112] Installation requires ldap support for php or server won't work (no registeration etc.) --- install_hackthis_ubuntu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install_hackthis_ubuntu.sh b/install_hackthis_ubuntu.sh index fb28f25..c3b9b67 100755 --- a/install_hackthis_ubuntu.sh +++ b/install_hackthis_ubuntu.sh @@ -92,7 +92,7 @@ ls README.md > /dev/null 2>&1 || { git_root_dir=`pwd` # Package installation -required_packages="apache2 php5 libapache2-mod-php5 mysql-server php5-mysql" +required_packages="apache2 php5 libapache2-mod-php5 mysql-server php5-mysql php5-ldap" echo Checking installed packages for package in $required_packages; do installMissingPackages $package From c2cd7451702caf831580415ba3726998c80ae325 Mon Sep 17 00:00:00 2001 From: ThibmoRozier Date: Thu, 17 Dec 2015 14:57:46 +0100 Subject: [PATCH 079/112] Fix(install): Fixing 500 internal server error (Apache2 on Ubuntu) * Fixes: Invalid command 'RewriteEngine' #167 --- install_hackthis_ubuntu.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/install_hackthis_ubuntu.sh b/install_hackthis_ubuntu.sh index c3b9b67..68fb49f 100755 --- a/install_hackthis_ubuntu.sh +++ b/install_hackthis_ubuntu.sh @@ -177,7 +177,6 @@ mysql -u $mysql_user $pass_clause < setup.sql echo -e "\t HackThis database was initialized" # Setting up directories and permissions - echo Setting up directories and permissions mkdir -p html/files/css/min{,/light,/dark} chmod 777 html/files/css/min{,/light,/dark} @@ -190,4 +189,9 @@ chmod 777 files/cache{,/twig} mkdir -p files/logs chmod 777 files/logs +# Enabeling ModRewrite to solve RewriteEngine issue +echo Enabeling Mod_Rewrite +a2enmod rewrite +service apache2 restart + echo Done! From 05e05fb8d6ab04874fd364f075b67e1df669eff9 Mon Sep 17 00:00:00 2001 From: xenob8 Date: Mon, 22 Feb 2016 15:09:22 +0100 Subject: [PATCH 080/112] Update view.php line 105, a bug in the code doesn't let a user edit a submitted but not approved article (redirects always to $id=1 and not the correct one) id;?> instead of should do the job? --- html/articles/view.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/articles/view.php b/html/articles/view.php index 194a901..4d162e8 100644 --- a/html/articles/view.php +++ b/html/articles/view.php @@ -102,7 +102,7 @@
    - + user->admin_pub_priv): ?> From b2015e6585fd8e9e843172eacc827fdbdf2b6aa9 Mon Sep 17 00:00:00 2001 From: Mugiwara Date: Tue, 8 Mar 2016 14:02:53 +0100 Subject: [PATCH 081/112] Allowing use of 'data:' for pictures As we can see in console, there is a problem with a script trying to injecting a picture using 'data:' format The script is this one : https://github.com/HackThis/hackthis.co.uk/blob/master/html/files/js/favcounter.js The problem is from CSP rule : 'img-src *' which disallow the use of 'data:' format. With this new rule, the script should works as he has to ! --- files/init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/init.php b/files/init.php index f823a12..0bdf1f3 100644 --- a/files/init.php +++ b/files/init.php @@ -16,7 +16,7 @@ default-src 'self' https://hackthis.co.uk:8080 wss://hackthis.co.uk:8080 https://themes.googleusercontent.com https://*.facebook.com https://fonts.gstatic.com https://hackthis-10af.kxcdn.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.googleapis.com https://*.google-analytics.com https://hackthis.co.uk:8080 https://cdnjs.cloudflare.com https://*.twitter.com https://*.api.twitter.com https://pagead2.googlesyndication.com *.newrelic.com https://www.google.com https://ssl.gstatic.com https://members.internetdefenseleague.org https://hackthis-10af.kxcdn.com https://cdn.socket.io https://d3t63m1rxnixd2.cloudfront.net; style-src 'self' 'unsafe-inline' https://*.googleapis.com https://hackthis-10af.kxcdn.com; - img-src *; + img-src * data:; object-src 'self' https://*.youtube.com https://*.ytimg.com; frame-src 'self' https://googleads.g.doubleclick.net https://*.youtube-nocookie.com https://*.vimeo.com https://kiwiirc.com https://www.google.com"; header("Content-Security-Policy: " . $csp_rules); From 87b3968097ffacad773cc8fced5039a054ee526c Mon Sep 17 00:00:00 2001 From: mobilecrackers Date: Wed, 15 Jun 2016 21:09:21 +0530 Subject: [PATCH 082/112] Added spoiler tag Spoiler tag has been added with eye icon
  • --- files/elements/wysiwyg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/elements/wysiwyg.php b/files/elements/wysiwyg.php index ecc729c..2868bdf 100644 --- a/files/elements/wysiwyg.php +++ b/files/elements/wysiwyg.php @@ -25,7 +25,7 @@ ?>
  • - +
  • From b4235ef4f192ad6329b9db6dfd0ce74de2865a62 Mon Sep 17 00:00:00 2001 From: L3gand Date: Wed, 15 Jun 2016 22:03:55 +0530 Subject: [PATCH 083/112] Bug fix on user name itemprop='author' href='/user/flabbyrabbit>'>flabbyrabbit to itemprop='author' href='/user/flabbyrabbit'>flabbyrabbit There was a extra > symbol which leads to error page --- html/conduct.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/conduct.php b/html/conduct.php index 56353f8..2c4b5cc 100644 --- a/html/conduct.php +++ b/html/conduct.php @@ -19,7 +19,7 @@

    Code of Conduct

    September 7, 2014 - +
    From d932d32d28b51ad7eb548a674399809e52a1478f Mon Sep 17 00:00:00 2001 From: L3gand Date: Wed, 15 Jun 2016 22:23:26 +0530 Subject: [PATCH 084/112] Bug fix on user name #2 There is a extra > symbol after the username --- html/privacy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/privacy.php b/html/privacy.php index 4b17954..248f6ec 100644 --- a/html/privacy.php +++ b/html/privacy.php @@ -19,7 +19,7 @@

    Privacy Policy

    June 8, 2014 - +
    From c45d510a29560eec01030294ae2b0173a8ce7c10 Mon Sep 17 00:00:00 2001 From: L3gand Date: Wed, 15 Jun 2016 22:24:41 +0530 Subject: [PATCH 085/112] Bug fix on user name #3 There is an extra > symbol after the user name --- html/terms.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/terms.php b/html/terms.php index dc986d6..f6b14e6 100644 --- a/html/terms.php +++ b/html/terms.php @@ -19,7 +19,7 @@

    Terms of Use

    April 1, 2014 - +
    From 314df40b31bd06a829aae6316e0491e67c420832 Mon Sep 17 00:00:00 2001 From: L3gand Date: Thu, 16 Jun 2016 10:31:24 +0530 Subject: [PATCH 086/112] Update wysiwyg.php --- files/elements/wysiwyg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/elements/wysiwyg.php b/files/elements/wysiwyg.php index 2868bdf..ffc0c19 100644 --- a/files/elements/wysiwyg.php +++ b/files/elements/wysiwyg.php @@ -25,7 +25,7 @@ ?>
  • -
  • +
  • From 5ba5a5eceac2321e0f4bdafc7a51da86c90aace3 Mon Sep 17 00:00:00 2001 From: Agra Fikri Luqmansyah Date: Fri, 1 Jul 2016 22:00:53 +0700 Subject: [PATCH 087/112] Update faq.php --- html/faq.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/html/faq.php b/html/faq.php index e2170f6..329a3d2 100644 --- a/html/faq.php +++ b/html/faq.php @@ -24,7 +24,7 @@

    Forum

    How do I start a thread in the forum‭?

    - A new thread can be only started under a sub-section.‭ ‬If you go on the forum,‭ ‬or even under a section,‭ ‬you will see that the button‭ “‬New thread‭” ‬is not active and if you try to click on it,‭ ‬a message will appear saying that you can only start a new thread after you chose a sub-section.‭ + A new thread can only be started under a sub-section.‭ ‬If you go to the forum,‭ ‬or even under a section,‭ ‬you will see that the button‭ “‬New thread‭” ‬is not active and if you try to click on it,‭ ‬a message will appear saying that you can only start a new thread after you chose a sub-section.‭

    @@ -32,11 +32,11 @@

    What is karma?

    - Karma is a way of rating users posts within the forum. The karma rating is displayed as a number in the top of left of each forum post. Users with the bronze Karma medal are allowed to upvote posts and only users with the silver Karma medal can down vote. You earn the medals by completing levels and being active within the forum. Posts with karma of -3 or less are automatically hidden although this isn't the main role of the karma system, see reporting posts below. + Karma is a way to rate users posts within the forum. The karma rating is displayed as a number in the top of left of each forum post. Users with the bronze Karma medal are allowed to upvote posts and only users with the silver Karma medal can down vote. You earn the medals by completing levels and being active within the forum. Posts with karma of -3 or less are automatically hidden although this isn't the main role of the karma system, see reporting posts below.

    How do I control which threads I get notifications for?

    - You have the ability to watch and unwatch any thread in the forum. At the top of each thread your current preference is displayed and can be changed simply by clicking the button. By posting in a thread you automatically start watching that thread. + You have the ability to watch and unwatch any thread in the forum. At the top of each thread your current preference is displayed and can be simply changed by clicking the button. By posting in a thread you automatically start watching that thread.

    How do I report bad content within the forum?

    @@ -46,4 +46,4 @@ \ No newline at end of file +?> From 568a5bae87ce5047c76f6842eaf1fee22dda2900 Mon Sep 17 00:00:00 2001 From: Agra Fikri Luqmansyah Date: Fri, 1 Jul 2016 22:03:11 +0700 Subject: [PATCH 088/112] Update faq.php From 46f83925adcd7fd75c35687a6e54b36a993cd6a1 Mon Sep 17 00:00:00 2001 From: Richard Brook Date: Sun, 3 Jul 2016 21:22:50 +0100 Subject: [PATCH 089/112] Update class.admin.php --- files/class.admin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/class.admin.php b/files/class.admin.php index a54c9e6..abc634e 100644 --- a/files/class.admin.php +++ b/files/class.admin.php @@ -6,7 +6,7 @@ class admin { 'This post is primarily an advertisement with no disclosure. It is not useful or relevant, but promotional. If you are interested in advertising on our platform please contact us.', 'This post has severe formatting or content problems. Please be more considered when posting in future.', 'The communities first and only language is English. If you are feel you need to talk in another language please find another member who can speak that language and contact them directly via PM.', - 'This post refers to a post that longer exists and is being removed just to tidy things up. Don\'t worry about this report.'); + 'This post refers to a post that no longer exists and is being removed just to tidy things up. Don\'t worry about this report.'); private $forum_reasons_threads = array('This thread is not relevant to this site. If you want to ask a user a specific question unrelated to the site topic please use the PM system.', 'This thread is primarily an answer or has far more detail than is necessary to be helpful.', 'This thread is primarily an advertisement with no disclosure. It is not useful or relevant, but promotional. If you are interested in advertising on our platform please contact us.', From e9cca2f823e36aa6bfb99e50997c543453f8fdbc Mon Sep 17 00:00:00 2001 From: Dom-1 Date: Sun, 28 Aug 2016 20:44:20 -0500 Subject: [PATCH 090/112] Update class.admin.php --- files/class.admin.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/files/class.admin.php b/files/class.admin.php index abc634e..f8b2d91 100644 --- a/files/class.admin.php +++ b/files/class.admin.php @@ -4,14 +4,14 @@ class admin { private $forum_reasons_posts = array('This post is not relevant to the thread. If you need help or want to post something that has not been discussed then please create a new thread. If you want to ask a user a specific question unrelated to the current topic please use the PM system.', 'This post is primarily an answer or has far more detail than is necessary to be helpful.', 'This post is primarily an advertisement with no disclosure. It is not useful or relevant, but promotional. If you are interested in advertising on our platform please contact us.', - 'This post has severe formatting or content problems. Please be more considered when posting in future.', - 'The communities first and only language is English. If you are feel you need to talk in another language please find another member who can speak that language and contact them directly via PM.', + 'This post has severe formatting or content problems. Please be more cautious when posting in future.', + 'The communities first and only language is English. If you feel you need to talk in another language please find another member who can speak that language and contact them directly via PM.', 'This post refers to a post that no longer exists and is being removed just to tidy things up. Don\'t worry about this report.'); private $forum_reasons_threads = array('This thread is not relevant to this site. If you want to ask a user a specific question unrelated to the site topic please use the PM system.', 'This thread is primarily an answer or has far more detail than is necessary to be helpful.', 'This thread is primarily an advertisement with no disclosure. It is not useful or relevant, but promotional. If you are interested in advertising on our platform please contact us.', - 'This thread has severe formatting or content problems. Please be more considered when posting in future.', - 'The communities first and only language is English. If you are feel you need to talk in another language please find another member who can speak that language and contact them directly via PM.', + 'This thread has severe formatting or content problems. Please be more cautious when posting in future.', + 'The communities first and only language is English. If you feel you need to talk in another language please find another member who can speak that language and contact them directly via PM.', 'This thread has been removed to tidy things up. Don\'t worry about this report.'); public function __construct($app) { From 161cbe2fa7a0eae5e83e9911a0ce7c4f818d058d Mon Sep 17 00:00:00 2001 From: Luke Ward <0x6C77@gmail.com> Date: Sat, 10 Sep 2016 10:53:07 +0100 Subject: [PATCH 091/112] Fixed mismatched close thread CSRF key --- html/forum/view.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html/forum/view.php b/html/forum/view.php index 0087af8..720f13e 100644 --- a/html/forum/view.php +++ b/html/forum/view.php @@ -110,7 +110,7 @@ closed && $thread->question->user_id === $app->user->uid): ?> - Close + Close closed && $thread->question->user_id === $app->user->uid) { - $app->utils->message("Is one of these posts the answer to your question? If so click here to close thread.
    After closing a thread no more posts will be accepted.", 'info'); + $app->utils->message("Is one of these posts the answer to your question? If so click here to close thread.
    After closing a thread no more posts will be accepted.", 'info'); } ?>

      From 6530b440a8b69ca802bf90614957f81a7a6fd121 Mon Sep 17 00:00:00 2001 From: gala0sup Date: Thu, 20 Oct 2016 22:50:13 +0530 Subject: [PATCH 092/112] Updated install_hackthis_windows.sh the path to sh.exe "c:\program files (x86)\Git\bin\sh.exe" in the script is wrong as in win10 update program files (x86) is changed to program files so just edited that --- install_hackthis_windows.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/install_hackthis_windows.sh b/install_hackthis_windows.sh index 7b1ac8c..3301531 100755 --- a/install_hackthis_windows.sh +++ b/install_hackthis_windows.sh @@ -118,16 +118,16 @@ ls /c/wamp/wampmanager.exe > /dev/null 2>&1 || { } echo -e "\t WAMP installation found in C:\wamp" -# Make sure that sh.exe is found in C:\Program Files (x86)\Git\bin\sh.exe -ls /c/Program\ Files\ \(x86\)/Git/bin/sh.exe > /dev/null 2>&1 || { +# Make sure that sh.exe is found in C:\Program Files\Git\bin\sh.exe +ls /c/Program\ Files/Git/bin/sh.exe > /dev/null 2>&1 || { echo echo -e "\t Git Bash was not installed in the default location." - echo -e '\t Installation requires sh.exe to be found at C:\Program Files (x86)\Git\\bin\sh.exe.' + echo -e '\t Installation requires sh.exe to be found at C:\Program Files\Git\\bin\sh.exe.' echo echo -e "\e[0;31mAborting...\e[m" waitForEnterAndExit } -echo -e "\t Git Bash installation found in C:\Program Files (x86)/Git/bin/sh.exe" +echo -e "\t Git Bash installation found in C:\Program Files/Git/bin/sh.exe" # Make sure mysql client is installed in C:\wamp\bin\mysql\[mysql version\bin\mysql.exe mysql_client=`ls /c/wamp/bin/mysql/*/bin/mysql.exe 2>/dev/null`; From 9d631248a95e7afe99e07b1f8506f576685a5dec Mon Sep 17 00:00:00 2001 From: Dom-1 Date: Sun, 20 Nov 2016 13:39:18 -0600 Subject: [PATCH 093/112] Update version_history.md Simple spelling error --- files/cache/version_history.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/cache/version_history.md b/files/cache/version_history.md index c5ad416..797b11d 100644 --- a/files/cache/version_history.md +++ b/files/cache/version_history.md @@ -75,7 +75,7 @@ * W3C validation fixes - [DJDavid98](/user/djdavid98) * Styling fix for invisible select elements - [DJDavid98](/user/djdavid98) -## 23-02-2104 +## 23-02-2014 * Code added to handle plural articles on article contributors - [DJDavid98](/user/djdavid98) * Footer grammar fix - [kamzhik](/user/kamzhik) From 3e426033102d868c12ac260170e309fc134d04b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Pichard?= Date: Tue, 23 May 2017 12:53:27 +0200 Subject: [PATCH 094/112] Dynamic label attribute "for" --- files/elements/levels/level.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/elements/levels/level.php b/files/elements/levels/level.php index ec9e73d..e73f872 100644 --- a/files/elements/levels/level.php +++ b/files/elements/levels/level.php @@ -26,7 +26,7 @@
      method)?'method="'.strtoupper($form->method).'"':'';?>>
      fields AS $field): ?> - + ' autocomplete="off" name)?"id='{$field->name}' name='{$field->name}'":'';?>>
      @@ -45,4 +45,4 @@ echo ' '.$currentLevel->data['code']->pos4 . "\n"; } endif; -?> \ No newline at end of file +?> From a42b2006a780183d9eb6ebe38c103ec8f7cd5163 Mon Sep 17 00:00:00 2001 From: Ahmad Alyousef Date: Wed, 7 Jun 2017 11:14:59 +0300 Subject: [PATCH 095/112] Added an option to mark messages as seen. --- files/class.messages.php | 7 +++++++ html/inbox/index.php | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/files/class.messages.php b/files/class.messages.php index bdc115c..99fe747 100644 --- a/files/class.messages.php +++ b/files/class.messages.php @@ -171,6 +171,13 @@ public function deleteConvo($id) { return $st->execute(array(':uid' => $this->app->user->uid, ':pm_id' => $id)); } + public function markMessagesRead() + { + // Mark thread as seen + $st = $this->app->db->prepare("UPDATE pm_users SET `seen` = NOW() WHERE user_id = :uid"); + $st->execute(array(':uid' => $this->app->user->uid)); + } + public function newMessage($to, $body, $pm_id=null) { $body = trim($body); diff --git a/html/inbox/index.php b/html/inbox/index.php index 04d6797..6177bde 100644 --- a/html/inbox/index.php +++ b/html/inbox/index.php @@ -12,6 +12,11 @@ $status = $messages->deleteConvo($_GET['delete']); } + if(isset($_GET['readall'])) + { + $messages->markMessagesRead(); + } + if (isset($_POST['body']) && isset($_GET['view'])) { $result = $messages->newMessage(null, $_POST['body'], $_GET['view']); if ($result) { @@ -61,6 +66,9 @@ New Message + + Mark all as Read + @@ -195,6 +203,9 @@ if (isset($_GET['delete'])) { $app->utils->message($status?'Conversation deleted':'Error deleting conversation', $status?'good':'error'); } + if(isset($_GET['readall'])) { + $app->utils->message("All messages has been marked as read.", 'good'); + } ?>
      Date: Sat, 21 Oct 2017 11:58:24 -0600 Subject: [PATCH 096/112] Update contact.php Changed line 408. Fixed grammar issue and clarified phrase. The original file contained the text "Feel free to say hi or send us any comments or suggestions you have...good or bad.", which states that bad suggestions are wanted as well. At the very least, there needs to be a comma after "hi." --- html/contact.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html/contact.php b/html/contact.php index 7f06f09..fc51241 100644 --- a/html/contact.php +++ b/html/contact.php @@ -405,7 +405,7 @@

      Contact Us

      - The easiest way to contact us is to use the form provided below. We will respond to you as quickly as we can. Feel free to say hi or send us any comments or suggestions you have...good or bad. + The easiest way to contact us is to use the form provided below. We will respond to you as quickly as we can. Feel free to say hi, suggest changes, or share your thoughts with us (good or bad).

      \ No newline at end of file +?> From 3a628fc5e12657ac8a4322d02c8e247890696146 Mon Sep 17 00:00:00 2001 From: DIDIx13 Date: Sun, 29 Oct 2017 21:02:48 +0100 Subject: [PATCH 097/112] Create hackers.txt Found out it's missing, it's very unpopular and useless but still :) I found out the existence of hackers.txt when searching about robots.txt and humans.txt Here are some of my sources : http://humanstxt.org/img/Illustration-home/03-where-is-located.png http://www.scifiscripts.com/scripts/hackers.txt --- html/hackers.txt | 937 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 937 insertions(+) create mode 100644 html/hackers.txt diff --git a/html/hackers.txt b/html/hackers.txt new file mode 100644 index 0000000..1840afd --- /dev/null +++ b/html/hackers.txt @@ -0,0 +1,937 @@ +***************************************************************************** +* * +* From the March 1990 edition of Harper's Magazine * +* * +* IS COMPUTER HACKING A CRIME? * +* * +* Typed by Warlock, March 13. * +* * +***************************************************************************** + + The image of the computer hacker drifted into public awareness in the +middle Seventies, when reports of Chinese-food-consuming geniuses working +compulsively at keyboards began to issue from MIT. Over time, several of +these impresarios entered commerce, and the public's impression of hackers +changed: They were no longer nerds but young, millionaire entrepreneurs. + The most recent news reports have given the term a more felonious +connotation. Early this year, a graduate student named Robert Morris Jr. went +on trial for releasing a computer program known as a worm into the vast +Internet system, halting more than 6,000 computers. The subsequent public +debate ranged from the matter of proper punishment for a mischievous kid to +the issue of our rapidly changing notion of what constitutes free speech -- or +property -- in an age of modems and data bases. In order to allow hackers to +speak for themselves, Harper's Magazine recently organized an electronic +discussion and asked some of the nation's best hackers to "log on," discuss +the protean notions of contemporary speech, and explain what their powers and +talents are. + +The following forum is based on a discussion held on the WELL, a computer +bulletin board system based in Sausalito, California. The forum is the result +of a gradual accretion of arguments as the participants -- located throughout +the country -- opined and reacted over an eleven day period. Harper's Magazine +senior editor Jack Hitt and assistant editor Paul Tough served as moderators. + +ADELAIDE is a pseudonym for a former hacker who has sold his soul to the +corporate state as a computer programmer. + +BARLOW is John Perry Barlow, a retired cattle rancher, a former Republican +county chairman, and a lyricist for the Grateful Dead, who currently is +writing a book on computers and consciousness entitled Everything We Know Is +Wrong. + +BLUEFIRE is Dr. Robert Jacobson, associate director of the Human Interface +Technology Laboratory at the University of Washington and a former +information-policy analyst with the California legislature. + +BRAND is Russell Brand, a senior computer scientist with Reasoning Systems, in +Palo Alto, California. + +CLIFF is Clifford Stoll, the astronomer who caught a spy in a military computer +network and recently published an account of his investigation entitled The +Cuckoo's Egg. + +DAVE is Dave Hughes, a retired West Pointer who currently operates his own +political bulletin board. + +DRAKE is Frank Drake, a computer-science student at a West Coast university and +the editor of W.O.R.M., a cyberpunk magazine. + +EDDIE JOE HOMEBOY is a pseudonym for a professional software engineer who has +worked at Lucasfilm, Pyramid Technology, Apple Computer, and Autodesk. + +EMMANUEL GOLDSTEIN is the editor of 2600, the "hacker's quarterly." + +HANK is Hank Roberts, who builds mobiles, flies hang gliders, and proofreads +for the Whole Earth Catalog. + +JIMG is Jim Gasperini, the author, with TRANS Fiction Systems, of Hidden +Agenda, a computer game that simulates political conflict in Central America. + +JRC is Jon Carroll, daily columnist for the San Francisco Chronical and +writer-in-residence for the Pickle Family Circus, a national traveling circus +troupe based in San Francisco. + +KK is Kevin Kelly, editor of the Whole Earth Review and a cofounder of the +Hacker's Conference. + +LEE is Lee Felstein, who designed the Osborne-1 computer and cofounded the +Homebrew Computer Club. + +MANDEL is Tom Mandel, a professional futurist and an organizer of the Hacker's +Conference. + +RH is robert Horvitz, Washington correspondent for the Whole earth Review. + +RMS is Richard Stallman, founder of the Free Software Foundation. + +TENNEY is Glenn Tenney, an independant-systems architect and an organizer of +the Hacker's Conference. + +ACID PHREAK and PHIBER OPTIK are both pseudonyms for hackers who decline to be +identified. + + A Hacker's Lexicon + +Back Door: A point of entry into a computer system -- often installed there by +the original programmer -- that provides secret access. + +Bomb: A destructive computer program, which, when activated, destroys the +files in a computer system. + +Chipper: A hacker who specializes in changing the programming instructions of +computer chips. + +Cracker: A hacker who breaks illegally into computer systems and creates +mischief; often used pejoratively. The original meaning of cracker was +narrower, describing those who decoded copyright-protection schemes on +commercial software products or to modify them; sometimes known as a software +pirate. + +Hacker: Originally, a compulsive computer programmer. The word has evolved in +meaning over the years. Among computer users, hacker carries a positive +connotation, meaning anyone who creatively explores the operations of computer +systems. Recently, it has taken on a negative connotation, primarily through +confusion with cracker. + +Phone phreak: One who explores the operations of the phone system, often with +the intent of making free phone calls. + +Social engineering: A nontechnical means of gaining information simply by +persuading people to hand it over. If a hacker wished to gain access to a +computer system, for example, an act of social engineering might be able to +contact a system operator and to convince him or her that the hacker is a +legitimate user in need of a password; more colloquially, a con job. + +Virus: A program that, having been introduced into a system, replicates itself +and attaches itself to other programs, often with a variety of mischievous +effects. + +Worm: A destructive program that, when activated, fills a computer system with +self-replicating information, clogging the system so that its operations are +severely slowed, sometimes stopped. + + The Digital Frontier + +HARPER'S [Day 1, 9:00 A.M.]: When the computer was young, the word hacking was +used to describe the work of brilliant students who explored and expanded the +uses to which this new technology might be employed. There was even talk of a +"hacker ethic." Somehow, in the succeeding years, the word has taken on dark +connotations, suggestion the actions of a criminal. What is the hacker ethic, +and does it survive? + +ADELAIDE [Day 1, 9:25 A.M.]: the hacker ethic survives, and it is a fraud. It +survives in anyone excited by technology's power to turn many small, +insignificant things into one vast, beautiful thing. It is a fraud because +there is nothing magical about computers that causes a user to undergo +religious conversion and devote himself to the public good. Early automobile +inventors were hackers too. At first the elite drove in luxury. Later +practically everyone had a car. Now we have traffic jams, drunk drivers, air +pollution, and suburban sprawl. The old magic of an automobile occasionally +surfaces, but we possess no delusions that it automatically invades the +consciousness of anyone who sits behind the wheel. Computers are power, and +direct contact with power can bring out the best or worst in a person. It's +tempting to think that everyone exposed to the technology will be grandly +inspired, but, alas, it just ain't so. + +BRAND [Day 1, 9:54 A.M.] The hacker ethic involves several things. One is +avoiding waste; insisting on using idle computer power -- often hacking into a +system to do so, while taking the greatest precautions not to damage the +system. A second goal of many hackers is the free exchange of technical +information. These hackers feel that patent and copyright restrictions slow +down technological advances. A third goal is the advancement of human +knowledge for its own sake. Often this approach is unconventional. People we +call crackers often explore systems and do mischief. The are called hackers by +the press, which doesn't understand the issues. + +KK [Day 1, 11:19 A.M.]: The hacker ethic went unnoticed early on because the +explorations of basement tinkerers were very local. Once we all became +connected, the work of these investigations rippled through the world. today +the hacking spirit is alive and kicking in video, satellite TV, and radio. In +some fields they are called chippers, because the modify and peddle altered +chips. Everything that was once said about "phone phreaks" can be said about +them too. + +DAVE [Day 1, 11:29 A.M.]: Bah. Too academic. Hackers hack. Because the want +to. Not for any higher purpose. Hacking is not dead and won't be as long as +teenagers get their hands on the tools. There is a hacker born every minute. + +ADELAIDE [Day 1, 11:42 A.M.]: Don't forget ego. People break into computers +because it's fun and it makes them feel powerful. + +BARLOW [Day 1, 11:54 A.M.]: Hackers hack. Yeah, right, but what's more to the +point is that humans hack and always have. Far more than just opposable +thumbs, upright posture, or excess cranial capacity, human beings are set apart +from all other species by an itch, a hard-wired dissatisfaction. Computer +hacking is just the latest in a series of quests that started with fire +hacking. Hacking is also a collective enterprise. It brings to our joint +endeavors the simultaneity that other collective organisms -- ant colonies, +Canada geese -- take for granted. This is important, because combined with our +itch to probe is a need to connect. Humans miss the almost telepathic +connectedness that I've observed in other herding mammals. And we want it +back. Ironically, the solitary sociopath and his 3:00 A.M. endeavors hold the +most promise for delivering species reunion. + +EDDIE JOE HOMEBOY [Day 1, 4:44 P.M.]: Hacking really took hold with the advent +of the personal computer, which freed programmers from having to use a big +time-sharing system. A hacker could sit in the privacy of his home and hack to +his heart's and head's content. + +LEE [Day 1, 5:17 P.M.]: "Angelheaded hipsters burning for the ancient heavenly +connection to the starry dynamo in the machinery of night" (Allen Ginsberg, +"Howl"). I still get an endorphin rush when I go on a design run -- my mind +out over the edge, groping for possibilities that can be sensed when various +parts are held in juxtaposition with a view toward creating a whole object: +straining to get through the epsilon-wide crack between What Is and What Could +Be. Somewhere there's the Dynamo of Night, the ultra-mechanism waiting to be +dreamed, that we'll never get to in actuality, (think what is would weigh!) but +that's present somehow in the vicinity of those mental wrestling matches. When +I re-emerge into the light of another day with the design on paper -- and with +the knowledge that if it ever gets built, things will never be the same again +-- I know I've been where artists go. That's hacking to me: to transcend +custom and to engage in creativity for its own sake, but also to create +objective effects. I've been around long enough to see the greed creeps take +up the unattended reins of power and shut down most of the creativity that put +them where they are. But I've also seen things change, against the best +efforts of a stupidly run industry. We cracked the egg out from under the +Computer Priesthood, and now everyone can have omelets. + +RMS [Day 1, 5:19 P.M.]: The media and the courts are spreading a certain image +of hackers. It's important for us not to be shaped by that image. But there +are two ways that it can happen. One way is for hackers to become part of the +security-maintenance establishment. The other, more subtle, way is for a +hacker to become the security-breaking phreak the media portray. By shaping +ourselves into the enemy of the establishment, we uphold the establishment. But +there's nothing wrong with breaking security if you're accomplishing something +useful. It's like picking a lock on a tool cabinet to get a screwdriver to fix +your radio. As long as you put the screwdriver back, what harm does it do? + +ACID PHREAK [Day 1, 6:34 P.M.]: There is no one hacker ethic. Everyone has +his own. To say that we all think the same way is preposterous. The hacker +of old sought to find what the computer itself could do. There was nothing +illegal about that. Today, hackers and phreaks are drawn to specific, often +corporate, systems. It's no wonder everyone on the other side is getting mad. +We're always one step ahead. We were back then, and we are now. + +CLIFF [Day 1, 8:38 P.M.]: RMS said, "There's nothing wrong with breaking +security if you're accomplishing something useful." Huh? How about, There's +nothing wrong with entering a neighbor's house if you're accomplishing +something useful, just as long as you clean up after yourself. Does my +personal privacy mean anything? Should my personal letters and data be open to +anyone who knows how to crack passwords? If not my property, then how about a +bank's? Should my credit history be available to anyone who can find a back +door to the private computers of TRW, the firm that tracks people's credit +histories? How about a list of AIDS patients from a hospital's data bank? Or +next week's prime interest rate from a computer at the Treasury Department? + +BLUEFIRE [Day 1, 9:20 P.M.]: Computers are everywhere, and they link us +together into a vast social "cybernetia." The grand skills of the hackers, +formidable though they may have been, are incapable of subverting this +automated social order. The networks in which we survive are more than copper +wire and radio waves: They are the social organization. For every hacker in +revolt, busting through a security code, ten thousand people are being wired up +with automatic call-identification and credit-checking machines. Long live the +Computer Revolution, which died aborning. + +JRC [Day 1, 10:28 P.M.]: We have two different definitions here. One speaks +of a tinkerer's ecstasy, an ecstasy that is hard to maintain in the corporate +world but is nevertheless at the heart of Why Hackers Hack. The second is +political, and it has to do with the free flow of information. Information +should flow more freely (how freely is being debated), and the hacker can make +it happen because the hacker knows how to undam the pipes. This makes the +hacker ethic -- of necessity -- antiauthoritarian. + +EMMANUEL GOLDSTEIN [Day 2, 2:41 A.M.]: It's meaningless what we call +ourselves: hackers, crackers, techno-rats. We're individuals who happen to +play with high tech. There is no hacker community in the traditional sense of +the term. There are no leaders and no agenda. We're just individuals out +exploring. + +BRAND [Day 2, 9:02 A.M.]: There are two issues: invariance and privacy. +Invariance is the art of leaving things as you found them. If someone used my +house for the day and left everything as he found it so that there was no way +to tell he had been there, I would see no problem. With a well-run computer +system, we can assure invariance. Without this assurance we must fear that the +person picking the lock to get the screwdriver will break the lock, the +screwdriver, or both. Privacy is more complicated. I want my medical records, +employment records, and letters to The New Republic private because I fear that +someone will do something with the information that is against my interests. +If I could trust people not to do bad things with information, I would not need +to hide it. Rather than preventing the "theft" of this data, we should +prohibit its collection in the first place. + +HOMEBOY [Day 2, 9:37 A.M.]: Are crackers really working for the free flow of +information? Or are they unpaid tools of the establishment, identifying the +holes in the institutional dike so that they can be plugged by the +authorities, only to be tossed in jail or exiled? + +DRAKE [Day 2, 10:54 A.M.]: There is an unchallenged assumption that crackers +have some political motivation. Earlier, crackers were portrayed as failed +revolutionaries; now Homeboy suggests that crackers may be tools of the +establishment. These ideas about crackers are based on earlier experiences +with subcultures (beats, hippies, yippies). Actually, the contemporary +cracker is often middle-class and doesn't really distance himself from the +"establishment." While there are some anarcho-crackers, there are even more +right-wing crackers. The hacker ethic crosses political boundaries. + +MANDEL [Day 2, 11:01 A.M.]: The data on crackers suggests that they are either +juvenile delinquents or plain criminals. + +BARLOW [Day 2, 11:34 A.M.]: I would far rather have everyone know my most +intimate secrets than to have noncontextual snippits of them "owned" by TRW +and the FBI -- and withheld from me! Any cracker who is entertained by +peeping into my electronic window is welcome to the view. Any institution that +makes money selling rumors of my peccadilloes is stealing from me. Anybody who +wants to inhibit that theft with electronic mischief has my complete support. +Power to the techno-rats! + +EMMANUEL [Day 2, 7:09 P.M.]: Calling someone on the phone is the equivalent of +knocking on that person's door, right? Wrong! When someone answers the phone, +you are inside the home. You have already been let in. The same with an +answering machine, or a personal computer, if it picks up the phone. It is +wrong to violate a person's privacy, but electronic rummaging is not the same +as breaking and entering. The key here is that most people are unaware of how +easy it is for others to invade their electronic privacy and see credit +reports, phone bills, FBI files, Social Security reports. The public is +grossly underinformed, and that's what must be fixed if hackers are to be +thwarted. If we had an educated public, though, perhaps the huge -- and now +common -- date bases would never have been allowed to exist. Hackers have +become scapegoats: We discover the gaping holes in the system and then get +blamed for the flaws. + +HOMEBOY [Day 2, 7:41 P.M.]: Large, insular, undemocratic governments and +institutions need scapegoats. It's the first step down the road to fascism. +That's where hackers play into the hands of the establishment. + +DAVE [Day 2, 7:55 P.M.]: If the real criminals are those who leave gaping +holes in their systems, the the real criminals in house burglaries are those +who leave their windows unlatched. Right? Hardly. And Emmanuel's analogy to +a phone being answered doesn't hold either. There is no security protection in +making a phone call. A computer system has a password, implying a desire for +security. Breaking into a poorly protected house is still burglary. + +CLIFF [Day 2, 9:06 P.M.]: Was there a hacker's ethic and does it survive? +More appropriately, was there a vandal's ethic and does it survive? As long as +there are communities, someone will violate the trust that binds them. Once, +our computers were isolated, much as eighteenth-century villages were. Little +was exchanged, and each developed independently. Now we've built far-flung +electronic neighborhoods. These communities are built on trust: people +believing that everyone profits by sharing resources. Sure enough, vandals +crept in, breaking into systems, spreading viruses, pirating software, and +destroying people's work. "It's okay," they say. "I can break into a system +because I'm a hacker." Give me a break! + +BARLOW [Day 2, 10:41 P.M.]: I live in a small town. I don't have a key to my +house. Am I asking for it? I think not. Among the juvenile delinquents in my +town, there does exist a vandal's ethic. I know because I once was one. In a +real community, part of a kid's rite of passage is discovering what walls can +be breached. Driving 110 miles per hour on Main Street is a common symptom of +rural adolescence, publicly denounced but privately understood. Many teenagers +die in this quest -- two just the night before last -- but it is basic to our +culture. Even rebellious kids understand that risk to one's safety is one +thing, wanton vandalism or theft is another. As a result, almost no one locks +anything here. In fact, a security system is an affront to a teenage psyche. +While a kid might be dissuaded by conscience, he will regard a barricade as an +insult and a challenge. So the CEOs who are moving here (the emperor of +PepsiCo and the secretary of state among them) soon discover that over the +winter people break into their protected mansions just to hang out. When +systems are open, the community prospers, and teenage miscreants are satisfied +to risk their own lives and little else. When the social contract is enforced +by security, the native freedom of the adolescent soul will rise up to +challenge it in direct proportion to its imposition. + +HANK [Day 2, 11:23 P.M.]: Barlow, the small town I grew up in was much like +yours -- until two interstate highways crossed nearby. The open-door style +changed in one, hard summer because our whole town became unlocked. I think +Cliff's community is analogous to my little town -- confronted not by a new +locked-up neighbor who poses a challenge to the local kids but by a sudden, +permanent opening up of the community to many faceless outsiders who owe the +town no allegiance. + +EMMANUEL [Day 3, 1:33 A.M.]: Sorry, I don't buy Dave's unlatched-window +analogy. A hacker who wanders into a system with the ease that it's done today +is, in my analogy, walking into a house without walls -- and with a cloaking +device! Any good hacker can make himself invisible. If housebreaking were +this easy, people would be enraged. But we're missing the point. I'm not +referring to accessing a PC in someone's bedroom but about accessing credit +reports, government files, motor vehicle records, and the megabytes of data +piling up on each of us. Thousands of people legally see and use this +ever-growing mountain of data, much of it erroneous. Whose rights are we +violating when we peruse a file? Those of the person we look up? He doesn't +even know that information exists, that it was compiled without his consent, +and that it's not his property anymore! The invasion of privacy took place +long before the hacker ever arrived. The only way to find out how such a +system works is to break the rules. It's not what hackers do that will lead us +into a state of constant surveillance; it's allowing the authorities to impose +on us a state of mock crisis. + +MANDEL [Day 3, 9:27 A.M.]: Note that the word crime has no fixed reference in +our discussion. Until recently, breaking into government computer systems +wasn't a crime; now it is. In fact, there is some debate, to be resolved in +the courts, whether what Robert Morris Jr. did was actually a crime [see "A +Brief History of Hacking"]. Crime gets redefined all the time. Offend enough +people or institutions and, lo and behold, someone will pass a law. That is +partly what is going on right now: Hackers are pushing buttons, becoming more +visible, and that inevitably means more laws and more crimes. + +ADELAIDE [Day 3, 9:42 A.M.]: Every practitioner of these arts knows that at +minimum he is trespassing. The English "country traveler ethic" applies: The +hiker is always ethical enough to close the pasture gates behind him so that no +sheep escape during his pastoral stroll through someone else's property. The +problem is that what some see as a gentle trespassing others see as theft of +service, invasion of privacy, threat to national security -- take your pick. + +BARLOW [Day 3, 2:38 P.M.]: I regard the existence of proprietary data about me +to be theft -- not just in the legal sense but in a faintly metaphysical one, +rather like the belief among aborigines that a photograph steals the soul. The +crackers who maintain access to that data are, at this level, liberators. +Their incursions are the only way to keep the system honest. + +RMS [Day 3, 2:48 P.M.]: Recently, a tough anti-hacker measure was proposed in +England. In The Economist I saw a wise response, arguing that it was silly to +treat an action as worse when it involves a computer that when it does not. +They noted, for example, that physical trespassing was considered a civil +affair, not a criminal one, and said that computer trespassing should be +treated likewise. Unfortunately, the U.S. government was not so wise. + +BARLOW [Day 3, 3:23 P.M.]: The idea that a crime is worse if a computer is +involved relates to the gathering governmental perception that computer viruses +and guns may be related. I know that sounds absurd, but they have more in +common than one might think. For all its natural sociopathy, the virus is not +without philosophical potency -- like a gun. Here in Wyoming guns are part of +the furniture. Only recently have I observed an awareness of their political +content. After a lot of frothing about prying cold, dead fingers from +triggers, the sentiment was finally distilled to a bumper sticker I saw on a +pickup the other day: "Fear the Government That Fears Your Gun." Now I've read +too much Ghandi to buy that line without misgivings, but it would be hard to +argue that Tiananmen Square could have been inflicted on a populace capable of +shooting back. I don't wholeheartedly defend computer viruses, but one must +consider their increasingly robust deterrent potential. Before it's over, the +War on Drugs could easily turn into an Armageddon between those who love +liberty and those who crave certainty, providing just the excuse the control +freaks have been waiting for to rid America of all that constitutional +mollycoddling called the Bill of Rights. Should that come to pass, I will want +to use every available method to vex and confuse the eyes and ears of +surveillance. The virus could become the necessary instrument of our freedom. +At the risk of sounding like some digital posse comitatus, I say* Fear the +Government That Fears Your Computer. + +TENNEY [Day 3, 4:41 P.M.]: Computer-related crimes are more feared because +they are performed remotely -- a crime can be committed in New York by someone +in Los Angeles -- and by people not normally viewed as being criminals -- by +teenagers who don't look like delinquents. They're smart nerds, and they don't +look like Chicago gangsters packing heat. + +BARLOW [Day 4, 12:12 A.M.]: People know so little of these things that they +endow computers and the people who do understand them with powers neither +possesses. If America has a religion, its ark is the computer and its +covenant is the belief that Science Knows. We are mucking around in the +temple, guys. It's a good way to catch hell. + +DAVE [Day 4, 9:18 A.M.]: Computers are the new American religion. The public +is in awe of -- and fears -- the mysteries and the high priests who tend them. +And the public reacts just as it always has when faced with fear of the unknown +-- punishment, burning at the stake. Hackers are like the early Christians. +When caught, they will be thrown to the lions before the Roman establishment: +This year the mob will cheer madly as Robert Morris is devoured. + +KK [Day 6, 11:37 A.M.]: The crackers here suggest that they crack into systems +with poor security BECAUSE the security is poor. Do more sophisticated +security precautions diminish the need to crack the system or increase it? + +ACID [Day 6, 1:20 P.M.]: If there was a system that we knew was uncrackable, +we wouldn't even try to crack it. On the other hand, if some organization +boasted that its system was impenetrable and we knew that was media hype, I +think it would be safe to say we'd have to "enlighten" them. + +EMMANUEL [Day 6, 2:49 P.M.]: Why do we insist on cracking systems? The more +people ask those kinds of questions, the more I want to get in! Forbid access +and the demand for access increases. For the most part, it's simply a mission +of exploration. In the words of the new captain of the starship Enterprise, +Jean-Luc Picard, "Let's see what's out there!" + +BARLOW [Day 6,4:34 P.M.]: Tell us, Acid, is there a system that you know to be +uncrackable to the point where everyone's given up? + +ACID [Day 6, 8:29 P.M.]: CICIMS is pretty tough. + +PHIBER OPTIK [Day 7, 2:36 P.M.]: Really? CICIMS is a system used by Bell +operating companies. The entire security system was changed after myself and a +friend must have been noticed in it. For the entire United States, there is +only one such system, located in Indiana. The new security scheme is flawless +in itself, and there is no chance of "social engineering" i.e., bullshitting +someone inside the system into telling you what the passwords are. The system +works something like this: You log on with the proper account and password; +then, depending on who you are, the system asks at random three of ten +questions that are unique to each user. But the system can be compromised by +entering forwarding instructions into the phone company's switch for that +exchange, thereby intercepting every phone call that comes in to the system +over a designated period of time and connecting the call to your computer. If +you are familiar with the security layout, you can emulate its appearance and +fool the caller into giving you the answers to his questions. Then you call +the system yourself and use those answers to get in. There are other ways of +doing it as well. + +BLUEFIRE [Day 7,11:53 P.M.]: I can't stand it! Who do you think pays for the +security that the telephone companies must maintain to fend off illegal use? I +bet it costs the ratepayers around $10 million for this little extravaganza. +The cracker circus isn't harmless at all, unless you don't mind paying for +other people's entertainment. Hackers who have contributed to the social +welfare should be recognized. But cracking is something else -- namely, fun at +someone else's expense -- and it ain't the folks who own the phone companies +who pay; it's us, me and you. + +BARLOW [Day 8, 7:35 A.M.]: I am becoming increasingly irritated at this idea +that you guys are exacting vengeance for the sin of openness. You seem to +argue that if a system is dumb enough to be open, it is your moral duty to +violate it. Does the fact that I've never locked my house -- even when I was +away for months at a time -- mean that someone should come in and teach me a +lesson? + +ACID [Day 8, 3:23 P.M.]: Barlow, you leave the door open to your house? Where +do you live? + +BARLOW [Day 8, 10:11 P.M.]: Acid, my house is at 372 North Franklin Street in +Pinedale, Wyoming. Heading north on Franklin, go about two blocks off the main +drag before you run into a hay meadow on the left. I'm the last house before +the field. The computer is always on. But do you really mean to imply what +you did with that question? Are you merely a sneak looking for easy places to +violate? You disappoint me, pal. For all your James Dean-on-Silicon, you're +just a punk. + +EMMANUEL [Day 9, 12:55 A.M.]: No offense, Barlow, but your house analogy +doesn't stand up, because your house is far less interesting than a Defense +Department computer. For the most part, hackers don't mess with individuals. +Maybe we feel sorry for them; maybe they're boring. Institutions are where +the action is, because they are compiling this mountain of data -- without +your consent. Hackers are not guardian angels, but if you think we're what's +wrong with the system, I'd say that's precisely what those in charge want you +to believe. By the way, you left out your zip code. It's 82941. + +BARLOW [Day 9, 8:34 A.M.]: Now that's more like it. There is an ethical +distinction between people and institutions. The law makes little distinction. +We pretend that institutions are somehow human because they are made of humans. +A large bureaucracy resembles a human about as much as a reef resembles a coral +polyp. To expect an institution to have a conscience is like expecting a horse +to have one. As with every organism, institutions are chiefly concerned with +their own physical integrity and survival. To say that they have some higher +purpose beyond their survival is to anthropomorphize them. You are right, +Emmanuel. The house analogy breaks down here. Individuals live in houses; +institutions live in mainframes. Institutions are functionally remorseless and +need to be checked. Since their blood is digital, we need to be in their +bloodstreams like an infection of humanity. I'm willing to extend limitless +trust to other human beings. In my experience they've never failed to deserve +it. But I have as much faith in institutions as they have in me. None. + +OPTIK [Day 9, 10:19 A.M.]: In other words, Mr. Barlow, you say something, +someone proves you wrong, and then you agree with him. I'm getting the feeling +that you don't exactly chisel your views in stone. + +HANK [Day 9, 11:18 A.M.]: Has Mr. Optik heard the phrase "thesis, antithesis, +synthesis"? + +BARLOW [Day 10, 10:48 A.M.]: Optik, I do change my mind a lot. Indeed, I +often find it occupied by numerous contradictions. The last time I believed in +absolutes, I was about your age. And there's not a damn thing wrong with +believing in absolutes at your age either. Continue to do so, however, and +you'll find yourself, at my age, carrying placards filled with nonsense and +dressing in rags. + +ADELAIDE [Day 10, 6:27 P.M.]: The flaw in this discussion is the distorted +image the media promote of the hacker as "whiz." The problem is that the one +who gets caught obviously isn't. I haven't seen a story yet on a true genius +hacker. Even Robert Morris was no whiz. The genius hackers are busy doing +constructive things or are so good no one's caught them yet. It takes talent +to break into something. Nobody calls subway graffiti artists geniuses for +figuring out how to break into the yard. There's a difference between genius +and ingenuity. + +BARLOW [Day 19, 9:48 P.M.]: Let me define my terms. Using hacker in a +midspectrum sense (with crackers on one end and Leonardo da Vinci on the +other), I think it does take a kind of genius to be a truly productive hacker. +I'm learning PASCAL now, and I am constantly amazed that people can string +those prolix recursions into something like PageMaker. It fills me with the +kind of awe I reserve for splendors such as the cathedral at Chartres. With +crackers like Acid and Optik, the issue is less intelligence than alienation. +Trade their modems for skateboards and only a slight conceptual shift would +occur. Yet I'm glad they're wedging open the cracks. Let a thousand worms +flourish. + +OPTIK [Day 10, 10:11 P.M.]: You have some pair of balls comparing my talent +with that of a skateboarder. Hmm... This was indeed boring, but nonetheless: +[Editor's note: At this point in the discussion, Optik -- apparently having +hacked into TRW's computer records -- posted a copy of Mr. Barlow's credit +history. In the interest of Mr. Barlow's privacy -- at least what's left of it +-- Harper's Magazine has not printed it.] I'm not showing off. Any fool +knowing the proper syntax and the proper passwords can look up credit history. +I just find your high-and-mighty attitude annoying and, yes, infantile. + +HOMEBOY [Day 10, 10:17 P.M.]: Key here is "any fool." + +ACID [Day 11, 1:37 P.M.]: For thirty-five dollars a year anyone can have +access to TRW and see his or her own credit history. Optik did it for free. +What's wrong with that? And why does TRW keep files on what color and religion +we are? If you didn't know that they kept such files, who would have found out +if it wasn't for a hacker? Barlow should be grateful that Optik has offered +his services to update him on his personal credit file. Of course, I'd hate to +see my credit history up in lights. But if you hadn't made our skins crawl, +your info would not have been posted. Everyone gets back at someone when he's +pissed; so do we. Only we do it differently. Are we punks? Yeah, I guess we +are. A punk is what someone who has been made to eat his words calls the guy +who fed them to him. + +**************************************************************************** + + A Brief History of Hacking + +September 1970 - John Draper takes as his alias the name of Captain Crunch +after he discovers that the toy whistle found in the cereal of the same name +perfectly simulates the tone necessary to make free phone calls. + +March 1975 - The Homebrew Computer Club, an early group of computer hackers, +holds its first meeting in Menlo Park, California. + +July 1976 - Homebrew members Steve Wozniak, twenty-six, and Steve Jobs, +twenty-one, working out of a garage, begin selling the first personal computer, +known as the Apple. + +June 1980 - In one week, errors in the computer system operating the U.S. +air-defense network cause two separate false reports of soviet missile +launches, each prompting an increased state of nuclear readiness. + +December 1982 - Sales of Apple personal computers top one billion dollars per +year. + +November 1984 - Steven Levy's book Hackers is published, popularizing the +concept of the "hacker ethic": that "access to computers, and anything that +might teach you something about the way the world works, should be unlimited +and total." The book inspires the first Hacker's Conference, held that month. + +January 1986 - The "Pakistani Brain" virus, created by a software distributor +in Lahore, Pakistan, infects IBM computers around the world, erasing data +files. + +June 1986 - The U.S. Office of Technology Assessment warns that massive, +cross-indexed government computer records have become a "de facto national data +base containing personal information on most Americans." + +March 1987 - William Fates, a Harvard dropout who founded Microsoft +Corporation, becomes a billionaire. + +November 1988 - More that 6,000 computers linked by the nationwide Internet +computer network are infected by a computer program known as a worm and are +crippled for two days. The worm is traced to Robert Morris Jr., a twenty-four- +year-old Cornell University graduate student. + +December 1988 - A federal grand jury charges Kevin Mitnick, twenty-five, with +stealing computer programs over telephone lines. Mitnick is held without bail +and forbidden access to any telephones without supervision. + +March 1989 - Three West German hackers are arrested for entering thirty +sensitive military computers using home computers and modems. The arrests +follow a three-year investigation by Clifford Stoll, an astronomer at the +Lawrence Berkeley Laboratory who began tracing the hackers after finding a +seventy-five-cent billing error in the lab's computer system. + +January 1990 - Robert Morris Jr. Goes on trial in Syracuse, New York, for +designing and releasing the Internet worm. Convicted, he faces up to five +years in prison and a $250,000 fine. + + +***************************************************************************** +* * +* Part 2: Hacking The Constitution * +* * +***************************************************************************** + +HARPER'S [Day 4, 9:00 A.M.]: Suppose that a mole inside the government +confirmed the existence of files on each of you, stored in the White House +computer system, PROFS. Would you have the right to hack into that system to +retrieve and expose the existence of such files? Could you do it? + +TENNEY [Day 4, 1:42 P.M.]: The proverbial question of whether the end +justifies the means. This doesn't have much to do with hacking. If the file +were a sheet of paper in a locked cabinet, the same question would apply. In +that case you could accomplish everything without technological hacking. +Consider the Pentagon Papers. + +EMMANUEL [Day 4, 3:55 P.M.]: Let's address the hypothetical. First, I need to +find out more about PROFS. Is it accessible from off site, and if so, how? +Should I update my 202-456 scan [a list of phone numbers in the White House's +exchange that connect incoming calls to a computer]? I have a listing for +every computer in that exchange, but the scan was done back in 1984. Is PROFS +a new system? Perhaps it's in a different exchange? Does anybody know how +many people have access to it? I'm also on fairly god terms with a White House +operator who owes me a favor. But I don't know what to ask for. Obviously, +I've already made up my mind about the right to examine this material. I don't +want to debate the ethics of it at this point. If you're with me, let's do +something about this. Otherwise, stay out of the way. There's hacking to be +done. + +ACID [Day 4, 5:24 P.M.]: Yes, I would try to break into the PROFS system. But +first I'd have someone in the public eye, with no ties to hacking, request the +info through the Freedom of Information Act. Then I'd hack in to verify the +information I received. + +DRAKE [Day 4, 9:13 P.M.]: Are there a lot of people involved in this +antihacker project? If so, the chances of social engineering data out of +people would be far higher than if it were a small, close-knit group. But yes, +the simple truth is, if the White House has a dial-up line, it can be hacked. + +EMMANUEL [Day 4, 11:27 P.M.]: The implication that a trust has been betrayed +on the part of the government is certainly enough to make me want to look a +little further. And I know I'm doing the right thing on behalf of others who +don't have my abilities. Most people I meet see me as an ally who can help +them stay ahead of an unfair system. That's what I intend to do here. I have +a small core of dedicated hackers who could help. One's specialty is the UNIX +system, another's is networks, and another's is phone systems. + +TENNEY [Day 5, 12:24 P.M.]: PROFS is an IBM message program that runs on an +operating system known as VM. VM systems usually have a fair number of holes, +wither to gain access or to gain full privileges. The CIA was working on, and +may have completed, a supposedly secure VM system. No ethics here, just facts. +But a prime question is to determine what system via what phone number. +Of course, the old inside job is easier. Just find someone who owes a favor or +convince an insider that it is a moral obligation to do this. + +BARLOW [Day 5, 2:46 P.M.]: This scenario needs to be addressed in four parts: +ethical, political, practical I (from the standpoint of the hack itself), and +practical II (disseminating the information without undue risk). + Ethical: Since World War II, we've been governed by a paramilitary + bureaucracy that believes freedom is too precious to be entrusted to the + people. These are the same folks who had to destroy the village in order + to save it. Thus the government has become a set of Chinese boxes. + Americans who believe in democracy have little choice but to shred the + barricades of secrecy at every opportunity. It isn't merely permissible + to hack PROFS. It is a moral obligation. + Political: In the struggle between control and liberty, one has to avoid + action that will drive either side to extreme behaviour. The basis of + terrorism, remember, is excess. If we hack PROFS, we must do it in a way + that doesn't become a pretext for hysterical responses that might + eventually include zero tolerance of personal computers. The answer is to + set up a system for entry and exit that never lets on we've been there. + Practical I: Hacking the system should be a trivial undertaking. + Practical II: Having retrieved the smoking gun, it must be made public in + such a way that the actual method of acquisition does not become public. + Consider Watergate: The prime leaker was somebody whose identity and + information-gathering technique is still unknown. So having obtained the + files, we turn them over to the Washington Post without revealing our own + identities or how we came by the files. + +EMMANUEL [Day 5, 9:51 P.M.]: PROFS is used for sending messages back and +forth. It's designed not to forget things. And it's used by people who are +not computer literate. The document we are looking for is likely an electronic +message. If we can find out who the recipient or sender is, we can take it +from there. Since these people frequently use the system to communicate, there +may be a way for them to dial into the White House from home. Finding that +number won't be difficult: frequent calls to a number local to the White House +and common to a few different people. Once I get the dial-up, I'll have to +look at whatever greeting I get to determine what kind of system it is. Then we +need to locate someone expert in the system to see if there are any built-in +back doors. If there aren't, I will social engineer my way into a working +account and then attempt to break out of the program and explore the entire +system. + +BRAND [Day 6, 10:06 A.M.]: I have two questions: do you believe in due process +as found in our Constitution? And do you believe that this "conspiracy" is so +serious that extraordinary measures need to be taken? If you believe in due +process, then you shouldn't hack into the system to defend our liberties. If +you don't believe in due process, you are an anarchist and potentially a +terrorist. The government is justified in taking extreme action to protect +itself and the rest of us from you. If you believe in the Constitution but +also that this threat is so extreme that patriots have a duty to intercede, +then you should seek one of the honest national officials who can legally +demand a copy of the document. If you believe that there is no sufficiently +honest politician and you steal and publish the documents, you are talking +about a revolution. + +ACID [Day 6, 1:30 P..]: This is getting too political. Who says that hacking +has to have a political side? Generalizing does nothing but give hackers a +false image. I couldn't care less about politics, and I hack. + +LEE [Day 6, 9:01 P.M.]: Sorry, Acid, but if you hack, what you do is +inherently political. Here goes: Political power is exercised by control of +information channels. Therefore, any action that changes the capability of +someone in power to control these channels is politically relevant. +Historically, the one in power has been not the strongest person but the one +who has convinced the goon squad to do his bidding. The goons give their power +to him, usually in exchange for free food, sex, and great uniforms. The +turning point of most successful revolutions is when the troops ignore the +orders coming from above and switch their allegiance. Information channels. +Politics. These days, the cracker represents a potential for making serious +political change if he coordinates with larger social and economic forces. +With out this coordination, the cracker is but a techno-bandit, sharpening his +weapon and chuckling about how someday... Revolutions often make good use of +bandits, and some of them move into high positions when they're successful. +but most of them are done away with. One cracker getting in won't do much +good. Working in coordination with others is another matter -- called +politics. + +JIMG [Day 7, 12:28 A.M.]: A thought: Because it has become so difficult to +keep secrets (thanks, in part, to crackers), and so expensive and +counterproductive (the trade-off in lost opportunities is too great), secrets +are becoming less worth protecting. Today, when secrets come out that would +have brought down governments in the past, "spin-control experts" shower the +media with so many lies that the truth is obscured despite being in plain +sight. It's the information equivalent of the Pentagon planto surround each +real missile with hundreds of fake ones, rendering radar useless. If hackers +managed to crack the White House system, a hue and cry would be raised -- not +about what the hackers found in the files but about what a threat hackers are +to this great democracy of ours. + +HARPER'S [Day 7, 9:00 A.M.]: Suppose you hacked the files from the White House +and a backlash erupted. Congressmen call for restrictions, arguing that the +computer is "property" susceptible to regulation and not an instrument of +"information" protected by the First Amendment. Can we craft a manifesto +setting forth your views on how the computer fits into the traditions of the +American Constitution? + +DAVE [Day 7, 5:30 P.M.]: If Congress ever passed laws that tried to define +what we do as "technology" (regulatable) and not "speech," I would become a +rebellious criminal immediately -- and as loud as Thomas Paine ever was. +Although computers are part "property" and part "premises" (which suggest a +need for privacy), they are supremely instruments of speech. I don't want any +congressional King Georges treading on my cursor. We must continue to have +absolute freedom of electronic speech! + +BARLOW [Day 7, 10:07 P.M.]: Even in a court guided by my favorite oxymoron, +Justice Rehnquist, this is an open-and-shut case. The computer is a printing +press. Period. The only hot-lead presses left in this country are either in +museums or being operated by poets in Vermont. The computer cannot fall under +the kind of regulation to which radio and TV have become subject, since +computer output is not broadcast. If these regulations amount to anything more +than a fart in the congressional maelstrom, then we might as well scrap the +whole Bill of Rights. What I am doing with my fingers is "speech" in the +clearest sense of the word. We don't need no stinking manifestos. + +JIMG [Day 8, 12:02 P.M.]: This type of congressional action is so clearly +unconstitutional that "law hackers" -- everyone from William Kunstler to Robert +Bork -- would be all over it. The whole idea runs so completely counter to our +laws that it's hard to get worked up about it. + +ADELAIDE [Day 8, 9:51 A.M.]: Not so fast. There used to be a right in the +Constitution called "freedom from unreasonable search and seizure," but, thanks +to recent Supreme Court decisions, your urine can be demanded by a lot of +people. I have no faith in the present Supreme Court to uphold any of my +rights of free speech. The complacent reaction here -- that whatever Congress +does will eventually be found unconstitutional -- is the same kind of +complacency that led to the current near-reversals of Roe v. Wade. + +JRC [Day 8, 10:05 A.M.]: I'd forgo the manifestos and official explanations +altogether: Fight brushfire wars against specific government incursions and +wait for the technology to metastasize. In a hundred years, people won't have +to be told about computers because they will have an instinctive understanding +of them. + +KK [Day 8, 2:14 P.M.]: Hackers are not sloganeers. They are doers, +take-things-in-handers. They are the opposite of philosophers: They don't wait +for language to catch up to them. Their arguments are their actions. You want +a manifesto? The Internet worm was a manifesto. It had more meaning and +symbolism than any revolutionary document you could write. To those in power +running the world's nervous system, it said: Wake up! To the underground of +hackers, crackers, chippers, techno-punks, it said: You have power; be careful. +To the mass of citizens who find computers taking over their telephone, their +TV, their toaster, and their house, it said: Welcome to Wonderland. + +BARLOW [Day 8, 10:51 P.M.]: Apart from the legal futility of fixing the dam +after it's been breached, I've never been comfortable with manifestos. They +are based on the ideologue's delusion about the simplicity, the +figure-out-ability, of the infinitely complex thing that is Life Among the +Humans. Manifestos take reductionism for a long ride off a short pier. +Sometimes the ride takes a very long time. Marx and Engels didn't actually +crash until last year. Manifestos fail because they are fixed and +consciousness isn't. I'm with JRC: Deal with incursions when we need to, on +our terms, like the guerrillas we are. To say that we can outmaneuver those +who are against us is like saying that honeybees move quicker than Congress. +The future is to the quick, not the respectable. + +RH [Day 8, 11:43 P.M.]: Who thinks computers can't be regulated? The +Electronic Communications Privacy Act of 1986 made it a crime to own "any +electronic, mechanical, or other device [whose design] renders it primarily +useful for the purpose of the surreptitious interception of wire, oral, or +electronic communication." Because of the way Congress defined "electronic +communication," one could argue that even a modem is a surreptitious +interception device (SID), banned by the ECPA and subject to confiscation. +It's not that Congress intended to ban modems; it was just sloppy drafting. +The courts will ultimately decide what devices are legal. Since it may not be +possible to draw a clear bright line between legal and illegal interception +devices, the grey area -- devices with both legitimate uses and illegitimate +uses -- may be subject to regulation. + +BARLOW [Day 9, 8:52 A.M.]: I admit with some chagrin that I'm not familiar +with the ECPA. It seems I've fallen on the wrong side of an old tautology: +Just because all saloon keepers are Democrats, it doesn't follow that all +Democrats are saloon keepers. By the same token, the fact that all printing +presses are computers hardly limits computers to that function. And one of +the other things computers are good at it surreptitous monitoring. Maybe +there's more reason for concern than I thought. Has any of this stuff been +tested in the courts yet? + +RH [Day 9, 10:06 P.M.]: My comments about surreptitous interception devices +are not based on any court cases, since there have not been any in this area +since the ECPA was enacted. It is a stretch of the imagination to think that +a judge would ever find a stock, off-the-shelf personal computer to be a +"surreptitous interception device." But a modem is getting a little closer to +the point where a creative prosecutor could make trouble for a cracker, with +fallout affecting many others. An important unknown is how the courts will +apply the word surreptitious. There's very little law, but taking it to mean +"by stealth; hidden from view; having its true purpose physically disguised," +I can spin some worrisome examples. I lobbied against the bill, pointing out +the defects. Congressional staffers admitted privately that there was a +problem, but they were in a rush to get the bill to the floor before Congress +adjourned. They said they could patch it later, but it is a pothole waiting +for a truck axle to rumble through. + +JIMG [Day 10, 8:55 A.M.]: That's sobering information, RH. Yet I still think +that this law, if interpreted the way you suggest, would be found +unconstitutional, even by courts dominated by Reagan appointees. Also, the +economic cost of prohibiting modems, or even restricting their use, would so +outweigh conceivable benefits that the law would never go through. Finally, +restricting modems would have no effect on the phreaks but would simply manage +to slow everybody else down. If modems are outlawed, only outlaws will have +modems. + +RH [Day 10, 1:52 P.M.]: We're already past the time when one would wrap +hacking in the First Amendment. There's a traditional distinction between +words -- expressions of opinions, beliefs, and information -- and deeds. You +can shout "Revolution!" from the rooftops all you want, and the post office +will obligingly deliver your recipes for nitroglycerin. But acting on that +information exposes you to criminal prosecution. The philosophical problem +posed by hacking is that computer programs transcend this distinction: They +are pure language that dictates action when read by the device being +addressed. In that sense, a program is very different from a novel, a play, +or even a recipe: Actions result automatically from the machine reading the +words. A computer has no independent moral judgement, no sense of +responsibility. Not yet, anyway. As we program and automate more of our +lives, we undoubtedly will deal with more laws: limiting what the public can +know, restricting devices that can execute certain instructions, and +criminalizing the possession of "harmful" programs with "no redeeming social +value." Blurring the distinction between language and action, as computer +programming does, could eventually undermine the First Amendment or at least +force society to limit its application. That's a very high price to pay, even +for all the good things that computers make possible. + +HOMEBOY [Day 10, 11:03 P.M.]: HACKING IS ART. CRACKING IS REVOLUTION. All +else is noise. Cracks in the firmament are by nature threatening. Taking a +crowbar to them is revolution. + +****************************************************************************** From 2419596e3a63e66c661cdceb3bcf540ef41246dd Mon Sep 17 00:00:00 2001 From: DIDIx13 Date: Mon, 13 Nov 2017 18:34:35 +0100 Subject: [PATCH 098/112] Minus change (I'm actually bored and I'm looking at hackthis source to find some issues with my little knowledge) --- html/example.htaccess | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html/example.htaccess b/html/example.htaccess index dc96ddb..fb539a8 100644 --- a/html/example.htaccess +++ b/html/example.htaccess @@ -156,7 +156,7 @@ AddDefaultCharset utf-8 # Block access to directories without a default document. # Usually you should leave this uncommented because you shouldn't allow anyone -# to surf through every directory on your server (which may includes rather +# to surf through every directory on your server (which may include rather # private places like the CMS's directories). @@ -181,7 +181,7 @@ AddDefaultCharset utf-8 # danger when anyone has access to them. - Order allow,deny + Order allow, deny Deny from all Satisfy All From fc08268597703dca58a8e6bae9c4969e87ed3a9c Mon Sep 17 00:00:00 2001 From: DIDIx13 Date: Mon, 13 Nov 2017 18:45:14 +0100 Subject: [PATCH 099/112] Update more.php Align ul list --- html/more.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/html/more.php b/html/more.php index 0253237..56078d8 100644 --- a/html/more.php +++ b/html/more.php @@ -8,8 +8,8 @@ ?>

      More

        -
      • IRC
        -     - Stats +
      • IRC
        + -     Stats
      • user->loggedIn): @@ -30,4 +30,4 @@
      \ No newline at end of file +?> From 6f6d6721f8326a7ab03f03967aeaaddb87af9d5f Mon Sep 17 00:00:00 2001 From: DIDIx13 Date: Mon, 13 Nov 2017 18:50:09 +0100 Subject: [PATCH 100/112] Update git.php Some extended description --- html/git.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html/git.php b/html/git.php index 456b8a2..192454c 100644 --- a/html/git.php +++ b/html/git.php @@ -11,8 +11,8 @@ HackThis

      Get involved

      - Found a bug? Wish the site had feature x? The source code for all HackThis!! projects can be found on GitHub. We encourage you to fork the code and see if you can implement the fix/feature yourself. If you do develop something that you think would be beneficial, then create pull request and we can include it in the site! All users that submit an approved pull request receive a contributor medal, however small the change may be.

      - Not a developer? There are a lot of other changes that you could submit, for example spelling and grammatical fixes. We are grateful for even the smallest contribution. + Found a bug? Wish the site had feature x? The source code for all HackThis!! projects can be found on GitHub. We encourage you to fork the code and see if you can implement the fix/feature yourself. If you do develop something that you think would be beneficial, then create pull request and we can include it on the site! All users that submit an approved pull request receive a contributor medal, however small the change may be.

      + Not a developer? There are a lot of other changes that you could submit, for example, spelling and grammatical fixes. We are grateful for even the smallest contribution.


      Version History

      From bee0b193f9e2844352aeba994926a3cf22a2e1b8 Mon Sep 17 00:00:00 2001 From: DIDIx13 Date: Mon, 13 Nov 2017 18:51:22 +0100 Subject: [PATCH 101/112] Update version_history.md Minus error --- files/cache/version_history.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/cache/version_history.md b/files/cache/version_history.md index 797b11d..add71fe 100644 --- a/files/cache/version_history.md +++ b/files/cache/version_history.md @@ -13,7 +13,7 @@ * Added WeChall API pages ## 08-06-2014 -* Fixed gramatical error in privacy statement - [sabretooth](/user/sabretooth) +* Fixed grammatical error in privacy statement - [sabretooth](/user/sabretooth) ## 09-05-2014 * Added H3 tag to BBCode From 4b8d753f7470bdaa8759091d77d5ef74b2ab9935 Mon Sep 17 00:00:00 2001 From: DIDIx13 Date: Tue, 5 Dec 2017 20:29:47 +0100 Subject: [PATCH 102/112] Update terms.php --- html/terms.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/terms.php b/html/terms.php index f6b14e6..d5ebb2b 100644 --- a/html/terms.php +++ b/html/terms.php @@ -38,7 +38,7 @@

      3. Changes to the Terms.


      - From time to time, HackThis!! may change, remove, add to or otherwise modify the Terms, and reserves the right to do so in its discretion. We encourage you to periodically review the Terms. All new and/or amended Terms take effect immediately. Notwithstanding the foregoing, (i) no modification to the Terms will apply to any dispute between you and HackThis!! that arose prior to the effective date of any modification and (ii) if you do not agree with any modification to the Terms, you may terminate this agreement by ceasing use of the Websites and Services. Your continued use of any Website or Service after new and/or revised Terms are effective indicate that you have read, understood and agreed to those Terms. + From time to time, HackThis!! may change, remove, add to or otherwise modify the Terms, and reserves the right to do so in its discretion. We encourage you to periodically review the Terms. All new and/or amended Terms take effect immediately. Notwithstanding the foregoing, (i) no modification to the Terms will apply to any dispute between you and HackThis!! that arose prior to the effective date of any modification and (ii) if you do not agree with any modification to the Terms, you may terminate this agreement by ceasing use of the Websites and Services. Your continued use of any Website or Service after new and/or revised Terms are effective to indicate that you have read, understood and agreed to those Terms.

      4. Provision of the Websites and Services Generally.


      From 44591653abc2d15df1f6d6f0bcd4aae811ded4ef Mon Sep 17 00:00:00 2001 From: DIDIx13 Date: Mon, 26 Feb 2018 15:29:14 +0100 Subject: [PATCH 103/112] Fixed Grammar and corrected spelling --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0157687..f3abdf6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This repository contains the majority of the code for security challenge site ht You can set up the site on your own local machine and help the development. The specific instructions differ depending on your operating system. -Following are instructions for Windows and Ubuntu. In the end you can find a general description of the process for any other OS. +Following are instructions for Windows and Ubuntu. In the end, you can find a general description of the process for any other OS. ### Ubuntu Installation @@ -15,7 +15,7 @@ Following are instructions for Windows and Ubuntu. In the end you can find a gen git clone http://github.com/HackThis/hackthis.co.uk ``` -2. Run the the installation script by using the following command +2. Run the installation script by using the following command ``` sudo ./install_hackthis_ubuntu.sh ``` @@ -53,7 +53,7 @@ Following are instructions for Windows and Ubuntu. In the end you can find a gen Follow the instructions of the script until it's done. If an error occurs, the script will let you know what to do. Fix what's wrong and re-run the script until it ends successfully. -6. Open your broswer and navigate to +6. Open your browser and navigate to ``` http://localhost/hackthis/?generate @@ -113,13 +113,13 @@ Following are instructions for Windows and Ubuntu. In the end you can find a gen nano html/.htaccess ``` -8. Create and configure config file. Change path to the path of your hackthis.co.uk directory, without trailing slash. Next set MySQL credentials to match those used in setup, database is `hackthis`. Facebook, twitter and lastfm API keys are not required but some features will not work correctly. +8. Create and configure config file. Change path to the path of your hackthis.co.uk directory, without trailing slash. Next set MySQL credentials to match those used in setup, database is `hackthis`. Facebook, Twitter and Lastfm API keys are not required but some features will not work correctly. ``` cp files/example.config.php files/config.php nano files/config.php ``` -9. Create and set new folder privilages +9. Create and set new folder privileges ``` mkdir html/files/css/min mkdir html/files/css/min/light From 7afedb6707bd2105731778f7912d81599228dd7e Mon Sep 17 00:00:00 2001 From: DIDIx13 Date: Mon, 26 Feb 2018 15:35:55 +0100 Subject: [PATCH 104/112] Update robots.txt Indexing bots who are way too aggressive can slow down a lot specific target and can even crash. Yahoo Pipes 1.0 is for feeds not for Hackthis and it's still slow down the website. (Minor) --- html/robots.txt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/html/robots.txt b/html/robots.txt index 019c9eb..55aa799 100644 --- a/html/robots.txt +++ b/html/robots.txt @@ -10,4 +10,13 @@ Disallow: /ctf/8/php/* User-agent: Mediapartners-Google Disallow: -Sitemap: https://www.hackthis.co.uk/sitemap.xml \ No newline at end of file +User-agent: Yahoo Pipes 1.0 +Disallow: / + +User-agent: KSCrawler +Disallow: / + +User-agent: Spinn3r +Disallow: / + +Sitemap: https://www.hackthis.co.uk/sitemap.xml From 4bd0fdd94d5401fe0a91daa04293ea52be0b13b2 Mon Sep 17 00:00:00 2001 From: Darwin Ameli Date: Fri, 16 Mar 2018 13:47:28 +0100 Subject: [PATCH 105/112] Update footer.php with link to Status --- files/footer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/files/footer.php b/files/footer.php index 1d44826..acbf664 100644 --- a/files/footer.php +++ b/files/footer.php @@ -29,6 +29,7 @@
    • Articles
    • Forum
    • Contact Us
    • +
    • Status
    From 075dc4f207da9a6de9a425abf8eb767927a12ff6 Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Tue, 15 May 2018 09:45:50 +0000 Subject: [PATCH 106/112] WYSIWYG spoiler button --- files/cache/version_history.md | 125 ++++++++++++++++++++++++++++++++- files/elements/wysiwyg.php | 2 +- 2 files changed, 125 insertions(+), 2 deletions(-) mode change 100644 => 100755 files/cache/version_history.md diff --git a/files/cache/version_history.md b/files/cache/version_history.md old mode 100644 new mode 100755 index c5ad416..0987d6f --- a/files/cache/version_history.md +++ b/files/cache/version_history.md @@ -1,9 +1,125 @@ +## 09-07-2016 +* Fixed missing userbar image in user settings + +## 06-07-2016 +* Added Real level 7 + +## 30-06-2016 +* Fixed Intermediate level 5 +* Added spoiler tag to WYSIWYG editor - [L3gand](/user/L3gand) + +## 16-06-2015 +* Disabled users from logging in with old password hashes, forcing password reset + +## 15-06-2015 +* Added online indicators to levels that rely on external services +* Stricted forum checks added for newly registered users + +## 12-06-2015 +* Added two-factor authentication using Google Authenticator - [CygnusH33L](/user/CygnusH33L) + +## 07-06-2015 +* Spelling mistake in forum email - [MrCyph3r](/user/MrCyph3r) + +## 06-06-2015 +* Main authentication method switched to LDAP + +## 08-05-2015 +* Error in privacy document - [MrCyph3r](/user/MrCyph3r) +* Spelling mistake in Terms - [Rex-Mundi](/user/Rex_Mundi) + +## 03-05-2015 +* Hide latest news article from homepage after 2 weeks + +## 01-05-2015 +* Changed user profile history links for forum posts to go to the correct page + +## 09-03-2015 +* Tweaked IRC stats to format numbers and dates + +## 15-02-2015 +* Added basic forum stats + +## 19-01-2015 +* Grammatical fix - [tl0tr](/user/tl0tr) + +## 21-11-2014 +* Allow transition from dropdown message creator to full view - [singleton](/user/singleton) + +## 25-09-2014 +* Fixed XSS on profile - [darkl33ch](/user/darkl33ch) + +## 20-09-2014 +* Added Crypt 9 +* Improved forum flagging + +## 11-09-2014 +* Notifications are automatically marked as read when viewing thread + +## 09-09-2014 +* Fixed profile level details +* Fixed access to solutions forum sections +* Fixed levels dropdown menu + +## 07-09-2014 +* Changed level layout + +## 24-08-2014 +* Switched emails to Mandrell + +## 22-08-2014 +* Fixed Basic+ forum links on level pages + +## 16-08-2014 +* Added privacy controls to show/hide users in online and scoreboard lists +* Added website field to user profile +* Converted more pages to be rendered by Twig + +## 05-08-2014 +* Added statuses to contact tickets + +## 04-08-2014 +* Trigger added to handle changes in medal rewards across all users + +## 30-07-2014 +* Added regex solutions to levels + +## 29-07-2014 +* Allow new threads to be created outside of leaf nodes +* Fixed solutions showing up to non-authorized users + +## 28-07-2014 +* Basic+ Level 6 added + +## 26-07-2014 +* Auto-login added + +## 24-07-2014 +* Solved spacing in BBCode blocks +* Fixed double spacing in BBCode code blocks - [DJDavid98](/user/djdavid98) +* Changed styling of home forum widget, added section details and created date +* Fixed missing breadcrumbs on forum thread list +* Added restricted solution discussion forum sections + +## 23-07-2014 +* Added the year to short dates not for the current year - [DJDavid98](/user/djdavid98) + +## 21-07-2014 +* Crypt Level 8 added - [sabretooth](/user/sabretooth) + +## 19-07-2014 +* Fixed bug in account deletion + +## 02-07-2014 +* Karma controls are now still accessible when a post is hidden + ## 29-06-2014 * Added min length and filtered special characters in AJAX search - [verath](/user/verath) * Made WeChall user scores depend only on solved levels - [dloser](/user/dloser) * Navbar fix when no levels added - [dloser](/user/dloser) ## 22-06-2014 +* Real Level 6 added * Repaired friend removal from settings menu - [dloser](/user/dloser) ## 10-06-2014 @@ -105,4 +221,11 @@ * Included a detailed list of changes for each new version * Added slimdown, a markdown parser * Added contributor medal -* Uploaded wider background image to match new site width +* Uploaded wider background image to match new site width + + + +# Vulnerability disclosures +* [Pseudonym](/user/pseudonym) - 12/01/2013 - XSS, search results for forum title +* [Pseudonym](/user/pseudonym) - 05/01/2013 - PM subject showing up over multiple lines in navigation using script comments +* [Pseudonym](/user/pseudonym) - 05/01/2013 - Forum title, showing up in latest and feed diff --git a/files/elements/wysiwyg.php b/files/elements/wysiwyg.php index ecc729c..ffc0c19 100644 --- a/files/elements/wysiwyg.php +++ b/files/elements/wysiwyg.php @@ -25,7 +25,7 @@ ?>
  • - +
  • From 89990c84b147696e8c7f8d4a94826f7af98a9da3 Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Tue, 15 May 2018 09:48:34 +0000 Subject: [PATCH 107/112] Updated CSP rules --- files/init.php | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/files/init.php b/files/init.php index 21253c2..21cd5f9 100644 --- a/files/init.php +++ b/files/init.php @@ -1,4 +1,12 @@ custom_js, 'ajax_csrf_token.js'); array_push($minifier->custom_js, 'notifications.js'); - array_push($minifier->custom_js, 'chat.js'); + // array_push($minifier->custom_js, 'chat.js'); array_push($minifier->custom_js, 'autosuggest.js'); } else { array_push($minifier->custom_js, 'guest.js'); From 76b5e667a5da2b1ab52af00da3918c6ae7ab6fbd Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Tue, 15 May 2018 09:49:08 +0000 Subject: [PATCH 108/112] Removed article comments --- html/articles/view.php | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/html/articles/view.php b/html/articles/view.php index 194a901..222dcbb 100644 --- a/html/articles/view.php +++ b/html/articles/view.php @@ -102,7 +102,7 @@
    - + user->admin_pub_priv): ?> @@ -147,13 +147,6 @@ if (count($matches[0])): ?>
    -cat_id == 6): -?> -
    -

    Contents

      $article->id,"title"=>$article->title,"count"=>$article->comments); - include('elements/comments.php'); - } +// if (!$myArticle && (!isset($_GET['view']) || $_GET['view'] != 'app')) { +// $comments = array("id"=>$article->id,"title"=>$article->title,"count"=>$article->comments); +// include('elements/comments.php'); +// } endif; ?>
    From 79687d609f007c798fb47b7bf98d39cd5295c51d Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Tue, 15 May 2018 09:50:04 +0000 Subject: [PATCH 109/112] Minor tweaks --- html/conduct.php | 2 +- html/faq.php | 6 +++--- html/forum/view.php | 4 ++-- html/privacy.php | 2 +- html/terms.php | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/html/conduct.php b/html/conduct.php index 56353f8..2c4b5cc 100644 --- a/html/conduct.php +++ b/html/conduct.php @@ -19,7 +19,7 @@

    Code of Conduct

    September 7, 2014 - +
    diff --git a/html/faq.php b/html/faq.php index e2170f6..845439d 100644 --- a/html/faq.php +++ b/html/faq.php @@ -24,7 +24,7 @@

    Forum

    How do I start a thread in the forum‭?

    - A new thread can be only started under a sub-section.‭ ‬If you go on the forum,‭ ‬or even under a section,‭ ‬you will see that the button‭ “‬New thread‭” ‬is not active and if you try to click on it,‭ ‬a message will appear saying that you can only start a new thread after you chose a sub-section.‭ + A new thread can only be started under a sub-section.‭ ‬If you go to the forum,‭ ‬or even under a section,‭ ‬you will see that the button‭ “‬New thread‭” ‬is not active and if you try to click on it,‭ ‬a message will appear saying that you can only start a new thread after you chose a sub-section.‭

    @@ -32,11 +32,11 @@

    What is karma?

    - Karma is a way of rating users posts within the forum. The karma rating is displayed as a number in the top of left of each forum post. Users with the bronze Karma medal are allowed to upvote posts and only users with the silver Karma medal can down vote. You earn the medals by completing levels and being active within the forum. Posts with karma of -3 or less are automatically hidden although this isn't the main role of the karma system, see reporting posts below. + Karma is a way to rate users posts within the forum. The karma rating is displayed as a number in the top of left of each forum post. Users with the bronze Karma medal are allowed to upvote posts and only users with the silver Karma medal can down vote. You earn the medals by completing levels and being active within the forum. Posts with karma of -3 or less are automatically hidden although this isn't the main role of the karma system, see reporting posts below.

    How do I control which threads I get notifications for?

    - You have the ability to watch and unwatch any thread in the forum. At the top of each thread your current preference is displayed and can be changed simply by clicking the button. By posting in a thread you automatically start watching that thread. + You have the ability to watch and unwatch any thread in the forum. At the top of each thread your current preference is displayed and can be simply changed by clicking the button. By posting in a thread you automatically start watching that thread.

    How do I report bad content within the forum?

    diff --git a/html/forum/view.php b/html/forum/view.php index 0087af8..720f13e 100644 --- a/html/forum/view.php +++ b/html/forum/view.php @@ -110,7 +110,7 @@ closed && $thread->question->user_id === $app->user->uid): ?> - Close + Close closed && $thread->question->user_id === $app->user->uid) { - $app->utils->message("Is one of these posts the answer to your question? If so click here to close thread.
    After closing a thread no more posts will be accepted.", 'info'); + $app->utils->message("Is one of these posts the answer to your question? If so click here to close thread.
    After closing a thread no more posts will be accepted.", 'info'); } ?>

      diff --git a/html/privacy.php b/html/privacy.php index 4b17954..248f6ec 100644 --- a/html/privacy.php +++ b/html/privacy.php @@ -19,7 +19,7 @@

      Privacy Policy

      June 8, 2014 - +
    diff --git a/html/terms.php b/html/terms.php index dc986d6..f6b14e6 100644 --- a/html/terms.php +++ b/html/terms.php @@ -19,7 +19,7 @@

    Terms of Use

    April 1, 2014 - +
    From 2313411c08e6d7508cbcf4e4ff0328a59e4b643a Mon Sep 17 00:00:00 2001 From: flabbyrabbit Date: Tue, 15 May 2018 14:38:27 +0000 Subject: [PATCH 110/112] Fixed syntax error --- files/elements/levels/level.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/elements/levels/level.php b/files/elements/levels/level.php index e73f872..d30e1ee 100644 --- a/files/elements/levels/level.php +++ b/files/elements/levels/level.php @@ -26,7 +26,7 @@ method)?'method="'.strtoupper($form->method).'"':'';?>>
    fields AS $field): ?> - + ' autocomplete="off" name)?"id='{$field->name}' name='{$field->name}'":'';?>>
    From 5870b28ac93aa1e43f30ab0241f334503223073d Mon Sep 17 00:00:00 2001 From: r4v463 Date: Wed, 16 May 2018 17:29:57 +0200 Subject: [PATCH 111/112] Apache does not allow whitespaces in Order directive --- html/example.htaccess | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/example.htaccess b/html/example.htaccess index fb539a8..3711fc4 100644 --- a/html/example.htaccess +++ b/html/example.htaccess @@ -181,7 +181,7 @@ AddDefaultCharset utf-8 # danger when anyone has access to them. - Order allow, deny + Order allow,deny Deny from all Satisfy All From 9bdd38d9f124b393b2852a160517afb019e42d3b Mon Sep 17 00:00:00 2001 From: sekortest <36673250+sekortest@users.noreply.github.com> Date: Fri, 31 Aug 2018 16:52:42 +0300 Subject: [PATCH 112/112] Fix error when password change POST:newpassword2 never used --- html/settings/account.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html/settings/account.php b/html/settings/account.php index 83e1d3d..1253266 100644 --- a/html/settings/account.php +++ b/html/settings/account.php @@ -6,7 +6,7 @@ $app->page->title = 'Settings - Account'; if (isset($_GET['password'])) { - $changed = $app->user->changePassword($_POST['newpassword1'], $_POST['newpassword1']); + $changed = $app->user->changePassword($_POST['newpassword1'], $_POST['newpassword2']); } else if (isset($_GET['delete']) && isset($_POST['delete']) && isset($_POST['token'])) { $status = $app->user->delete($_POST['delete'], $_POST['token']); @@ -121,4 +121,4 @@ \ No newline at end of file +?>