Skip to content

Commit 6ee5741

Browse files
committed
implement improved input validation
1 parent f6af564 commit 6ee5741

File tree

8 files changed

+116
-24
lines changed

8 files changed

+116
-24
lines changed

jupiterone/client.py

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ def _validate_query_string(self, query: str, param_name: str = "query") -> None:
168168

169169
# Basic J1QL validation
170170
query_upper = query.upper().strip()
171-
if not query_upper.startswith(('FIND', 'MATCH', 'RETURN')):
172-
raise JupiterOneClientError(f"{param_name} must be a valid J1QL query starting with FIND, MATCH, or RETURN")
171+
if not query_upper.startswith('FIND'):
172+
raise JupiterOneClientError(f"{param_name} must be a valid J1QL query starting with FIND (case-insensitive)")
173173

174174
def _validate_properties(self, properties: Dict[str, Any], param_name: str = "properties") -> None:
175175
"""Validate entity/relationship properties"""
@@ -216,6 +216,11 @@ def token(self, value: Optional[str]) -> None:
216216
# pylint: disable=R1710
217217
def _execute_query(self, query: str, variables: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
218218
"""Executes query against graphql endpoint"""
219+
# Validate credentials before making API calls
220+
if not self.account:
221+
raise JupiterOneClientError("Account is required. Please set the account property.")
222+
if not self.token:
223+
raise JupiterOneClientError("Token is required. Please set the token property.")
219224

220225
data: Dict[str, Any] = {"query": query}
221226
if variables:
@@ -483,6 +488,11 @@ def query_with_deferred_response(self, query: str, cursor: Optional[str] = None)
483488

484489
def _execute_syncapi_request(self, endpoint: str, payload: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
485490
"""Executes POST request to SyncAPI endpoints"""
491+
# Validate credentials before making API calls
492+
if not self.account:
493+
raise JupiterOneClientError("Account is required. Please set the account property.")
494+
if not self.token:
495+
raise JupiterOneClientError("Token is required. Please set the token property.")
486496

487497
# initiate requests session and implement retry logic of 5 request retries with 1 second between retries
488498
response = self.session.post(
@@ -589,6 +599,34 @@ def create_entity(self, **kwargs: Any) -> Dict[str, Any]:
589599
timestamp (int): Specify createdOn timestamp
590600
properties (dict): Dictionary of key/value entity properties
591601
"""
602+
# Validate required parameters
603+
entity_key = kwargs.get("entity_key")
604+
entity_type = kwargs.get("entity_type")
605+
entity_class = kwargs.get("entity_class")
606+
607+
if not entity_key:
608+
raise JupiterOneClientError("entity_key is required")
609+
if not isinstance(entity_key, str) or not entity_key.strip():
610+
raise JupiterOneClientError("entity_key must be a non-empty string")
611+
612+
if not entity_type:
613+
raise JupiterOneClientError("entity_type is required")
614+
if not isinstance(entity_type, str) or not entity_type.strip():
615+
raise JupiterOneClientError("entity_type must be a non-empty string")
616+
617+
if not entity_class:
618+
raise JupiterOneClientError("entity_class is required")
619+
if not isinstance(entity_class, str) or not entity_class.strip():
620+
raise JupiterOneClientError("entity_class must be a non-empty string")
621+
622+
# Validate properties if provided
623+
if "properties" in kwargs and kwargs["properties"] is not None:
624+
self._validate_properties(kwargs["properties"])
625+
626+
# Validate timestamp if provided
627+
if "timestamp" in kwargs and kwargs["timestamp"] is not None:
628+
if not isinstance(kwargs["timestamp"], int) or kwargs["timestamp"] <= 0:
629+
raise JupiterOneClientError("timestamp must be a positive integer")
592630
variables = {
593631
"entityKey": kwargs.pop("entity_key"),
594632
"entityType": kwargs.pop("entity_type"),
@@ -614,6 +652,19 @@ def delete_entity(self, entity_id: Optional[str] = None, timestamp: Optional[int
614652
timestamp (int, optional): Timestamp for the deletion. Defaults to None.
615653
hard_delete (bool): Whether to perform a hard delete. Defaults to True.
616654
"""
655+
# Validate required parameters
656+
if not entity_id:
657+
raise JupiterOneClientError("entity_id is required")
658+
self._validate_entity_id(entity_id)
659+
660+
# Validate timestamp if provided
661+
if timestamp is not None:
662+
if not isinstance(timestamp, int) or timestamp <= 0:
663+
raise JupiterOneClientError("timestamp must be a positive integer")
664+
665+
# Validate hard_delete
666+
if not isinstance(hard_delete, bool):
667+
raise JupiterOneClientError("hard_delete must be a boolean")
617668
variables: Dict[str, Any] = {"entityId": entity_id, "hardDelete": hard_delete}
618669
if timestamp:
619670
variables["timestamp"] = timestamp
@@ -628,6 +679,14 @@ def update_entity(self, entity_id: Optional[str] = None, properties: Optional[Di
628679
entity_id (str): The _id of the entity to update
629680
properties (dict): Dictionary of key/value entity properties
630681
"""
682+
# Validate required parameters
683+
if not entity_id:
684+
raise JupiterOneClientError("entity_id is required")
685+
self._validate_entity_id(entity_id)
686+
687+
if not properties:
688+
raise JupiterOneClientError("properties is required")
689+
self._validate_properties(properties)
631690
variables = {"entityId": entity_id, "properties": properties}
632691
response = self._execute_query(UPDATE_ENTITY, variables=variables)
633692
return response["data"]["updateEntity"]
@@ -643,6 +702,39 @@ def create_relationship(self, **kwargs: Any) -> Dict[str, Any]:
643702
from_entity_id (str): Entity ID of the source vertex
644703
to_entity_id (str): Entity ID of the destination vertex
645704
"""
705+
# Validate required parameters
706+
relationship_key = kwargs.get("relationship_key")
707+
relationship_type = kwargs.get("relationship_type")
708+
relationship_class = kwargs.get("relationship_class")
709+
from_entity_id = kwargs.get("from_entity_id")
710+
to_entity_id = kwargs.get("to_entity_id")
711+
712+
if not relationship_key:
713+
raise JupiterOneClientError("relationship_key is required")
714+
if not isinstance(relationship_key, str) or not relationship_key.strip():
715+
raise JupiterOneClientError("relationship_key must be a non-empty string")
716+
717+
if not relationship_type:
718+
raise JupiterOneClientError("relationship_type is required")
719+
if not isinstance(relationship_type, str) or not relationship_type.strip():
720+
raise JupiterOneClientError("relationship_type must be a non-empty string")
721+
722+
if not relationship_class:
723+
raise JupiterOneClientError("relationship_class is required")
724+
if not isinstance(relationship_class, str) or not relationship_class.strip():
725+
raise JupiterOneClientError("relationship_class must be a non-empty string")
726+
727+
if not from_entity_id:
728+
raise JupiterOneClientError("from_entity_id is required")
729+
self._validate_entity_id(from_entity_id, "from_entity_id")
730+
731+
if not to_entity_id:
732+
raise JupiterOneClientError("to_entity_id is required")
733+
self._validate_entity_id(to_entity_id, "to_entity_id")
734+
735+
# Validate properties if provided
736+
if "properties" in kwargs and kwargs["properties"] is not None:
737+
self._validate_properties(kwargs["properties"])
646738
variables = {
647739
"relationshipKey": kwargs.pop("relationship_key"),
648740
"relationshipType": kwargs.pop("relationship_type"),

tests/test_client_init.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ def test_client_init_missing_token(self):
4545

4646
def test_client_init_empty_account(self):
4747
"""Test client initialization with empty account"""
48-
with pytest.raises(JupiterOneClientError, match="account is required"):
48+
with pytest.raises(JupiterOneClientError, match="Account cannot be empty"):
4949
JupiterOneClient(account="", token="test-token")
5050

5151
def test_client_init_empty_token(self):
5252
"""Test client initialization with empty token"""
53-
with pytest.raises(JupiterOneClientError, match="token is required"):
53+
with pytest.raises(JupiterOneClientError, match="Token cannot be empty"):
5454
JupiterOneClient(account="test-account", token="")
5555

5656
def test_client_init_none_account(self):

tests/test_create_entity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def request_callback(request):
3737
content_type='application/json',
3838
)
3939

40-
j1 = JupiterOneClient(account='testAccount', token='testToken')
40+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
4141
response = j1.create_entity(
4242
entity_key='host1',
4343
entity_type='test_host',

tests/test_create_relationship.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ def request_callback(request):
4141
content_type='application/json',
4242
)
4343

44-
j1 = JupiterOneClient(account='testAccount', token='testToken')
44+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
4545
response = j1.create_relationship(
4646
relationship_key='relationship1',
4747
relationship_type='test_relationship',
4848
relationship_class='TestRelationship',
49-
from_entity_id='2',
50-
to_entity_id='1'
49+
from_entity_id='entity-id-12345',
50+
to_entity_id='entity-id-67890'
5151
)
5252

5353
assert type(response) == dict

tests/test_delete_entity.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def request_callback(request):
3232
content_type='application/json',
3333
)
3434

35-
j1 = JupiterOneClient(account='testAccount', token='testToken')
35+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
3636
response = j1.delete_entity('1')
3737

3838
assert type(response) == dict
@@ -68,7 +68,7 @@ def request_callback(request):
6868
content_type='application/json',
6969
)
7070

71-
j1 = JupiterOneClient(account='testAccount', token='testToken')
71+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
7272
response = j1.delete_entity('2', timestamp=1640995200000)
7373

7474
assert type(response) == dict
@@ -103,7 +103,7 @@ def request_callback(request):
103103
content_type='application/json',
104104
)
105105

106-
j1 = JupiterOneClient(account='testAccount', token='testToken')
106+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
107107
response = j1.delete_entity('3', hard_delete=False)
108108

109109
assert type(response) == dict
@@ -138,7 +138,7 @@ def request_callback(request):
138138
content_type='application/json',
139139
)
140140

141-
j1 = JupiterOneClient(account='testAccount', token='testToken')
141+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
142142
response = j1.delete_entity('4', timestamp=1640995200000, hard_delete=True)
143143

144144
assert type(response) == dict

tests/test_delete_relationship.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def request_callback(request):
4141
content_type='application/json',
4242
)
4343

