Skip to content

Commit

Permalink
Add is_voting_rule
Browse files Browse the repository at this point in the history
  • Loading branch information
np5 committed Nov 12, 2024
1 parent 0c4e2bb commit 92e7638
Show file tree
Hide file tree
Showing 14 changed files with 389 additions and 29 deletions.
5 changes: 5 additions & 0 deletions server/static_src/scss/user_portal.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
:root {
--main-color: #040203;
--background-color: #fafafa;
--danger-color: #ea5165;
--default-weight: 400;
--bold-weight: 700;
--btn-border: 2px;
Expand Down Expand Up @@ -29,6 +30,10 @@ dd {
margin: 0 0 .5rem 0;
}

p.danger {
color: var(--danger-color);
}

/* Grid */

.main-head {
Expand Down
26 changes: 25 additions & 1 deletion tests/santa/test_api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from zentral.contrib.santa.models import Configuration, Rule, RuleSet, Target, Enrollment
from zentral.core.events.base import AuditEvent
from zentral.utils.payloads import get_payload_identifier
from .utils import new_cdhash, new_sha256, new_signing_id_identifier, new_team_id
from .utils import force_rule, new_cdhash, new_sha256, new_signing_id_identifier, new_team_id


class APIViewsTestCase(TestCase):
Expand Down Expand Up @@ -1047,6 +1047,7 @@ def test_rule_create(self, post_event):
"excluded_serial_numbers": [get_random_string(12)],
"tags": [t.id for t in self.force_tags(1)],
"excluded_tags": [t.id for t in self.force_tags(1)],
"is_voting_rule": True, # Read Only!!!
}
with self.captureOnCommitCallbacks(execute=True):
response = self.post_json_data(reverse("santa_api:rules"), data)
Expand All @@ -1062,6 +1063,7 @@ def test_rule_create(self, post_event):
"description": "Description",
"custom_msg": '',
"ruleset": None,
"is_voting_rule": False,
"primary_users": data["primary_users"],
"excluded_primary_users": data["excluded_primary_users"],
"serial_numbers": data["serial_numbers"],
Expand All @@ -1081,6 +1083,7 @@ def test_rule_create(self, post_event):
"description": rule.description,
"primary_users": rule.primary_users,
"ruleset": None,
"is_voting_rule": rule.is_voting_rule,
"custom_msg": '',
"excluded_primary_users": rule.excluded_primary_users,
"serial_numbers": rule.serial_numbers,
Expand Down Expand Up @@ -1629,6 +1632,19 @@ def test_update_rule_unauthorized(self):
response = self.put_json_data(reverse("santa_api:rule", args=(rule.pk,)), {}, include_token=False)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

def test_update_rule_voting_rule_error(self):
self.set_permissions("santa.change_rule")
rule = force_rule(is_voting_rule=True)
response = self.put_json_data(
reverse("santa_api:rule", args=(rule.pk,)),
{"configuration": rule.configuration.pk,
"policy": rule.policy,
"target_identifier": rule.target.identifier,
"target_type": rule.target.type}
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.json(), ['A voting rule cannot be directly updated'])

@patch("zentral.core.queues.backends.kombu.EventQueues.post_event")
def test_update_rule(self, post_event):
configuration = self.force_configuration()
Expand Down Expand Up @@ -1662,6 +1678,7 @@ def test_update_rule(self, post_event):
"description": "new description",
"custom_msg": "new custom block message",
"ruleset": None,
"is_voting_rule": False,
"primary_users": data["primary_users"],
"excluded_primary_users": data["excluded_primary_users"],
"serial_numbers": data["serial_numbers"],
Expand Down Expand Up @@ -1779,6 +1796,13 @@ def test_rule_delete_permission_denied(self):
response = self.delete(reverse("santa_api:rule", args=(rule.pk,)))
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_rule_delete_voting_rule_error(self):
self.set_permissions("santa.delete_rule")
rule = force_rule(is_voting_rule=True)
response = self.delete(reverse("santa_api:rule", args=(rule.pk,)))
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.json(), ['A voting rule cannot be directly deleted'])

# list configuration

def test_get_configurations_unauthorized(self):
Expand Down
115 changes: 115 additions & 0 deletions tests/santa/test_ballot_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,108 @@ def test_ballot_box_existing_votes_reset_no_existing_votes(self):
set()
)

