Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/apiv2/test_cracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def test_create_with_both_sources_rejects(self):

def test_create_with_missing_source_data_rejects(self):
obj = Cracker(crackerBinaryTypeId=1, version='7.2.7', binaryName='cracker',
sourceType='inline')
sourceType='inline', accessGroupId=1)
with self.assertRaises(HashtopolisError) as e:
obj.save()
self.assertEqual(400, e.exception.status_code)
Expand Down
8 changes: 7 additions & 1 deletion ci/apiv2/test_logentry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ class LogEntryTest(BaseTest):
model_class = LogEntry

def test_get_one(self):
obj = LogEntry.objects.get(pk=1)
# the id of the oldest entry cannot be assumed, the server deletes the
# oldest log entries once the configured limit is exceeded
entries = LogEntry.objects.all()
if len(entries) == 0:
self.skipTest('no log entries exist yet on this database')
obj = LogEntry.objects.get(pk=entries[0].id)
self.assertIsNotNone(obj)
self.assertEqual(entries[0].id, obj.id)

# TODO: Create event which generate logenties and check if logentry is created
98 changes: 97 additions & 1 deletion ci/apiv2/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest
import requests
from hashtopolis import Agent, Chunk, File, Hash, Hashlist, HashType, HealthCheckAgent, Helper, Task, TaskWrapper, User
from hashtopolis import Agent, Chunk, Cracker, CrackerType, File, Hash, Hashlist, HashType, HealthCheckAgent, Helper, Task, TaskWrapper, User

from utils import BaseTest, create_apitoken_raw, create_restricted_user, do_create_agentassignent, do_create_dummy_agent, request_with_api_token, get_hashtopolis_uri

Expand Down Expand Up @@ -1403,6 +1403,102 @@ def test_api_token_cracker_types_include_versions_reports_missing_cracker_binary
self.assertIn('permCrackerBinaryRead', json.dumps(body['meta']))
self.assertNotIn('crackerBinary', {item['type'] for item in body.get('included', [])})

def test_api_token_cracker_types_include_versions_enforces_cracker_access_group(self):
"""Cracker version includes enforce the related binary's access-group ACL."""
cracker_type = self.create_crackertype()
cracker = self.create_cracker(extra_payload={'crackerBinaryTypeId': cracker_type.id})
permissions = {
'permJwtApiKeyCreate': True,
'permCrackerBinaryTypeRead': True,
'permCrackerBinaryRead': True,
}
auth = create_restricted_user(self, permissions)
token = create_apitoken_raw(self, auth, [
'permCrackerBinaryTypeRead',
'permCrackerBinaryRead',
])

response = request_with_api_token(token.token, self._cracker_types_list_query_path(cracker_type))
self.assertEqual(response.status_code, 200, response.text)
body = response.json()
self.assertEqual(body['data'][0]['relationships']['crackerVersions']['data'], [])
included = {(item['type'], item['id']) for item in body.get('included', [])}
self.assertNotIn(('crackerBinary', cracker.id), included)

def test_api_token_health_check_include_enforces_cracker_access_group(self):
"""To-one cracker includes hide binaries outside the user's access groups."""
cracker = self.create_cracker()
health_check = self.create_healthcheck(extra_payload={'crackerBinaryId': cracker.id})
permissions = {
'permJwtApiKeyCreate': True,
'permHealthCheckRead': True,
'permCrackerBinaryRead': True,
}
auth = create_restricted_user(self, permissions)
token = create_apitoken_raw(self, auth, [
'permHealthCheckRead',
'permCrackerBinaryRead',
])
path = (
f'/ui/healthchecks?include=crackerBinary'
f'&filter[healthCheckId__eq]={health_check.id}&page[size]=1'
)

response = request_with_api_token(token.token, path)
self.assertEqual(response.status_code, 200, response.text)
body = response.json()
self.assertIsNone(body['data'][0]['relationships']['crackerBinary']['data'])
included = {(item['type'], item['id']) for item in body.get('included', [])}
self.assertNotIn(('crackerBinary', cracker.id), included)

def test_api_token_cracker_type_delete_rejects_inaccessible_binary(self):
"""Type deletion is atomic when any child binary is outside the user's groups."""
inaccessible_group = self._create_unique_accessgroup()
cracker_type = self.create_crackertype()
admin_token = self.create_apitoken(extra_payload={'scopes': _all_scopes_except(self, [])})
admin_membership_response = request_with_api_token(
admin_token.token,
f'/ui/accessgroups/{inaccessible_group.id}/relationships/userMembers',
method='POST',
payload=self._relationship_payload('user', 1),
)
self.assertEqual(admin_membership_response.status_code, 201, admin_membership_response.text)

