diff --git a/ci/apiv2/HACKING.md b/ci/apiv2/HACKING.md index 7fce5a5ce..079b3e1a5 100644 --- a/ci/apiv2/HACKING.md +++ b/ci/apiv2/HACKING.md @@ -14,7 +14,13 @@ curl --header "Content-Type: application/json" -X GET --header "Authorization: B Access database: ``` -mysql -u $HASHTOPOLIS_DB_USER -p $HASHTOPOLIS_DB_PASS -h $HASHTOPOLIS_DB_HOST -D $HASHTOPOLIS_DB_DATABASE +mysql -u $HASHTOPOLIS_DB_USER -p$HASHTOPOLIS_DB_PASS -h $HASHTOPOLIS_DB_HOST -D $HASHTOPOLIS_DB_DATABASE +``` + +Enable query logging: +``` +docker exec $(docker ps -aqf "ancestor=mysql:8.0") mysql -u root -phashtopolis -e "SET global log_output = 'FILE'; SET global general_log_file='/tmp/mysql_all.log'; SET global general_log = 1;" +docker exec $(docker ps -aqf "ancestor=mysql:8.0") tail -f /tmp/mysql_all.log ``` ### paper flipchart scribbles diff --git a/ci/apiv2/generate_dummy_data.py b/ci/apiv2/generate_dummy_data.py new file mode 100644 index 000000000..4677b306b --- /dev/null +++ b/ci/apiv2/generate_dummy_data.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +""" +Generate dummy data for development/debugging purposes +""" +from utils import do_create_hashlist + + +def generate_dummy_data(): + for _ in range(1000): + # TODO: Generate unique hashlists + do_create_hashlist() + + +# TODO: Generate different objects like users/tasks/crackerbinaries/etc +if __name__ == '__main__': + # TODO: Use seed to generate an predictable 'random' test dataset + generate_dummy_data() diff --git a/ci/apiv2/hashtopolis.py b/ci/apiv2/hashtopolis.py index 627f93303..600fa7abb 100644 --- a/ci/apiv2/hashtopolis.py +++ b/ci/apiv2/hashtopolis.py @@ -683,3 +683,56 @@ def purge_task(self, task): 'taskId': task.id, } return self._helper_request("purgeTask", payload) + + def export_cracked_hashes(self, hashlist): + payload = { + 'hashlistId': hashlist.id, + } + response = self._helper_request("exportCrackedHashes", payload) + return File(**response['data']) + + def export_left_hashes(self, hashlist): + payload = { + 'hashlistId': hashlist.id, + } + response = self._helper_request("exportLeftHashes", payload) + return File(**response['data']) + + def export_wordlist(self, hashlist): + payload = { + 'hashlistId': hashlist.id, + } + response = self._helper_request("exportWordlist", payload) + return File(**response['data']) + + def import_cracked_hashes(self, hashlist, source_data, separator): + payload = { + 'hashlistId': hashlist.id, + 'sourceData': source_data, + 'separator': separator, + } + response = self._helper_request("importCrackedHashes", payload) + return response['data'] + + + def recount_file_lines(self, file): + payload = { + 'fileId': file.id, + } + response = self._helper_request("recountFileLines", payload) + return File(**response['data']) + + def unassign_agent(self, agent): + payload = { + 'agentId': agent.id, + } + response = self._helper_request("unassignAgent", payload) + return response['data'] + + def assign_agent(self, agent, task): + payload = { + 'agentId': agent.id, + 'taskId': task.id, + } + response = self._helper_request("assignAgent", payload) + return response['data'] diff --git a/ci/apiv2/test_agent.py b/ci/apiv2/test_agent.py index e8af4c02d..1aae2c315 100644 --- a/ci/apiv2/test_agent.py +++ b/ci/apiv2/test_agent.py @@ -1,4 +1,5 @@ -from hashtopolis import Agent +from test_task import TaskTest +from hashtopolis import Agent, Helper from hashtopolis import HashtopolisError from utils import BaseTest @@ -28,3 +29,21 @@ def test_expandables(self): model_obj = self.create_test_object() expandables = ['accessGroups', 'agentstats'] self._test_expandables(model_obj, expandables) + + def test_assign_unassign_agent(self): + agent_obj = self.create_test_object() + + task_test = TaskTest() + task_obj = task_test.create_test_object(delete=True) + + helper = Helper() + + result = helper.assign_agent(agent=agent_obj, task=task_obj) + + self.assertEqual(result['assign'], 'success') + + result = helper.unassign_agent(agent=agent_obj) + + self.assertEqual(result['unassign'], 'success') + + task_test.tearDown() diff --git a/ci/apiv2/test_file.py b/ci/apiv2/test_file.py index 3de09f18a..2262ed6cd 100644 --- a/ci/apiv2/test_file.py +++ b/ci/apiv2/test_file.py @@ -1,4 +1,4 @@ -from hashtopolis import File +from hashtopolis import File, Helper from utils import BaseTest @@ -28,3 +28,14 @@ def test_expandables(self): def test_create_binary(self): model_obj = self.create_test_object(compress=True) self._test_create(model_obj) + + def test_recount_wordlist(self): + # Note: After the object creation, the line count is already updated, but afterward it is immutable on the API. + # There the test just check that the API function is callable and returns the file, but the count is + # already the same beforehand. + model_obj = self.create_test_object() + + helper = Helper() + file = helper.recount_file_lines(file=model_obj) + + self.assertEqual(file.lineCount, 3) diff --git a/ci/apiv2/test_filter_and_ordering.py b/ci/apiv2/test_filter_and_ordering.py index ebd1217c5..5597e35a5 100644 --- a/ci/apiv2/test_filter_and_ordering.py +++ b/ci/apiv2/test_filter_and_ordering.py @@ -24,7 +24,6 @@ def test_filter(self): [x.id for x in model_objs], [x.id for x in objs]) - @pytest.mark.skip(reason="Broken due to bug https://github.com/hashtopolis/server/issues/968") def test_filter__contains(self): search_token = "SHA" objs = HashType.objects.filter(description__contains=search_token) @@ -33,7 +32,6 @@ def test_filter__contains(self): [x.id for x in all_objs if search_token in x.description], [x.id for x in objs]) - @pytest.mark.skip(reason="Broken due to bug https://github.com/hashtopolis/server/issues/968") def test_filter__endswith(self): search_token = 'sha512' objs = HashType.objects.filter(description__endswith=search_token) @@ -108,7 +106,6 @@ def test_filter__ne(self): [x.id for x in all_objs if x.hashTypeId != 100], [x.id for x in objs]) - @pytest.mark.skip(reason="Broken due to bug https://github.com/hashtopolis/server/issues/968") def test_filter__startswith(self): objs = HashType.objects.filter(description__startswith="net") all_objs = HashType.objects.all() diff --git a/ci/apiv2/test_hashlist.py b/ci/apiv2/test_hashlist.py index a2d5225fe..5ddd36d1a 100644 --- a/ci/apiv2/test_hashlist.py +++ b/ci/apiv2/test_hashlist.py @@ -1,4 +1,4 @@ -from hashtopolis import Hashlist, Helper +from hashtopolis import Hashlist, Helper, File from utils import BaseTest @@ -38,6 +38,104 @@ def test_create_alternative_hashtype(self): model_obj = self.create_test_object(file_id='003') self._test_create(model_obj) + def test_export_cracked_hashes(self): + model_obj = self.create_test_object(file_id='001') + + helper = Helper() + file = helper.export_cracked_hashes(model_obj) + + obj = File.objects.get(fileId=file.id) + self.assertEqual(int(file.id), obj.id) + self.assertIn('Pre-cracked_', obj.filename) + + self.delete_after_test(obj) + + def test_export_left_hashes(self): + model_obj = self.create_test_object(file_id='001') + + helper = Helper() + file = helper.export_left_hashes(model_obj) + + obj = File.objects.get(fileId=file.id) + self.assertEqual(int(file.id), obj.id) + self.assertIn('Leftlist_', obj.filename) + + self.delete_after_test(obj) + + def test_export_wordlist(self): + model_obj = self.create_test_object(file_id='001') + + cracked = "cc03e747a6afbbcbf8be7668acfebee5:test123" + + helper = Helper() + helper.import_cracked_hashes(model_obj, cracked, ':') + + file = helper.export_wordlist(model_obj) + + obj = File.objects.get(fileId=file.id) + self.assertEqual(int(file.id), obj.id) + self.assertIn('Wordlist_', obj.filename) + + self.delete_after_test(obj) + + def test_import_cracked_hashes(self): + model_obj = self.create_test_object(file_id='001') + + cracked = "cc03e747a6afbbcbf8be7668acfebee5:test123" + + helper = Helper() + result = helper.import_cracked_hashes(model_obj, cracked, ':') + + self.assertEqual(result['totalLines'], 1) + self.assertEqual(result['newCracked'], 1) + + obj = Hashlist.objects.get(hashlistId=model_obj.id) + self.assertEqual(obj.cracked, 1) + + def test_import_cracked_hashes_invalid(self): + model_obj = self.create_test_object(file_id='001') + + cracked = "cc03e747a6afbbcbf8be7668acfebee5__test123" + + helper = Helper() + result = helper.import_cracked_hashes(model_obj, cracked, ':') + + self.assertEqual(result['totalLines'], 1) + self.assertEqual(result['invalid'], 1) + + obj = Hashlist.objects.get(hashlistId=model_obj.id) + self.assertEqual(obj.cracked, 0) + + def test_import_cracked_hashes_notfound(self): + model_obj = self.create_test_object(file_id='001') + + cracked = "ffffffffffffffffffffffffffffffff:test123" + + helper = Helper() + result = helper.import_cracked_hashes(model_obj, cracked, ':') + + self.assertEqual(result['totalLines'], 1) + self.assertEqual(result['notFound'], 1) + + obj = Hashlist.objects.get(hashlistId=model_obj.id) + self.assertEqual(obj.cracked, 0) + + def test_import_cracked_hashes_already_cracked(self): + model_obj = self.create_test_object(file_id='001') + + cracked = "cc03e747a6afbbcbf8be7668acfebee5:test123" + + helper = Helper() + helper.import_cracked_hashes(model_obj, cracked, ':') + + result = helper.import_cracked_hashes(model_obj, cracked, ':') + + self.assertEqual(result['totalLines'], 1) + self.assertEqual(result['alreadyCracked'], 1) + + obj = Hashlist.objects.get(hashlistId=model_obj.id) + self.assertEqual(obj.cracked, 1) + def test_helper_create_superhashlist(self): hashlists = [self.create_test_object() for _ in range(2)] diff --git a/ci/apiv2/test_task.py b/ci/apiv2/test_task.py index 09c99f6d3..ab3b9fb96 100644 --- a/ci/apiv2/test_task.py +++ b/ci/apiv2/test_task.py @@ -1,4 +1,4 @@ -from hashtopolis import Task +from hashtopolis import Task, TaskWrapper from utils import BaseTest @@ -75,3 +75,27 @@ def test_task_with_file(self): task = self.create_task(hashlist, extra_payload=extra_payload) obj = Task.objects.get(pk=task.id, expand='files') self.assertListEqual([x.id for x in files], [x.id for x in obj.files_set]) + + def test_task_update_priority(self): + task = self.create_test_object() + obj = TaskWrapper.objects.get(pk=task.taskWrapperId) + self.assertEqual(task.priority, obj.priority) + + new_priority = task.priority + 1234 + task.priority = new_priority + task.save() + + obj = TaskWrapper.objects.get(pk=task.taskWrapperId) + self.assertEqual(new_priority, obj.priority) + + def test_task_update_maxagent(self): + task = self.create_test_object() + obj = TaskWrapper.objects.get(pk=task.taskWrapperId) + self.assertEqual(task.maxAgents, obj.maxAgents) + + new_maxagent = task.maxAgents + 1234 + task.maxAgents = new_maxagent + task.save() + + obj = TaskWrapper.objects.get(pk=task.taskWrapperId) + self.assertEqual(new_maxagent, obj.maxAgents) diff --git a/src/api/v2/index.php b/src/api/v2/index.php index c02c7e82a..ef3a6b0ab 100644 --- a/src/api/v2/index.php +++ b/src/api/v2/index.php @@ -267,11 +267,18 @@ public function process(Request $request, RequestHandler $handler): Response { require __DIR__ . "/../../inc/apiv2/model/vouchers.routes.php"; require __DIR__ . "/../../inc/apiv2/helper/abortChunk.routes.php"; +require __DIR__ . "/../../inc/apiv2/helper/assignAgent.routes.php"; require __DIR__ . "/../../inc/apiv2/helper/createSupertask.routes.php"; require __DIR__ . "/../../inc/apiv2/helper/createSuperHashlist.routes.php"; +require __DIR__ . "/../../inc/apiv2/helper/exportCrackedHashes.routes.php"; +require __DIR__ . "/../../inc/apiv2/helper/exportLeftHashes.routes.php"; +require __DIR__ . "/../../inc/apiv2/helper/exportWordlist.routes.php"; +require __DIR__ . "/../../inc/apiv2/helper/importCrackedHashes.routes.php"; require __DIR__ . "/../../inc/apiv2/helper/importFile.routes.php"; require __DIR__ . "/../../inc/apiv2/helper/purgeTask.routes.php"; +require __DIR__ . "/../../inc/apiv2/helper/recountFileLines.routes.php"; require __DIR__ . "/../../inc/apiv2/helper/resetChunk.routes.php"; require __DIR__ . "/../../inc/apiv2/helper/setUserPassword.routes.php"; +require __DIR__ . "/../../inc/apiv2/helper/unassignAgent.routes.php"; $app->run(); diff --git a/src/bin/hashtopolis.zip b/src/bin/hashtopolis.zip index fe5a7bf44..cec2fba8d 100644 Binary files a/src/bin/hashtopolis.zip and b/src/bin/hashtopolis.zip differ diff --git a/src/dba/AbstractModelFactory.class.php b/src/dba/AbstractModelFactory.class.php index 875881528..c6c596e84 100755 --- a/src/dba/AbstractModelFactory.class.php +++ b/src/dba/AbstractModelFactory.class.php @@ -114,7 +114,7 @@ public function save($model) { $stmt = $dbh->prepare($query); $stmt->execute($vals); - $id = $dbh->lastInsertId(); + $id = intval($dbh->lastInsertId()); if ($id != 0) { $model->setId($id); return $model; diff --git a/src/inc/apiv2/common/AbstractBaseAPI.class.php b/src/inc/apiv2/common/AbstractBaseAPI.class.php index 4d97c6d82..424a1bd6f 100644 --- a/src/inc/apiv2/common/AbstractBaseAPI.class.php +++ b/src/inc/apiv2/common/AbstractBaseAPI.class.php @@ -1,5 +1,4 @@ getFormFields() as $key => $feature) { /* Innitate default values */ - $features[$key] = $feature + ['null' => False, 'protected' => False, 'private' => False, 'choices' => "unset"]; + $features[$key] = $feature + ['null' => False, 'protected' => False, 'private' => False, 'choices' => "unset", 'pk' => False]; if (!array_key_exists('alias', $feature)) { $features[$key]['alias'] = $key; } @@ -147,13 +142,13 @@ public function getExpandables(): array { return []; } - /** - * Process $expand on $object + * Fetch objects for $expand on $objects */ - protected function doExpand(object $object, string $expand): mixed { + protected function fetchExpandObjects(array $objects, string $expand): mixed { } + protected static function getModelFactory(string $model): object { switch($model) { case AccessGroup::class: @@ -490,34 +485,43 @@ protected function filterQuery(mixed $objFactory, DBA\QueryFilter $qF): array return $ret; } + + protected function applyExpansions(object $object, array $expands, array $expandResult): array { + $newObject = $this->obj2Array($object); + foreach ($expands as $expand) { + if (array_key_exists($object->getId(), $expandResult[$expand]) == false) { + $newObject[$expand] = []; + continue; + } + + $expandObject = $expandResult[$expand][$object->getId()]; + if (is_array($expandObject)) { + $newObject[$expand] = array_map(function($object) { return $this->obj2Array($object); }, $expandObject); + } else { + $newObject[$expand] = $this->obj2Array($expandObject); + } + } + + /* Ensure sorted, for easy debugging of fields */ + ksort($newObject); + + return $newObject; + } + + /** * Expands object items */ protected function object2Array(object $object, array $expands = []): array { - $item = $this->obj2Array($object); - + $expandResult = []; foreach ($expands as $expand) { $apiClass = $this->container->get('classMapper')->get(get_class($object)); - $item[$expand] = $apiClass::doExpand($object, $expand); - if (is_null($item[$expand])) { - throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); + $expandResult[$expand] = $apiClass::fetchExpandObjects([$object], $expand); } - }; - - $expandLeft = array_diff($expands, array_keys($item)); - if (sizeof($expandLeft) > 0) { - /* This should never happen, since valid parameter checking is done pre-flight - * in makeExpandables and assignment should be done for every expansion - */ - throw new BadFunctionCallException("Internal error: Expansion(s) '" . join(',', $expandLeft) . "' not implemented!"); - } - /* Ensure sorted, for easy debugging of fields */ - ksort($item); - - return $item; + return $this->applyExpansions($object, $expands, $expandResult); } @@ -626,7 +630,7 @@ protected function validateData(array $data, array $features) /** * Validate incoming parameter keys */ - protected function validateParameters($data, $allFeatures): void { + protected function validateParameters(array $data, array $allFeatures): void { // Features which MAY be present $validFeatures = []; // Features which MUST be present @@ -703,7 +707,7 @@ protected function makeExpandables(Request $request, array $validExpandables): a /** * Find primary key for DBA object */ - private function getPrimaryKey(): string + protected function getPrimaryKey(): string { $features = $this->getFeatures(); # Word-around required since getPrimaryKey is not static in dba/models/*.php diff --git a/src/inc/apiv2/common/AbstractModelAPI.class.php b/src/inc/apiv2/common/AbstractModelAPI.class.php index a2d48a193..a9a5bad15 100644 --- a/src/inc/apiv2/common/AbstractModelAPI.class.php +++ b/src/inc/apiv2/common/AbstractModelAPI.class.php @@ -6,8 +6,13 @@ use Slim\Exception\HttpNotFoundException; -use DBA\Factory; +use DBA\AbstractModelFactory; +use DBA\JoinFilter; +use DBA\Factory; +use DBA\ContainFilter; +use DBA\OrderFilter; +use DBA\QueryFilter; use Middlewares\Utils\HttpErrorException; @@ -31,6 +36,142 @@ final protected function getFeatures(): array ); } + /** + * Retrieve ForeignKey Relation + * + * @param array $objects Objects Fetch relation for selected Objects + * @param string $objectField Field to use as base for $objects + * @param object $factory Factory used to retrieve objects + * @param string $filterField Filter field of $field to filter against $objects field + * + * @return array + */ + final protected static function getForeignKeyRelation( + array $objects, + string $objectField, + object $factory, + string $filterField + ): array { + assert($factory instanceof AbstractModelFactory); + $retval = array(); + + /* Fetch required objects */ + $objectIds = []; + foreach($objects as $object) { + $kv = $object->getKeyValueDict(); + $objectIds[] = $kv[$objectField]; + } + $qF = new ContainFilter($filterField, $objectIds, $factory); + $hO = $factory->filter([Factory::FILTER => $qF]); + + /* Objects are uniquely identified by fields, create mapping to speed-up further processing */ + $f2o = []; + foreach ($hO as $relationObject) { + $f2o[$relationObject->getKeyValueDict()[$filterField]] = $relationObject; + }; + + /* Map objects */ + foreach ($objects as $object) { + $fieldId = $object->getKeyValueDict()[$objectField]; + if (array_key_exists($fieldId, $f2o) == true) { + $retval[$object->getId()] = $f2o[$fieldId]; + } + } + + return $retval; + } + + /** + * Retrieve ManyToOneRelation (reverse ForeignKey) + * + * @param array $objects Objects Fetch relation for selected Objects + * @param string $objectField Field to use as base for $objects + * @param object $factory Factory used to retrieve objects + * @param string $filterField Filter field of $field to filter against $objects field + * + * @return array + */ + final protected static function getManyToOneRelation( + array $objects, + string $objectField, + object $factory, + string $filterField + ): array { + assert($factory instanceof AbstractModelFactory); + $retval = array(); + + /* Fetch required objects */ + $objectIds = []; + foreach($objects as $object) { + $kv = $object->getKeyValueDict(); + $objectIds[] = $kv[$objectField]; + } + $qF = new ContainFilter($filterField, $objectIds, $factory); + $hO = $factory->filter([Factory::FILTER => $qF]); + + /* Map (multiple) objects to base objects */ + foreach ($hO as $relationObject) { + $kv = $relationObject->getKeyValueDict(); + $retval[$kv[$filterField]][] = $relationObject; + } + + return $retval; + } + + + /** + * Retrieve ManyToOne relalation for $objects ('parents') of type $targetFactory via 'intermidate' + * of $intermediateFactory joining on $joinField (between 'intermediate' and 'target'). Filtered by + * $filterField at $intermediateFactory. + * + * @param array $objects Objects Fetch relation for selected Objects + * @param string $objectField Field to use as base for $objects + * @param object $intermediateFactory Factory used as intermediate between parentObject and targetObject + * @param string $filterField Filter field of intermadiateObject to filter against $objects field + * @param object $targetFactory Object properties of objects returned + * @param string $joinField Field to connect 'intermediate' to 'target' + + * @return array + */ + final protected static function getManyToOneRelationViaIntermediate( + array $objects, + string $objectField, + object $intermediateFactory, + string $filterField, + object $targetFactory, + string $joinField, + ): array { + assert($intermediateFactory instanceof AbstractModelFactory); + assert($targetFactory instanceof AbstractModelFactory); + $retval = array(); + + + /* Retrieve Parent -> Intermediate -> Target objects */ + $objectIds = []; + foreach($objects as $object) { + $kv = $object->getKeyValueDict(); + $objectIds[] = $kv[$objectField]; + } + $qF = new ContainFilter($filterField, $objectIds, $intermediateFactory); + $jF = new JoinFilter($intermediateFactory, $joinField, $joinField); + $hO = $targetFactory->filter([Factory::FILTER => $qF, Factory::JOIN => $jF]); + + /* Build mapping Parent -> Intermediate */ + $i2p = []; + foreach($hO[$intermediateFactory->getModelName()] as $intermidiateObject) { + $kv = $intermidiateObject->getKeyValueDict(); + $i2p[$kv[$joinField]] = $kv[$filterField]; + } + + /* Associate Target -> Parent (via Intermediate) */ + foreach($hO[$targetFactory->getModelName()] as $targetObject) { + $parent = $i2p[$targetObject->getKeyValueDict()[$joinField]]; + $retval[$parent][] = $targetObject; + } + + return $retval; + } + /** * Retrieve permissions based on class and method requested */ @@ -128,16 +269,26 @@ public function get(Request $request, Response $response, array $args): Response $allFilters[Factory::ORDER] = $oFs; } - // TODO: Optimize code, should only fetch subsection of database, when pagination is in play + /* Request objects */ $objects = $factory->filter($allFilters); + /* Resolve all expandables */ + $expandResult = []; + foreach ($expands as $expand) { + // mapping from $objectId -> result objects in + $expandResult[$expand] = $this->fetchExpandObjects($objects, $expand); + } + + /* Convert objects to JSON */ $lists = []; foreach ($objects as $object) { - $lists[] = $this->object2Array($object, $expands); + $newObject = $this->applyExpansions($object, $expands, $expandResult); + $lists[] = $newObject; } // TODO: Implement actual expanding $total = count($objects); + $ret = [ "_expandable" => join(",", $expandable), "startAt" => $startAt, diff --git a/src/inc/apiv2/helper/assignAgent.routes.php b/src/inc/apiv2/helper/assignAgent.routes.php new file mode 100644 index 000000000..7597412b0 --- /dev/null +++ b/src/inc/apiv2/helper/assignAgent.routes.php @@ -0,0 +1,36 @@ + ["type" => "int"], + Task::TASK_ID => ["type" => "int"], + ]; + } + + public function actionPost($data): array|null { + AgentUtils::assign($data[Agent::AGENT_ID], $data[Task::TASK_ID], $this->getCurrentUser()); + + # TODO: Check how to handle custom return messages that are not object, probably we want that to be in some kind of standardized form. + return ["assign" => "success"]; + } +} + +AssignAgentHelperAPI::register($app); \ No newline at end of file diff --git a/src/inc/apiv2/helper/exportCrackedHashes.routes.php b/src/inc/apiv2/helper/exportCrackedHashes.routes.php new file mode 100644 index 000000000..9721b82c6 --- /dev/null +++ b/src/inc/apiv2/helper/exportCrackedHashes.routes.php @@ -0,0 +1,37 @@ + ["type" => "int"], + ]; + } + + public function actionPost($data): array|null { + $hashlist = self::getHashlist($data[Hashlist::HASHLIST_ID]); + + $file = HashlistUtils::export($hashlist->getId(), $this->getCurrentUser()); + return $this->object2Array($file); + } +} + +ExportCrackedHashesHelperAPI::register($app); \ No newline at end of file diff --git a/src/inc/apiv2/helper/exportLeftHashes.routes.php b/src/inc/apiv2/helper/exportLeftHashes.routes.php new file mode 100644 index 000000000..fc19578c3 --- /dev/null +++ b/src/inc/apiv2/helper/exportLeftHashes.routes.php @@ -0,0 +1,38 @@ + ["type" => "int"], + ]; + } + + public function actionPost($data): array|null { + $hashlist = self::getHashlist($data[Hashlist::HASHLIST_ID]); + + $file = HashlistUtils::leftlist($hashlist->getId(), $this->getCurrentUser()); + + return $this->object2Array($file); + } +} + +ExportLeftHashesHelperAPI::register($app); \ No newline at end of file diff --git a/src/inc/apiv2/helper/exportWordlist.routes.php b/src/inc/apiv2/helper/exportWordlist.routes.php new file mode 100644 index 000000000..9f53e1d83 --- /dev/null +++ b/src/inc/apiv2/helper/exportWordlist.routes.php @@ -0,0 +1,38 @@ + ["type" => "int"], + ]; + } + + public function actionPost($data): array|null { + $hashlist = self::getHashlist($data[Hashlist::HASHLIST_ID]); + + $arr = HashlistUtils::createWordlists($hashlist->getId(), $this->getCurrentUser()); + + return $this->object2Array($arr[2]); + } +} + +ExportWordlistHelperAPI::register($app); \ No newline at end of file diff --git a/src/inc/apiv2/helper/importCrackedHashes.routes.php b/src/inc/apiv2/helper/importCrackedHashes.routes.php new file mode 100644 index 000000000..ee375d2d3 --- /dev/null +++ b/src/inc/apiv2/helper/importCrackedHashes.routes.php @@ -0,0 +1,47 @@ + ["type" => "int"], + "sourceData" => ['type' => 'str'], + "separator" => ['type' => 'str'], + ]; + } + + public function actionPost($data): array|null { + $hashlist = self::getHashlist($data[Hashlist::HASHLIST_ID]); + + $result = HashlistUtils::processZap($hashlist->getId(), $data["separator"], "paste", ["hashfield" => $data["sourceData"]], [], $this->getCurrentUser()); + + # TODO: Check how to handle custom return messages that are not object, probably we want that to be in some kind of standardized form. + return [ + "totalLines" => $result[0], + "newCracked" => $result[1], + "alreadyCracked" => $result[2], + "invalid" => $result[3], + "notFound" => $result[4], + "processTime" => $result[5], + "tooLongPlaintexts" => $result[6], + ]; + } +} + +ImportCrackedHashesHelperAPI::register($app); \ No newline at end of file diff --git a/src/inc/apiv2/helper/recountFileLines.routes.php b/src/inc/apiv2/helper/recountFileLines.routes.php new file mode 100644 index 000000000..f737b984e --- /dev/null +++ b/src/inc/apiv2/helper/recountFileLines.routes.php @@ -0,0 +1,37 @@ + ["type" => "int"], + ]; + } + + public function actionPost($data): array|null { + // first retrieve the file, as fileCountLines does not check any permissions, therfore to be sure call getFile() first, even if it is not required technically + FileUtils::getFile($data[File::FILE_ID], $this->getCurrentUser()); + + FileUtils::fileCountLines($data[File::FILE_ID]); + + return $this->object2Array(FileUtils::getFile($data[File::FILE_ID], $this->getCurrentUser())); + } +} + +RecountFileFilesHelperAPI::register($app); \ No newline at end of file diff --git a/src/inc/apiv2/helper/unassignAgent.routes.php b/src/inc/apiv2/helper/unassignAgent.routes.php new file mode 100644 index 000000000..cfdda8080 --- /dev/null +++ b/src/inc/apiv2/helper/unassignAgent.routes.php @@ -0,0 +1,35 @@ + ["type" => "int"], + ]; + } + + public function actionPost($data): array|null { + AgentUtils::assign($data[Agent::AGENT_ID], 0, $this->getCurrentUser()); + + # TODO: Check how to handle custom return messages that are not object, probably we want that to be in some kind of standardized form. + return ["unassign" => "success"]; + } +} + +UnassignAgentHelperAPI::register($app); \ No newline at end of file diff --git a/src/inc/apiv2/model/accessgroups.routes.php b/src/inc/apiv2/model/accessgroups.routes.php index 922c9111d..50d8a4fa5 100644 --- a/src/inc/apiv2/model/accessgroups.routes.php +++ b/src/inc/apiv2/model/accessgroups.routes.php @@ -1,7 +1,5 @@ getId(), "=", Factory::getAccessGroupUserFactory()); - $jF = new JoinFilter(Factory::getAccessGroupUserFactory(), User::USER_ID, AccessGroupUser::USER_ID); - return $this->joinQuery(Factory::getUserFactory(), $qF, $jF); + return $this->getManyToOneRelationViaIntermediate( + $objects, + AccessGroup::ACCESS_GROUP_ID, + Factory::getAccessGroupUserFactory(), + AccessGroupUser::ACCESS_GROUP_ID, + Factory::getUserFactory(), + User::USER_ID + ); case 'agentMembers': - $qF = new QueryFilter(AccessGroupAgent::ACCESS_GROUP_ID, $object->getId(), "=", Factory::getAccessGroupAgentFactory()); - $jF = new JoinFilter(Factory::getAccessGroupAgentFactory(), Agent::AGENT_ID, AccessGroupAgent::AGENT_ID); - return $this->joinQuery(Factory::getAgentFactory(), $qF, $jF); + return $this->getManyToOneRelationViaIntermediate( + $objects, + AccessGroup::ACCESS_GROUP_ID, + Factory::getAccessGroupAgentFactory(), + AccessGroupAgent::ACCESS_GROUP_ID, + Factory::getAgentFactory(), + Agent::AGENT_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } protected function createObject(array $data): int { $object = AccessGroupUtils::createGroup($data[AccessGroup::GROUP_NAME]); diff --git a/src/inc/apiv2/model/agentassignments.routes.php b/src/inc/apiv2/model/agentassignments.routes.php index c395bb68e..d66a3dfa6 100644 --- a/src/inc/apiv2/model/agentassignments.routes.php +++ b/src/inc/apiv2/model/agentassignments.routes.php @@ -3,7 +3,9 @@ use DBA\QueryFilter; use DBA\OrderFilter; +use DBA\Agent; use DBA\Assignment; +use DBA\Task; require_once(dirname(__FILE__) . "/../common/AbstractModelAPI.class.php"); @@ -25,18 +27,30 @@ public function getExpandables(): array { return ["task", "agent"]; } - protected function doExpand(object $object, string $expand): mixed { - assert($object instanceof Assignment); + protected function fetchExpandObjects(array $objects, string $expand): mixed { + /* Ensure we receive the proper type */ + array_walk($objects, function($obj) { assert($obj instanceof Assignment); }); + /* Expand requested section */ switch($expand) { case 'task': - $obj = Factory::getTaskFactory()->get($object->getTaskId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Assignment::TASK_ID, + Factory::getTaskFactory(), + Task::TASK_ID + ); case 'agent': - $obj = Factory::getAgentFactory()->get($object->getAgentId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Assignment::AGENT_ID, + Factory::getAgentFactory(), + Agent::AGENT_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } protected function createObject(array $data): int { AgentUtils::assign($data[Assignment::AGENT_ID], $data[Assignment::TASK_ID], $this->getCurrentUser()); diff --git a/src/inc/apiv2/model/agents.routes.php b/src/inc/apiv2/model/agents.routes.php index 0d993580b..b46a4779b 100644 --- a/src/inc/apiv2/model/agents.routes.php +++ b/src/inc/apiv2/model/agents.routes.php @@ -1,8 +1,5 @@ getId(), "=", Factory::getAccessGroupAgentFactory()); - $jF = new JoinFilter(Factory::getAccessGroupAgentFactory(), AccessGroup::ACCESS_GROUP_ID, AccessGroupAgent::ACCESS_GROUP_ID); - return $this->joinQuery(Factory::getAccessGroupFactory(), $qF, $jF); + return $this->getManyToOneRelationViaIntermediate( + $objects, + Agent::AGENT_ID, + Factory::getAccessGroupAgentFactory(), + AccessGroupAgent::AGENT_ID, + Factory::getAccessGroupFactory(), + AccessGroup::ACCESS_GROUP_ID + ); case 'agentstats': - $qF = new QueryFilter(AgentStat::AGENT_ID, $object->getId(), "="); - return $this->filterQuery(Factory::getAgentStatFactory(), $qF); + return $this->getManyToOneRelation( + $objects, + Agent::AGENT_ID, + Factory::getAgentStatFactory(), + AgentStat::AGENT_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } } diff --git a/src/inc/apiv2/model/agentstats.routes.php b/src/inc/apiv2/model/agentstats.routes.php index 8ccecae17..11c9a4c3a 100644 --- a/src/inc/apiv2/model/agentstats.routes.php +++ b/src/inc/apiv2/model/agentstats.routes.php @@ -1,7 +1,5 @@ get($object->getTaskId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Chunk::TASK_ID, + Factory::getTaskFactory(), + Task::TASK_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } } diff --git a/src/inc/apiv2/model/configs.routes.php b/src/inc/apiv2/model/configs.routes.php index 02f536038..94a6a216f 100644 --- a/src/inc/apiv2/model/configs.routes.php +++ b/src/inc/apiv2/model/configs.routes.php @@ -2,6 +2,7 @@ use DBA\Factory; use DBA\Config; +use DBA\ConfigSection; require_once(dirname(__FILE__) . "/../common/AbstractModelAPI.class.php"); @@ -23,14 +24,23 @@ public function getExpandables(): array { return ['configSection']; } - protected function doExpand(object $object, string $expand): mixed { - assert($object instanceof Config); + protected function fetchExpandObjects(array $objects, string $expand): mixed { + /* Ensure we receive the proper type */ + array_walk($objects, function($obj) { assert($obj instanceof Config); }); + + /* Expand requested section */ switch($expand) { case 'configSection': - $obj = Factory::getConfigSectionFactory()->get($object->getConfigSectionId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Config::CONFIG_SECTION_ID, + Factory::getConfigSectionFactory(), + ConfigSection::CONFIG_SECTION_ID, + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } protected function createObject(array $data): int { /* Dummy code to implement abstract functions */ diff --git a/src/inc/apiv2/model/configsections.routes.php b/src/inc/apiv2/model/configsections.routes.php index 5c31c4b78..b9f21ee79 100644 --- a/src/inc/apiv2/model/configsections.routes.php +++ b/src/inc/apiv2/model/configsections.routes.php @@ -1,6 +1,4 @@ get($object->getCrackerBinaryTypeId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + CrackerBinary::CRACKER_BINARY_TYPE_ID, + Factory::getCrackerBinaryTypeFactory(), + CrackerBinaryType::CRACKER_BINARY_TYPE_ID, + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } protected function createObject(array $data): int { CrackerUtils::createBinary( diff --git a/src/inc/apiv2/model/crackertypes.routes.php b/src/inc/apiv2/model/crackertypes.routes.php index 9b42824a0..6e7a730dc 100644 --- a/src/inc/apiv2/model/crackertypes.routes.php +++ b/src/inc/apiv2/model/crackertypes.routes.php @@ -22,14 +22,23 @@ public function getExpandables(): array { return ["crackerVersions"]; } - protected function doExpand(object $object, string $expand): mixed { - assert($object instanceof CrackerBinaryType); + protected function fetchExpandObjects(array $objects, string $expand): mixed { + /* Ensure we receive the proper type */ + array_walk($objects, function($obj) { assert($obj instanceof CrackerBinaryType); }); + + /* Expand requested section */ switch($expand) { case 'crackerVersions': - $qF = new QueryFilter(CrackerBinary::CRACKER_BINARY_TYPE_ID, $object->getId(), "="); - return $this->filterQuery(Factory::getCrackerBinaryFactory(), $qF); + return $this->getManyToOneRelation( + $objects, + CrackerBinaryType::CRACKER_BINARY_TYPE_ID, + Factory::getCrackerBinaryFactory(), + CrackerBinary::CRACKER_BINARY_TYPE_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } protected function createObject(array $data): int { CrackerUtils::createBinaryType($data[CrackerBinaryType::TYPE_NAME]); diff --git a/src/inc/apiv2/model/files.routes.php b/src/inc/apiv2/model/files.routes.php index de9299a5b..8926c1fed 100644 --- a/src/inc/apiv2/model/files.routes.php +++ b/src/inc/apiv2/model/files.routes.php @@ -1,4 +1,6 @@ get($object->getAccessGroupId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + File::ACCESS_GROUP_ID, + Factory::getAccessGroupFactory(), + AccessGroup::ACCESS_GROUP_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } } diff --git a/src/inc/apiv2/model/globalpermissiongroups.routes.php b/src/inc/apiv2/model/globalpermissiongroups.routes.php index 94cf65aff..e99c69868 100644 --- a/src/inc/apiv2/model/globalpermissiongroups.routes.php +++ b/src/inc/apiv2/model/globalpermissiongroups.routes.php @@ -1,7 +1,5 @@ getId(), "="); - return $this->filterQuery(Factory::getUserFactory(), $qF); + return $this->getManyToOneRelation( + $objects, + RightGroup::RIGHT_GROUP_ID, + Factory::getUserFactory(), + User::RIGHT_GROUP_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } /** * Rewrite permissions DB values to CRUD field values diff --git a/src/inc/apiv2/model/hashes.routes.php b/src/inc/apiv2/model/hashes.routes.php index c247be112..97d44cce7 100644 --- a/src/inc/apiv2/model/hashes.routes.php +++ b/src/inc/apiv2/model/hashes.routes.php @@ -1,7 +1,9 @@ get($object->getHashlistId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Hash::HASHLIST_ID, + Factory::getHashListFactory(), + HashList::HASHLIST_ID + ); case 'chunk': - if (is_null($object->getChunkId())) { - /* Chunk expansions are optional, hence the chunk object could be empty */ - return []; - } else { - $obj = Factory::getChunkFactory()->get($object->getChunkId()); - return $this->obj2Array($obj); - } + return $this->getForeignKeyRelation( + $objects, + Hash::CHUNK_ID, + Factory::getChunkFactory(), + Chunk::CHUNK_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } protected function createObject(array $data): int { /* Dummy code to implement abstract functions */ diff --git a/src/inc/apiv2/model/hashlists.routes.php b/src/inc/apiv2/model/hashlists.routes.php index 2c519ceb0..159b4eeef 100644 --- a/src/inc/apiv2/model/hashlists.routes.php +++ b/src/inc/apiv2/model/hashlists.routes.php @@ -1,10 +1,11 @@ get($object->getAccessGroupId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Hashlist::ACCESS_GROUP_ID, + Factory::getAccessGroupFactory(), + AccessGroup::ACCESS_GROUP_ID + ); case 'hashType': - $obj = Factory::getHashTypeFactory()->get($object->getHashTypeId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Hashlist::HASH_TYPE_ID, + Factory::getHashTypeFactory(), + HashType::HASH_TYPE_ID + ); case 'hashes': - $qF = new QueryFilter(Hash::HASHLIST_ID, $object->getId(), "="); - return $this->filterQuery(Factory::getHashFactory(), $qF); + return $this->getManyToOneRelation( + $objects, + Hashlist::HASHLIST_ID, + Factory::getHashFactory(), + Hash::HASHLIST_ID + ); case 'hashlists': - $qF = new QueryFilter(HashlistHashlist::PARENT_HASHLIST_ID, $object->getId(), "=", Factory::getHashlistHashlistFactory()); - $jF = new JoinFilter(Factory::getHashlistHashlistFactory(), Hashlist::HASHLIST_ID, HashlistHashlist::HASHLIST_ID); - return $this->joinQuery(Factory::getHashlistFactory(), $qF, $jF); + /* PARENT_HASHLIST_ID in use in intermediate table */ + return $this->getManyToOneRelationViaIntermediate( + $objects, + Hashlist::HASHLIST_ID, + Factory::getHashlistHashlistFactory(), + HashlistHashlist::PARENT_HASHLIST_ID, + Factory::getHashlistFactory(), + Hashlist::HASHLIST_ID, + ); case 'tasks': - $qF = new QueryFilter(TaskWrapper::HASHLIST_ID, $object->getHashTypeId(), "=", Factory::getTaskWrapperFactory()); - $jF = new JoinFilter(Factory::getTaskWrapperFactory(), Task::TASK_WRAPPER_ID, TaskWrapper::TASK_WRAPPER_ID); - return $this->joinQuery(Factory::getTaskFactory(), $qF, $jF); + return $this->getManyToOneRelationViaIntermediate( + $objects, + Hashlist::HASHLIST_ID, + Factory::getTaskWrapperFactory(), + TaskWrapper::HASHLIST_ID, + Factory::getTaskFactory(), + Task::TASK_WRAPPER_ID, + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } protected function getFilterACL(): array { return [new ContainFilter(Hashlist::ACCESS_GROUP_ID, Util::arrayOfIds(AccessUtils::getAccessGroupsOfUser($this->getCurrentUser())))]; diff --git a/src/inc/apiv2/model/hashtypes.routes.php b/src/inc/apiv2/model/hashtypes.routes.php index 8d414b306..5d10b8285 100644 --- a/src/inc/apiv2/model/hashtypes.routes.php +++ b/src/inc/apiv2/model/hashtypes.routes.php @@ -1,9 +1,4 @@ get($object->getAgentId()); - return $this->obj2Array($obj); - case 'healthCheck': - $obj = Factory::getHealthCheckFactory()->get($object->getHealthCheckId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + HealthCheckAgent::AGENT_ID, + Factory::getAgentFactory(), + Agent::AGENT_ID + ); + case 'healthCheck': + return $this->getForeignKeyRelation( + $objects, + HealthCheckAgent::HEALTH_CHECK_ID, + Factory::getHealthCheckFactory(), + HealthCheck::HEALTH_CHECK_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } protected function createObject(array $object): int { /* Dummy code to implement abstract functions */ diff --git a/src/inc/apiv2/model/healthchecks.routes.php b/src/inc/apiv2/model/healthchecks.routes.php index 383ccd45f..0a608d3a4 100644 --- a/src/inc/apiv2/model/healthchecks.routes.php +++ b/src/inc/apiv2/model/healthchecks.routes.php @@ -1,8 +1,9 @@ get($object->getCrackerBinaryId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + HealthCheck::CRACKER_BINARY_ID, + Factory::getCrackerBinaryFactory(), + CrackerBinary::CRACKER_BINARY_ID + ); case 'healthCheckAgents': - $qF = new QueryFilter(HealthCheck::HEALTH_CHECK_ID, $object->getId(), "="); - return $this->filterQuery(Factory::getHealthCheckAgentFactory(), $qF); + return $this->getManyToOneRelation( + $objects, + HealthCheck::HEALTH_CHECK_ID, + Factory::getHealthCheckAgentFactory(), + HealthCheckAgent::HEALTH_CHECK_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } protected function createObject(array $data): int { $obj = HealthUtils::createHealthCheck( diff --git a/src/inc/apiv2/model/notifications.routes.php b/src/inc/apiv2/model/notifications.routes.php index 4bab9b886..a3a262ce9 100644 --- a/src/inc/apiv2/model/notifications.routes.php +++ b/src/inc/apiv2/model/notifications.routes.php @@ -4,6 +4,7 @@ use DBA\QueryFilter; use DBA\NotificationSetting; +use DBA\User; require_once(dirname(__FILE__) . "/../common/AbstractModelAPI.class.php"); @@ -20,14 +21,23 @@ public function getExpandables(): array { return ['user']; } - protected function doExpand(object $object, string $expand): mixed { - assert($object instanceof NotificationSetting); + protected function fetchExpandObjects(array $objects, string $expand): mixed { + /* Ensure we receive the proper type */ + array_walk($objects, function($obj) { assert($obj instanceof NotificationSetting); }); + + /* Expand requested section */ switch($expand) { case 'user': - $obj = Factory::getUserFactory()->get($object->getUserId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + NotificationSetting::USER_ID, + Factory::getUserFactory(), + User::USER_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } public function getFormFields(): array { return ['actionFilter' => ['type' => 'str(256)']]; diff --git a/src/inc/apiv2/model/pretasks.routes.php b/src/inc/apiv2/model/pretasks.routes.php index d78a86f31..89a091c7f 100644 --- a/src/inc/apiv2/model/pretasks.routes.php +++ b/src/inc/apiv2/model/pretasks.routes.php @@ -24,15 +24,25 @@ public function getExpandables(): array { return ["pretaskFiles"]; } - protected function doExpand(object $object, string $expand): mixed { - assert($object instanceof PreTask); + protected function fetchExpandObjects(array $objects, string $expand): mixed { + /* Ensure we receive the proper type */ + array_walk($objects, function($obj) { assert($obj instanceof PreTask); }); + + /* Expand requested section */ switch($expand) { case 'pretaskFiles': - $qF = new QueryFilter(FilePretask::PRETASK_ID, $object->getId(), "=", Factory::getFilePretaskFactory()); - $jF = new JoinFilter(Factory::getFilePretaskFactory(), File::FILE_ID, FilePretask::FILE_ID); - return $this->joinQuery(Factory::getFileFactory(), $qF, $jF); + return $this->getManyToOneRelationViaIntermediate( + $objects, + Pretask::PRETASK_ID, + Factory::getFilePretaskFactory(), + FilePretask::PRETASK_ID, + Factory::getFileFactory(), + File::FILE_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } public function getFormFields(): array { // TODO Form declarations in more generic class to allow auto-generated OpenAPI specifications diff --git a/src/inc/apiv2/model/speeds.routes.php b/src/inc/apiv2/model/speeds.routes.php index 13f9cd394..3a2761bae 100644 --- a/src/inc/apiv2/model/speeds.routes.php +++ b/src/inc/apiv2/model/speeds.routes.php @@ -1,7 +1,9 @@ get($object->getAgentId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Speed::AGENT_ID, + Factory::getAgentFactory(), + Agent::AGENT_ID + ); case 'task': - $obj = Factory::getTaskFactory()->get($object->getTaskId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Speed::TASK_ID, + Factory::getTaskFactory(), + Task::TASK_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } protected function createObject(array $data): int { assert(False, "Speeds cannot be created via API"); diff --git a/src/inc/apiv2/model/supertasks.routes.php b/src/inc/apiv2/model/supertasks.routes.php index e0980929c..deec64475 100644 --- a/src/inc/apiv2/model/supertasks.routes.php +++ b/src/inc/apiv2/model/supertasks.routes.php @@ -1,6 +1,5 @@ getId(), "=", Factory::getSupertaskPretaskFactory()); - $jF = new JoinFilter(Factory::getSupertaskPretaskFactory(), Pretask::PRETASK_ID, SupertaskPretask::PRETASK_ID); - return $this->joinQuery(Factory::getPretaskFactory(), $qF, $jF); + return $this->getManyToOneRelationViaIntermediate( + $objects, + Supertask::SUPERTASK_ID, + Factory::getSupertaskPretaskFactory(), + SupertaskPretask::SUPERTASK_ID, + Factory::getPretaskFactory(), + Pretask::PRETASK_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } + } public function getFormFields(): array { return [ diff --git a/src/inc/apiv2/model/tasks.routes.php b/src/inc/apiv2/model/tasks.routes.php index 24f7986cd..4244ac679 100644 --- a/src/inc/apiv2/model/tasks.routes.php +++ b/src/inc/apiv2/model/tasks.routes.php @@ -1,10 +1,10 @@ getId(), "=", Factory::getAssignmentFactory()); - $jF = new JoinFilter(Factory::getAssignmentFactory(), Agent::AGENT_ID, Assignment::AGENT_ID); - return $this->joinQuery(Factory::getAgentFactory(), $qF, $jF); + return $this->getManyToOneRelationViaIntermediate( + $objects, + Task::TASK_ID, + Factory::getAssignmentFactory(), + Assignment::TASK_ID, + Factory::getAgentFactory(), + Agent::AGENT_ID + ); case 'crackerBinary': - $obj = Factory::getCrackerBinaryFactory()->get($object->getCrackerBinaryId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Task::CRACKER_BINARY_ID, + Factory::getCrackerBinaryFactory(), + CrackerBinary::CRACKER_BINARY_ID + ); case 'crackerBinaryType': - $obj = Factory::getCrackerBinaryTypeFactory()->get($object->getCrackerBinaryTypeId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + Task::CRACKER_BINARY_TYPE_ID, + Factory::getCrackerBinaryTypeFactory(), + CrackerBinaryType::CRACKER_BINARY_TYPE_ID + ); case 'hashlist': - // Tasks are bit of a special case, as in the task the hashlist is not directly available. - // To get this information we need to join the task with the Hashlist and the TaskWrapper to get the Hashlist. - $qF = new QueryFilter(TaskWrapper::TASK_WRAPPER_ID, $object->getTaskWrapperId(), "=", Factory::getTaskWrapperFactory()); - $jF = new JoinFilter(Factory::getTaskWrapperFactory(), Hashlist::HASHLIST_ID, TaskWrapper::HASHLIST_ID); - return $this->joinQuery(Factory::getHashlistFactory(), $qF, $jF); + return $this->getManyToOneRelationViaIntermediate( + $objects, + Task::TASK_WRAPPER_ID, + Factory::getTaskWrapperFactory(), + TaskWrapper::TASK_WRAPPER_ID, + Factory::getHashlistFactory(), + Hashlist::HASHLIST_ID + ); case 'speeds': - $qF = new QueryFilter(Speed::TASK_ID, $object->getId(), "="); - return $this->filterQuery(Factory::getSpeedFactory(), $qF); + return $this->getManyToOneRelation( + $objects, + Task::TASK_ID, + Factory::getSpeedFactory(), + Speed::TASK_ID + ); case 'files': - $qF = new QueryFilter(FileTask::TASK_ID, $object->getId(), "=", Factory::getFileTaskFactory()); - $jF = new JoinFilter(Factory::getFileTaskFactory(), File::FILE_ID, FileTask::FILE_ID); - return $this->joinQuery(Factory::getFileFactory(), $qF, $jF); + return $this->getManyToOneRelationViaIntermediate( + $objects, + Task::TASK_ID, + Factory::getFileTaskFactory(), + FileTask::TASK_ID, + Factory::getFileFactory(), + File::FILE_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } - } - + } + public function getFormFields(): array { - // TODO Form declarations in more generic class to allow auto-generated OpenAPI specifications - return [ - "hashlistId" => ['type' => 'int'], - "files" => ['type' => 'array', 'subtype' => 'int'], - ]; + // TODO Form declarations in more generic class to allow auto-generated OpenAPI specifications + return [ + "hashlistId" => ['type' => 'int'], + "files" => ['type' => 'array', 'subtype' => 'int'], + ]; } protected function createObject(array $data): int { @@ -104,6 +134,20 @@ public function updateObject(object $object, $data, $processed = []): void { TaskUtils::archiveTask($object->getId(), $this->getCurrentUser()); } + /* Update connected TaskWrapper priority as well */ + $key = Task::PRIORITY; + if (array_key_exists($key, $data)) { + array_push($processed, $key); + TaskUtils::updatePriority($object->getId(), $data[Task::PRIORITY], $this->getCurrentUser()); + } + + /* Update connected TaskWrapper maxAgents as well */ + $key = Task::MAX_AGENTS; + if (array_key_exists($key, $data)) { + array_push($processed, $key); + TaskUtils::updateMaxAgents($object->getId(), $data[Task::MAX_AGENTS], $this->getCurrentUser()); + } + parent::updateObject($object, $data, $processed); } } diff --git a/src/inc/apiv2/model/taskwrappers.routes.php b/src/inc/apiv2/model/taskwrappers.routes.php index c8a3f8b39..9d03f0007 100644 --- a/src/inc/apiv2/model/taskwrappers.routes.php +++ b/src/inc/apiv2/model/taskwrappers.routes.php @@ -1,4 +1,6 @@ get($object->getAccessGroupId()); - return $this->obj2Array($obj); + return $this->getForeignKeyRelation( + $objects, + TaskWrapper::ACCESS_GROUP_ID, + Factory::getAccessGroupFactory(), + AccessGroup::ACCESS_GROUP_ID + ); case 'tasks': - $qF = new QueryFilter(TaskWrapper::TASK_WRAPPER_ID, $object->getId(), "=", Factory::getTaskWrapperFactory()); - $jF = new JoinFilter(Factory::getTaskWrapperFactory(), Task::TASK_WRAPPER_ID, TaskWrapper::TASK_WRAPPER_ID); - return $this->joinQuery(Factory::getTaskFactory(), $qF, $jF); + return $this->getManyToOneRelation( + $objects, + TaskWrapper::TASK_WRAPPER_ID, + Factory::getTaskFactory(), + Task::TASK_WRAPPER_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } } diff --git a/src/inc/apiv2/model/users.routes.php b/src/inc/apiv2/model/users.routes.php index 817e89504..d1214fe5a 100644 --- a/src/inc/apiv2/model/users.routes.php +++ b/src/inc/apiv2/model/users.routes.php @@ -1,11 +1,11 @@ getId(), "=", Factory::getAccessGroupUserFactory()); - $jF = new JoinFilter(Factory::getAccessGroupUserFactory(), AccessGroup::ACCESS_GROUP_ID, AccessGroupUser::ACCESS_GROUP_ID); - return $this->joinQuery(Factory::getAccessGroupFactory(), $qF, $jF); - case 'globalPermissionGroup': - $obj = Factory::getRightGroupFactory()->get($object->getRightGroupId()); - return $this->obj2Array($obj); + return $this->getManyToOneRelationViaIntermediate( + $objects, + User::USER_ID, + Factory::getAccessGroupUserFactory(), + AccessGroupUser::USER_ID, + Factory::getAccessGroupFactory(), + AccessGroup::ACCESS_GROUP_ID + ); + case 'globalPermissionGroup': + return $this->getForeignKeyRelation( + $objects, + User::RIGHT_GROUP_ID, + Factory::getRightGroupFactory(), + RightGroup::RIGHT_GROUP_ID + ); + default: + throw new BadFunctionCallException("Internal error: Expansion '$expand' not implemented!"); } } diff --git a/src/inc/utils/HashlistUtils.class.php b/src/inc/utils/HashlistUtils.class.php index 3fc163af8..489c24747 100644 --- a/src/inc/utils/HashlistUtils.class.php +++ b/src/inc/utils/HashlistUtils.class.php @@ -212,8 +212,9 @@ public static function createWordlists($hashlistId, $user) { fclose($wordlistFile); //add file to files list - $file = new File(null, $wordlistName, Util::filesize($wordlistFilename), $hashlist->getIsSecret(), 0, $hashlist->getAccessGroupId(), null); - Factory::getFileFactory()->save($file); + $file = new File(null, $wordlistName, Util::filesize($wordlistFilename), $hashlist->getIsSecret(), 0, $hashlist->getAccessGroupId(), $wordCount); + $file = Factory::getFileFactory()->save($file); + # TODO: returning wordCount and wordlistName are not really required here as the name and the count are already given in the file object return [$wordCount, $wordlistName, $file]; } diff --git a/src/install/hashtopolis.sql b/src/install/hashtopolis.sql index 41badf626..b1483a411 100644 --- a/src/install/hashtopolis.sql +++ b/src/install/hashtopolis.sql @@ -55,7 +55,7 @@ CREATE TABLE `AgentBinary` ( ) ENGINE = InnoDB; INSERT INTO `AgentBinary` (`agentBinaryId`, `type`, `version`, `operatingSystems`, `filename`, `updateTrack`, `updateAvailable`) VALUES - (1, 'python', '0.7.1', 'Windows, Linux, OS X', 'hashtopolis.zip', 'stable', ''); + (1, 'python', '0.7.2', 'Windows, Linux, OS X', 'hashtopolis.zip', 'stable', ''); CREATE TABLE `AgentError` ( `agentErrorId` INT(11) NOT NULL, diff --git a/src/install/updates/update_v0.14.0_v0.14.1.php b/src/install/updates/update_v0.14.x_v0.14.2.php similarity index 71% rename from src/install/updates/update_v0.14.0_v0.14.1.php rename to src/install/updates/update_v0.14.x_v0.14.2.php index e67582fc4..bd3357e52 100644 --- a/src/install/updates/update_v0.14.0_v0.14.1.php +++ b/src/install/updates/update_v0.14.x_v0.14.2.php @@ -9,4 +9,9 @@ Factory::getFileFactory()->getDB()->query("ALTER TABLE `TaskWrapper` ADD `maxAgents` INT(11) NOT NULL;"); } $EXECUTED["v0.14.x_maxAgents_taskwrapper"] = true; -} \ No newline at end of file +} + +if (!isset($PRESENT["v0.14.x_agentBinaries"])) { + Util::checkAgentVersion("python", "0.7.2", true); + $EXECUTED["v0.14.x_agentBinaries"] = true; +}