def test_ballot_box_no_conflicting_rule(self):
realm, realm_user = force_realm_user()
force_configuration(voting_realm=realm)
ballot_box = BallotBox.for_realm_user(self.file_target, realm_user, all_configurations=True)
self.assertEqual(ballot_box.conflicting_non_voting_rules, {})
self.assertEqual(ballot_box.conflicting_non_voting_rule_custom_messages, [])

def test_ballot_box_cdhash_conflicting_rule(self):
realm, realm_user = force_realm_user()
configuration = force_configuration(voting_realm=realm)
ballot_box = BallotBox.for_realm_user(self.file_target, realm_user, all_configurations=True)
rule = Rule.objects.create(
configuration=configuration,
policy=Rule.Policy.BLOCKLIST,
target=self.cdhash_target
)
ballot_box.conflicting_non_voting_rules
self.assertEqual(
ballot_box.conflicting_non_voting_rules,
{configuration: [rule]}
)
self.assertEqual(ballot_box.conflicting_non_voting_rule_custom_messages,
["Voting is not allowed on this app."])

def test_ballot_box_binary_conflicting_rule(self):
realm, realm_user = force_realm_user()
configuration = force_configuration(voting_realm=realm)
ballot_box = BallotBox.for_realm_user(self.cdhash_target, realm_user, all_configurations=True)
rule = Rule.objects.create(
configuration=configuration,
policy=Rule.Policy.BLOCKLIST,
target=self.file_target,
)
ballot_box.conflicting_non_voting_rules
self.assertEqual(
ballot_box.conflicting_non_voting_rules,
{configuration: [rule]}
)

def test_ballot_box_signing_id_conflicting_rule(self):
realm, realm_user = force_realm_user()
configuration = force_configuration(voting_realm=realm)
ballot_box = BallotBox.for_realm_user(self.metabundle_target, realm_user, all_configurations=True)
Rule.objects.create( # not a conflict, because more precise
configuration=configuration,
policy=Rule.Policy.BLOCKLIST,
target=self.file_target
)
rule = Rule.objects.create( # conflict
configuration=configuration,
policy=Rule.Policy.BLOCKLIST,
target=self.signing_id_target,
custom_msg="YOLO FOMO",
)
ballot_box.conflicting_non_voting_rules
self.assertEqual(
ballot_box.conflicting_non_voting_rules,
{configuration: [rule]}
)
self.assertEqual(ballot_box.conflicting_non_voting_rule_custom_messages,
["YOLO FOMO"])

def test_ballot_box_certificate_conflicting_rule(self):
realm, realm_user = force_realm_user()
configuration = force_configuration(voting_realm=realm)
ballot_box = BallotBox.for_realm_user(self.cert_target, realm_user, all_configurations=True)
Rule.objects.create( # not a conflict, because more precise
configuration=configuration,
policy=Rule.Policy.BLOCKLIST,
target=self.signing_id_target,
)
rule = Rule.objects.create( # conflict
configuration=configuration,
policy=Rule.Policy.BLOCKLIST,
target=self.team_id_target,
)
ballot_box.conflicting_non_voting_rules
self.assertEqual(
ballot_box.conflicting_non_voting_rules,
{configuration: [rule]}
)

def test_ballot_box_team_id_conflicting_rule(self):
realm, realm_user = force_realm_user()
configuration = force_configuration(voting_realm=realm)
ballot_box = BallotBox.for_realm_user(self.team_id_target, realm_user, all_configurations=True)
Rule.objects.create( # not a conflict, because more precise
configuration=configuration,
policy=Rule.Policy.BLOCKLIST,
target=self.cert_target,
)
rule = Rule.objects.create( # conflict
configuration=configuration,
policy=Rule.Policy.BLOCKLIST,
target=self.team_id_target,
)
ballot_box.conflicting_non_voting_rules
self.assertEqual(
ballot_box.conflicting_non_voting_rules,
{configuration: [rule]}
)