accessible_cracker = self.create_cracker(extra_payload={
'crackerBinaryTypeId': cracker_type.id,
'accessGroupId': 1,
})
inaccessible_cracker = self.create_cracker(extra_payload={
'crackerBinaryTypeId': cracker_type.id,
'accessGroupId': inaccessible_group.id,
})
permissions = {
'permJwtApiKeyCreate': True,
'permCrackerBinaryTypeDelete': True,
}
auth = create_restricted_user(self, permissions)
user = User.objects.get(name=auth[0])

membership_response = request_with_api_token(
admin_token.token,
'/ui/accessgroups/1/relationships/userMembers',
method='POST',
payload=self._relationship_payload('user', user.id),
)
self.assertEqual(membership_response.status_code, 201, membership_response.text)

token = create_apitoken_raw(self, auth, ['permCrackerBinaryTypeDelete'])
response = request_with_api_token(
token.token,
f'/ui/crackertypes/{cracker_type.id}',
method='DELETE',
)
self.assertEqual(response.status_code, 403, response.text)

self.assertEqual(CrackerType.objects.get(pk=cracker_type.id).id, cracker_type.id)
self.assertEqual(Cracker.objects.get(pk=accessible_cracker.id).id, accessible_cracker.id)
self.assertEqual(Cracker.objects.get(pk=inaccessible_cracker.id).id, inaccessible_cracker.id)