44-
j1 = JupiterOneClient(account='testAccount', token='testToken')
44+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
4545
response = j1.delete_relationship('1')
4646

4747
assert type(response) == dict

tests/test_query.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def test_execute_query():
8383
content_type='application/json',
8484
)
8585

86-
j1 = JupiterOneClient(account='testAccount', token='testToken')
86+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
8787
query = "find Host with _id='1'"
8888
variables = {
8989
'query': query,
@@ -110,7 +110,7 @@ def test_limit_skip_query_v1():
110110
content_type='application/json',
111111
)
112112

113-
j1 = JupiterOneClient(account='testAccount', token='testToken')
113+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
114114
query = "find Host with _id='1'"
115115
response = j1.query_v1(
116116
query=query,
@@ -139,7 +139,7 @@ def test_cursor_query_v1():
139139
content_type='application/json',
140140
)
141141

142-
j1 = JupiterOneClient(account='testAccount', token='testToken')
142+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
143143
query = "find Host with _id='1'"
144144

145145
response = j1.query_v1(
@@ -186,7 +186,7 @@ def request_callback(request):
186186
content_type='application/json',
187187
)
188188

189-
j1 = JupiterOneClient(account='testAccount', token='testToken')
189+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
190190
query = "find Host with _id='1' return tree"
191191
response = j1.query_v1(
192192
query=query,
@@ -236,7 +236,7 @@ def request_callback(request):
236236
content_type='application/json',
237237
)
238238

