Skip to content

Commit b02dced

Browse files
committed
Merge branch '3411-percent-type' into develop
Issue #3411 PR #3558
2 parents 8a5e9c8 + 5a398d8 commit b02dced

File tree

6 files changed

+130
-3
lines changed

6 files changed

+130
-3
lines changed

monkey/agent_plugins/payloads/cryptojacker/src/cryptojacker_options.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from pydantic import Field, conint
1+
from pydantic import Field
22

33
from common.base_models import InfectionMonkeyBaseModel
4+
from common.types import PercentLimited
45

56

67
class CryptojackerOptions(InfectionMonkeyBaseModel):
@@ -11,12 +12,12 @@ class CryptojackerOptions(InfectionMonkeyBaseModel):
1112
default=300, # 5 minutes
1213
ge=0,
1314
)
14-
cpu_utilization: conint(ge=0, le=100) = Field( # type: ignore[valid-type]
15+
cpu_utilization: PercentLimited = Field( # type: ignore[valid-type]
1516
title="CPU utilization",
1617
description="The percentage of CPU to use on a machine",
1718
default=80,
1819
)
19-
memory_utilization: conint(ge=0, le=100) = Field( # type: ignore[valid-type]
20+
memory_utilization: PercentLimited = Field( # type: ignore[valid-type]
2021
title="Memory utilization",
2122
description="The percentage of memory to use on a machine",
2223
default=20,

monkey/common/types/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
from .networking import NetworkService, NetworkPort, PortStatus, SocketAddress, NetworkProtocol
66
from .secrets import OTP, Token
77
from .file_extension import FileExtension
8+
from .percent import Percent, PercentLimited

monkey/common/types/percent.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from typing import Any, Self
2+
3+
from pydantic import NonNegativeFloat
4+
5+
6+
class Percent(NonNegativeFloat):
7+
"""
8+
A type representing a percentage
9+
10+
Note that percentages can be greater than 100. For example, I may have consumed 120% of my quota
11+
(if quotas aren't strictly enforced).
12+
"""
13+
14+
# This __init__() is required so that instances of Percent can be created. If you try to create
15+
# an instance of NonNegativeFloat, no validation is performed.
16+
def __init__(self, v: Any):
17+
Percent._validate_range(v)
18+
19+
@classmethod
20+
def __get_validators__(cls):
21+
for v in super().__get_validators__():
22+
yield v
23+
24+
yield cls.validate
25+
26+
@classmethod
27+
def validate(cls, v: Any) -> Self:
28+
cls._validate_range(v)
29+
30+
# This is required so that floats passed into pydantic models are converted to instances of
31+
# Percent objects.
32+
return cls(v)
33+
34+
@staticmethod
35+
def _validate_range(v: Any):
36+
if v < 0:
37+
raise ValueError("value must be non-negative")
38+
39+
def as_decimal_fraction(self) -> NonNegativeFloat:
40+
"""
41+
Return the percentage as a decimal fraction
42+
43+
Example: 50% -> 0.5
44+
45+
return: The percentage as a decimal fraction
46+
"""
47+
return self / 100.0
48+
49+
50+
class PercentLimited(Percent):
51+
"""
52+
A type representing a percentage limited to 100
53+
"""
54+
55+
le = 100
56+
57+
def __init__(self, v: Any):
58+
PercentLimited._validate_range(v)
59+
60+
@classmethod
61+
def __get_validators__(cls):
62+
for v in super().__get_validators__():
63+
yield v
64+
65+
@staticmethod
66+
def _validate_range(v: Any):
67+
if not (0.0 <= v <= 100.0):
68+
raise ValueError("value must be between 0 and 100")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import pytest
2+
from pydantic import BaseModel
3+
4+
from common.types import Percent, PercentLimited
5+
6+
7+
class Model(BaseModel):
8+
p: Percent
9+
10+
11+
def test_non_negative_percent():
12+
with pytest.raises(ValueError):
13+
Percent(-1.0)
14+
15+
16+
def test_incorrect_type():
17+
with pytest.raises(ValueError):
18+
Percent("stuff")
19+
20+
21+
@pytest.mark.parametrize("input_value", [0.0, 1.0, 99.9, 100.0, 120.5, 50.123, 25])
22+
def test_valid_percent(input_value: float):
23+
assert Percent(input_value) == input_value # type: ignore [arg-type]
24+
25+
26+
@pytest.mark.parametrize(
27+
"input_value,expected_decimal_fraction",
28+
[(0.0, 0.0), (99, 0.99), (51.234, 0.51234), (121.2, 1.212)],
29+
)
30+
def test_as_decimal_fraction(input_value: float, expected_decimal_fraction: float):
31+
assert Model(p=input_value).p.as_decimal_fraction() == expected_decimal_fraction # type: ignore [arg-type] # noqa: E501
32+
33+
34+
class ModelLimited(BaseModel):
35+
p: PercentLimited
36+
37+
38+
@pytest.mark.parametrize("input_value", (-1, -0.01, 100.01))
39+
def test_percent_limited_out_of_range(input_value: float):
40+
with pytest.raises(ValueError):
41+
PercentLimited(input_value)
42+
43+
44+
@pytest.mark.parametrize("input_value", [0.0, 1.0, 99.9, 100.0, 50.123, 25])
45+
def test_valid_percent_limited(input_value: float):
46+
assert PercentLimited(input_value) == input_value # type: ignore [arg-type]
47+
48+
49+
@pytest.mark.parametrize(
50+
"input_value,expected_decimal_fraction", [(0.0, 0.0), (99, 0.99), (51.234, 0.51234)]
51+
)
52+
def test_as_decimal_fraction_limited(input_value: float, expected_decimal_fraction: float):
53+
assert ModelLimited(p=input_value).p.as_decimal_fraction() == expected_decimal_fraction # type: ignore [arg-type] # noqa: E501

vulture_allowlist.py

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from infection_monkey.network.firewall import FirewallApp, WinAdvFirewall, WinFirewall
3232
from infection_monkey.utils import commands
3333
from infection_monkey.utils.decorators import request_cache
34+
from monkey.common.types import Percent
3435
from monkey_island.cc.deployment import Deployment
3536
from monkey_island.cc.models import Machine
3637
from monkey_island.cc.repositories import IAgentEventRepository, MongoAgentEventRepository
@@ -188,3 +189,6 @@
188189
AgentPluginRepositoryIndex._infection_monkey_version_parser
189190
AgentPluginRepositoryIndex._sort_plugins_by_version
190191
AgentPluginRepositoryIndex.use_enum_values
192+
193+
# Remove when cryptojacker is completed
194+
Percent.as_decimal_fraction

0 commit comments

Comments
 (0)