def test_api_token_high_value_helpers_report_each_missing_required_scope(self):
"""High-value helper endpoints enforce every declared required permission.

Expand Down
3 changes: 2 additions & 1 deletion ci/apiv2/test_taskwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ def test_helper_create_supertask_generic_cracker(self):
crackerBinaryTypeId=crackertype.id,
version='1.2.3',
downloadUrl=get_cracker_archive_url(),
binaryName='generic-x64')
binaryName='generic-x64',
accessGroupId=1)
cracker.save()
self.delete_after_test(cracker)
hashlist = self.create_hashlist()
Expand Down
4 changes: 2 additions & 2 deletions ci/apiv2/testfiles/cracker/create_cracker_001.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"crackerBinaryTypeId": 1,
"version": "0.0.1",
"downloadUrl": "https://example.org/files/cracker-0.0.1.7z",
"binaryName": "cracker"
"binaryName": "cracker",
"accessGroupId": 1
}

4 changes: 2 additions & 2 deletions ci/apiv2/testfiles/cracker/create_cracker_002.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"crackerBinaryTypeId": 1,
"version": "0.0.1",
"downloadUrl": "https://example.org/files/cracker-0.0.1.7z"
"downloadUrl": "https://example.org/files/cracker-0.0.1.7z",
"accessGroupId": 1
}

4 changes: 2 additions & 2 deletions ci/phpunit/TestBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,10 @@ protected function createCrackerBinaryType(): CrackerBinaryType {
/**
* @throws Exception
*/
protected function createCrackerBinary(CrackerBinaryType $crackerBinaryType): CrackerBinary {
protected function createCrackerBinary(CrackerBinaryType $crackerBinaryType, int $accessGroupId = 1): CrackerBinary {
$crackerBinary = $this->createDatabaseObject(
Factory::getCrackerBinaryFactory(),
new CrackerBinary(null, $crackerBinaryType->getId(), '1.0.' . uniqid(), 'https://example.invalid/' . uniqid(), 'binary_' . uniqid(), null)
new CrackerBinary(null, $crackerBinaryType->getId(), '1.0.' . uniqid(), 'https://example.invalid/' . uniqid(), 'binary_' . uniqid(), null, $accessGroupId)
);
$this->assertTrue($crackerBinary instanceof CrackerBinary);
return $crackerBinary;
Expand Down
2 changes: 1 addition & 1 deletion ci/phpunit/dba/AbstractModelFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1681,7 +1681,7 @@ private function setUpHealthCheck(): array {
$crackerBinaryType = new CrackerBinaryType(null, '', 0);
$crackerBinaryType = $this->createDatabaseObject(Factory::getCrackerBinaryTypeFactory(), $crackerBinaryType);

$crackerBinary = new CrackerBinary(null, $crackerBinaryType->getId(), '', '', '', null);
$crackerBinary = new CrackerBinary(null, $crackerBinaryType->getId(), '', '', '', null, 1);
$crackerBinary = $this->createDatabaseObject(Factory::getCrackerBinaryFactory(), $crackerBinary);

$healthCheck = new HealthCheck(null, 0, 0, 0, $hashType->getId(), $crackerBinary->getId(), 0, '');
Expand Down
2 changes: 1 addition & 1 deletion ci/phpunit/dba/MassUpdateSetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public function testMassSingleUpdateWithMappedColumn(): void {
$agent = $this->createDatabaseObject(Factory::getAgentFactory(), new Agent(null, '', '', 0, '', '', 0, 0, 0, '', '', 0, '', null, 0, ''));
$hashType = $this->createDatabaseObject(Factory::getHashTypeFactory(), new HashType(null, $prefix . '_ht', 0, 0));
$cbt = $this->createDatabaseObject(Factory::getCrackerBinaryTypeFactory(), new CrackerBinaryType(null, '', 0));
$cb = $this->createDatabaseObject(Factory::getCrackerBinaryFactory(), new CrackerBinary(null, $cbt->getId(), '', '', '', null));
$cb = $this->createDatabaseObject(Factory::getCrackerBinaryFactory(), new CrackerBinary(null, $cbt->getId(), '', '', '', null, 1));
$healthCheck = $this->createDatabaseObject(Factory::getHealthCheckFactory(), new HealthCheck(null, 0, 0, 0, $hashType->getId(), $cb->getId(), 0, ''));

$hca1 = $this->createDatabaseObject(Factory::getHealthCheckAgentFactory(), new HealthCheckAgent(null, $healthCheck->getId(), $agent->getId(), 0, 0, 0, 0, 100, ''));
Expand Down
11 changes: 8 additions & 3 deletions ci/phpunit/downloadapi/DownloadAppTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Hashtopolis\dba\Factory;
use Hashtopolis\dba\models\Agent;
use Hashtopolis\dba\models\AccessGroupAgent;
use Hashtopolis\dba\models\CrackerBinary;
use Hashtopolis\dba\models\CrackerBinaryType;
use Hashtopolis\inc\defines\DDirectories;
Expand Down Expand Up @@ -51,20 +52,24 @@ protected function setUp(): void {
);
$this->externalBinary = $this->createDatabaseObject(
Factory::getCrackerBinaryFactory(),
new CrackerBinary(null, $this->type->getId(), '1.0.0', 'http://example.com/hc.7z', 'testcracker', null)
new CrackerBinary(null, $this->type->getId(), '1.0.0', 'http://example.com/hc.7z', 'testcracker', null, 1)
);

// create a locally stored binary through the import source
$this->agentToken = 'dl-test-' . uniqid();
$this->createDatabaseObject(
$agent = $this->createDatabaseObject(
Factory::getAgentFactory(),
new Agent(null, 'download-test-agent-' . $suffix, '', 0, '', '', 0, 0, 0, $this->agentToken, '', 0, '', null, 0, '')
);
$this->createDatabaseObject(
Factory::getAccessGroupAgentFactory(),
new AccessGroupAgent(null, 1, $agent->getId())
);

$importName = 'download-test-' . uniqid() . '.7z';
$this->archiveContent = self::SEVEN_ZIP_MAGIC . 'download-test-content';
file_put_contents(self::getImportPath() . $importName, $this->archiveContent);
$this->localBinary = CrackerUtils::createBinaryFromUpload('7.2.7', 'testcracker', $this->type->getId(), 'import', $importName);
$this->localBinary = CrackerUtils::createBinaryFromUpload('7.2.7', 'testcracker', $this->type->getId(), 'import', $importName, 1);
$this->registerDatabaseObject(Factory::getCrackerBinaryFactory(), $this->localBinary);

if (isset($_SERVER['HTTP_RANGE'])) {
Expand Down
28 changes: 24 additions & 4 deletions ci/phpunit/fixtures/openapi/crackerbinarytype.spec.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion ci/phpunit/inc/UtilTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,8 @@ public function testCheckOrCreateInitialObjectCrackerBinary(): void {
'version' => '7.0.0',
'downloadUrl' => 'https://example.com/test.7z',
'binaryName' => 'testHashcat',
'filename' => null
'filename' => null,
'accessGroupId' => 1
];
Util::checkOrCreateInitialObject(Factory::getCrackerBinaryFactory(), $data);
$obj = Factory::getCrackerBinaryFactory()->get($id);
Expand Down
Loading
Loading