239-
j1 = JupiterOneClient(account='testAccount', token='testToken')
239+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
240240
query = "find Host with _id='1' return tree"
241241
response = j1.query_v1(query)
242242

@@ -268,7 +268,7 @@ def test_retry_on_limit_skip_query():
268268
content_type='application/json',
269269
)
270270

271-
j1 = JupiterOneClient(account='testAccount', token='testToken')
271+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
272272
query = "find Host with _id='1'"
273273
response = j1.query_v1(
274274
query=query,
@@ -302,7 +302,7 @@ def test_retry_on_cursor_query():
302302
content_type='application/json',
303303
)
304304

305-
j1 = JupiterOneClient(account='testAccount', token='testToken')
305+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
306306
query = "find Host with _id='1'"
307307
response = j1.query_v1(
308308
query=query
@@ -322,7 +322,7 @@ def test_avoid_retry_on_limit_skip_query():
322322
content_type='application/json',
323323
)
324324

325-
j1 = JupiterOneClient(account='testAccount', token='testToken')
325+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
326326
query = "find Host with _id='1'"
327327
with pytest.raises(JupiterOneApiError):
328328
j1.query_v1(
@@ -340,7 +340,7 @@ def test_avoid_retry_on_cursor_query():
340340
content_type='application/json',
341341
)
342342

343-
j1 = JupiterOneClient(account='testAccount', token='testToken')
343+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
344344
query = "find Host with _id='1'"
345345
with pytest.raises(JupiterOneApiError):
346346
j1.query_v1(
@@ -356,7 +356,7 @@ def test_warn_limit_and_skip_deprecated():
356356
content_type='application/json',
357357
)
358358

359-
j1 = JupiterOneClient(account='testAccount', token='testToken')
359+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
360360
query = "find Host with _id='1'"
361361

362362
with pytest.warns(DeprecationWarning):

tests/test_update_entity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def request_callback(request):
3434
content_type='application/json',
3535
)
3636

37-
j1 = JupiterOneClient(account='testAccount', token='testToken')
37+
j1 = JupiterOneClient(account='testAccount', token='testToken1234567890')
3838
response = j1.update_entity('1', properties={'testKey': 'testValue'})
3939

4040
assert type(response) == dict

0 commit comments

Comments
 (0)