def test_ballot_box_check_voting_allowed_for_configuration_anonymous_voter(self):
ballot_box = BallotBox.for_realm_user(self.file_target, None)
self.assertEqual(ballot_box.check_voting_allowed_for_configuration(None, True),
Expand Down Expand Up @@ -393,6 +495,19 @@ def test_ballot_box_check_voting_allowed_for_configuration_downvote_bundle_error
self.assertEqual(ballot_box.check_voting_allowed_for_configuration(configuration, False),
"A BUNDLE cannot be downvoted")

def test_ballot_box_check_voting_allowed_for_configuration_conflicting_rule(self):
realm, realm_user = force_realm_user()
configuration = force_configuration(voting_realm=realm, default_ballot_target_types=[Target.Type.BUNDLE])
Rule.objects.create(
configuration=configuration,
target=self.file_target,
policy=Rule.Policy.BLOCKLIST,
custom_msg=get_random_string(12)
)
ballot_box = BallotBox.for_realm_user(self.bundle_target, realm_user, all_configurations=True)
self.assertEqual(ballot_box.check_voting_allowed_for_configuration(configuration, True),
"Conflicting non-voting rule")

def test_ballot_box_get_default_votes(self):
realm, realm_user = force_realm_user()
configuration = force_configuration(voting_realm=realm, default_ballot_target_types=[Target.Type.BUNDLE])
Expand Down
66 changes: 65 additions & 1 deletion tests/santa/test_setup_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from zentral.contrib.santa.models import Bundle, Enrollment, Rule, Target
from zentral.core.events.base import AuditEvent
from .utils import (force_configuration,
force_realm, force_realm_group, force_realm_user, force_voting_group,
force_realm, force_realm_group, force_realm_user, force_rule, force_voting_group,
new_cdhash, new_sha256, new_signing_id_identifier, new_team_id)


Expand Down Expand Up @@ -810,6 +810,58 @@ def test_configuration_rules_binary_search(self):
self.assertNotContains(response, reverse("santa:configuration_rules",
args=(rule.configuration.pk,)) + '">all the items')

def test_configuration_rules_no_links(self):
rule = self._force_rule(target_type=Target.Type.BINARY)
voting_rule = force_rule(configuration=rule.configuration, is_voting_rule=True)
self._login("santa.view_rule")
response = self.client.get(reverse("santa:configuration_rules", args=(rule.configuration.pk,)))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "santa/configuration_rules.html")
self.assertContains(response, rule.target.identifier)
self.assertContains(response, voting_rule.target.identifier)
self.assertNotContains(
response,
reverse("santa:update_configuration_rule", args=(rule.configuration.pk, rule.pk))
)
self.assertNotContains(
response,
reverse("santa:delete_configuration_rule", args=(rule.configuration.pk, rule.pk))
)
self.assertNotContains(
response,
reverse("santa:update_configuration_rule", args=(voting_rule.configuration.pk, voting_rule.pk))
)
self.assertNotContains(
response,
reverse("santa:delete_configuration_rule", args=(voting_rule.configuration.pk, voting_rule.pk))
)

def test_configuration_rules_links(self):
rule = self._force_rule(target_type=Target.Type.BINARY)
voting_rule = force_rule(configuration=rule.configuration, is_voting_rule=True)
self._login("santa.change_rule", "santa.delete_rule", "santa.view_rule")
response = self.client.get(reverse("santa:configuration_rules", args=(rule.configuration.pk,)))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "santa/configuration_rules.html")
self.assertContains(response, rule.target.identifier)
self.assertContains(response, voting_rule.target.identifier)
self.assertContains(
response,
reverse("santa:update_configuration_rule", args=(rule.configuration.pk, rule.pk))
)
self.assertContains(
response,
reverse("santa:delete_configuration_rule", args=(rule.configuration.pk, rule.pk))
)
self.assertNotContains(
response,
reverse("santa:update_configuration_rule", args=(voting_rule.configuration.pk, voting_rule.pk))
)
self.assertNotContains(
response,
reverse("santa:delete_configuration_rule", args=(voting_rule.configuration.pk, voting_rule.pk))
)

def test_configuration_rules_no_result(self):
rule = self._force_rule(target_type=Target.Type.BINARY)
self._login("santa.view_rule")
Expand Down Expand Up @@ -1232,6 +1284,12 @@ def test_update_configuration_rule_scope_conflict(self):
'excluded_primary_users': ["'fomo' both included and excluded"],
'excluded_tags': [f"'{tags[0].name}' both included and excluded"]})

def test_update_configuration_rule_voting_rule_error(self):
self._login("santa.change_rule")
rule = force_rule(is_voting_rule=True)
response = self.client.get(reverse("santa:update_configuration_rule", args=(rule.configuration.pk, rule.pk)))
self.assertEqual(response.status_code, 404)

