Skip to content

Commit 4847dad

Browse files
author
Andrei Neagu
committed
added proper validation to ensure security
1 parent 5f3c6f1 commit 4847dad

File tree

2 files changed

+89
-5
lines changed

2 files changed

+89
-5
lines changed

packages/models-library/src/models_library/api_schemas_dynamic_sidecar/telemetry.py

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
from abc import abstractmethod
2-
from typing import Protocol
2+
from typing import Any, Final, Protocol
33

44
from models_library.projects_nodes_io import NodeID
5-
from pydantic import BaseModel, ByteSize, Field
5+
from pydantic import (
6+
BaseModel,
7+
ByteSize,
8+
Field,
9+
NonNegativeFloat,
10+
root_validator,
11+
validator,
12+
)
13+
14+
_EPSILON: Final[NonNegativeFloat] = 1e-16
615

716

817
class SDiskUsageProtocol(Protocol):
@@ -27,28 +36,70 @@ def percent(self) -> float:
2736
...
2837

2938

39+
def _get_percent(used: float, total: float) -> float:
40+
return round(used * 100 / (total + _EPSILON), 2)
41+
42+
3043
class DiskUsage(BaseModel):
3144
used: ByteSize = Field(description="used space")
3245
free: ByteSize = Field(description="remaining space")
3346

3447
total: ByteSize = Field(description="total space = free + used")
35-
used_percent: float = Field(
48+
used_percent: NonNegativeFloat = Field(
3649
gte=0.00,
3750
lte=100.00,
3851
description="Percent of used space relative to the total space",
3952
)
4053

54+
@validator("free")
55+
@classmethod
56+
def _free_positive(cls, v: float) -> float:
57+
if v < 0:
58+
msg = f"free={v} cannot be a negative value"
59+
raise ValueError(msg)
60+
return v
61+
62+
@validator("used")
63+
@classmethod
64+
def _used_positive(cls, v: float) -> float:
65+
if v < 0:
66+
msg = f"used={v} cannot be a negative value"
67+
raise ValueError(msg)
68+
return v
69+
70+
@root_validator(pre=True)
71+
@classmethod
72+
def _check_total(cls, values: dict[str, Any]) -> dict[str, Any]:
73+
total = values["total"]
74+
free = values["free"]
75+
used = values["used"]
76+
if total != free + used:
77+
msg = f"{total=} is different than the sum of {free=}+{used=} => sum={free+used}"
78+
raise ValueError(msg)
79+
return values
80+
4181
@classmethod
4282
def from_ps_util_disk_usage(
4383
cls, ps_util_disk_usage: SDiskUsageProtocol
4484
) -> "DiskUsage":
4585
total = ps_util_disk_usage.free + ps_util_disk_usage.used
46-
used_percent = round(ps_util_disk_usage.used * 100 / total, 2)
4786
return cls(
4887
used=ByteSize(ps_util_disk_usage.used),
4988
free=ByteSize(ps_util_disk_usage.free),
5089
total=ByteSize(total),
51-
used_percent=used_percent,
90+
used_percent=_get_percent(ps_util_disk_usage.used, total),
91+
)
92+
93+
@classmethod
94+
def from_efs_guardian(
95+
cls, used: NonNegativeFloat, total: NonNegativeFloat
96+
) -> "DiskUsage":
97+
free = total - used
98+
return cls(
99+
used=ByteSize(used),
100+
free=ByteSize(free),
101+
total=ByteSize(total),
102+
used_percent=_get_percent(used, total),
52103
)
53104

54105
def __hash__(self):

packages/models-library/tests/test_api_schemas_dynamic_sidecar_telemetry.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33
from models_library.api_schemas_dynamic_sidecar.telemetry import DiskUsage
44
from psutil._common import sdiskusage
5+
from pydantic import ByteSize, ValidationError
56

67

78
def _assert_same_value(ps_util_disk_usage: sdiskusage) -> None:
@@ -27,3 +28,35 @@ def test_disk_usage_regression_cases(ps_util_disk_usage: sdiskusage):
2728
def test_disk_usage():
2829
ps_util_disk_usage = psutil.disk_usage("/")
2930
_assert_same_value(ps_util_disk_usage)
31+
32+
33+
def test_from_efs_guardian_constructor():
34+
result = DiskUsage.from_efs_guardian(10, 100)
35+
assert result.used == ByteSize(10)
36+
assert result.free == ByteSize(90)
37+
assert result.total == ByteSize(100)
38+
assert result.used_percent == 10
39+
40+
41+
def test_failing_validation():
42+
with pytest.raises(ValidationError) as exc:
43+
assert DiskUsage.from_efs_guardian(100, 10)
44+
45+
assert "free=" in f"{exc.value}"
46+
assert "negative value" in f"{exc.value}"
47+
48+
with pytest.raises(ValidationError) as exc:
49+
assert DiskUsage(
50+
used=-10, # type: ignore
51+
free=ByteSize(10),
52+
total=ByteSize(0),
53+
used_percent=-10,
54+
)
55+
assert "used=" in f"{exc.value}"
56+
assert "negative value" in f"{exc.value}"
57+
58+
with pytest.raises(ValidationError) as exc:
59+
DiskUsage(
60+
used=ByteSize(10), free=ByteSize(10), total=ByteSize(21), used_percent=0
61+
)
62+
assert "is different than the sum of" in f"{exc.value}"

0 commit comments

Comments
 (0)