# delete configuration rule

def test_delete_configuration_rule(self):
Expand Down Expand Up @@ -1259,6 +1317,12 @@ def test_delete_configuration_rule(self):
self.assertTemplateUsed(response, "santa/configuration_rules.html")
self.assertFalse(any(rule.target.identifier == binary_hash for rule in response.context["object_list"]))

def test_delete_configuration_rule_voting_rule_error(self):
self._login("santa.delete_rule")
rule = force_rule(is_voting_rule=True)
response = self.client.get(reverse("santa:delete_configuration_rule", args=(rule.configuration.pk, rule.pk)))
self.assertEqual(response.status_code, 404)

# pick rule binary

def test_pick_rule_binary_redirect(self):
Expand Down
23 changes: 22 additions & 1 deletion tests/santa/test_up_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
from django.conf import settings
from django.http import HttpRequest
from django.urls import reverse
from django.utils.crypto import get_random_string
from django.test import TestCase, override_settings
from realms.backends.views import finalize_session
from realms.models import RealmAuthenticationSession
from zentral.contrib.santa.ballot_box import BallotBox
from zentral.contrib.santa.events import SantaBallotEvent, SantaTargetStateUpdateEvent
from zentral.contrib.santa.models import Ballot, Target
from zentral.contrib.santa.models import Ballot, Rule, Target
from .utils import add_file_to_test_class, force_configuration, force_enrolled_machine, force_realm, force_realm_user


Expand Down Expand Up @@ -321,3 +322,23 @@ def test_target_post_file_signing_id_ballot_box_yes_duplicate(self):
response.context["existing_votes"],
[(self.configuration, True)],
)

def test_target_get_conflicting_rule_ballot_box(self):
self._login()
rule = Rule.objects.create( # conflicting rule
configuration=self.configuration,
policy=Rule.Policy.BLOCKLIST,
target=self.team_id_target,
custom_msg=get_random_string(12),
)
response = self.client.get(
reverse("realms_public:santa_up:target",
args=(self.realm.pk, "bundle", self.bundle_sha256))
)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "user_portal/santa_target_detail.html")
self.assertNotContains(response, "Vote to allowlist")
self.assertContains(response, rule.custom_msg)
ballot_box = response.context["ballot_box"]
self.assertEqual(ballot_box.target.type, Target.Type.METABUNDLE)
self.assertEqual(ballot_box.target.identifier, self.metabundle_sha256)
12 changes: 11 additions & 1 deletion tests/santa/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,11 +214,17 @@ def force_rule(
target_identifier=None,
configuration=None,
policy=Rule.Policy.BLOCKLIST,
is_voting_rule=False,
):
target = force_target(target_type, target_identifier)
if configuration is None:
configuration = force_configuration()
return Rule.objects.create(configuration=configuration, target=target, policy=policy)
return Rule.objects.create(
configuration=configuration,
target=target,
policy=policy,
is_voting_rule=is_voting_rule
)


# file
Expand Down Expand Up @@ -287,12 +293,16 @@ def add_file_to_test_class(cls):
uploaded_bundles = _create_bundle_binaries(events)
_commit_files(events)
update_metabundles(uploaded_bundles)
cls.cdhash_target = Target.objects.get(type=Target.Type.CDHASH, identifier=cls.cdhash)
cls.file_target = Target.objects.get(type=Target.Type.BINARY, identifier=cls.file_sha256)
cls.file = File.objects.get(sha_256=cls.file_sha256)
cls.bundle_target = Target.objects.get(type=Target.Type.BUNDLE, identifier=cls.bundle_sha256)
cls.bundle = cls.bundle_target.bundle
cls.metabundle_target = cls.bundle.metabundle.target
cls.metabundle_sha256 = cls.bundle.metabundle.target.identifier
cls.cert_target = Target.objects.get(type=Target.Type.CERTIFICATE, identifier=cls.file_cert_sha256)
cls.signing_id_target = Target.objects.get(type=Target.Type.SIGNING_ID, identifier=cls.file_signing_id)
cls.team_id_target = Target.objects.get(type=Target.Type.TEAM_ID, identifier=cls.file_team_id)


# ballot
Expand Down
Loading

0 comments on commit 92e7638

Please sign in to comment.