Skip to content

Commit 506f9d9

Browse files
committed
feat(honeytokens): add GGClient.create_honeytoken_with_context method
1 parent e575ef2 commit 506f9d9

File tree

5 files changed

+236
-0
lines changed

5 files changed

+236
-0
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!--
2+
A new scriv changelog fragment.
3+
4+
Uncomment the section that is right (remove the HTML comment wrapper).
5+
-->
6+
7+
<!--
8+
### Removed
9+
10+
- A bullet item for the Removed category.
11+
12+
-->
13+
14+
### Added
15+
16+
- Add `GGClient.create_honeytoken_with_context()` method
17+
18+
<!--
19+
### Changed
20+
21+
- A bullet item for the Changed category.
22+
23+
-->
24+
<!--
25+
### Deprecated
26+
27+
- A bullet item for the Deprecated category.
28+
29+
-->
30+
<!--
31+
### Fixed
32+
33+
- A bullet item for the Fixed category.
34+
35+
-->
36+
<!--
37+
### Security
38+
39+
- A bullet item for the Security category.
40+
41+
-->

pygitguardian/client.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
DocumentSchema,
3131
HealthCheckResponse,
3232
HoneytokenResponse,
33+
HoneytokenWithContextResponse,
3334
JWTResponse,
3435
JWTService,
3536
MultiScanResult,
@@ -513,6 +514,54 @@ def create_honeytoken(
513514
result.status_code = resp.status_code
514515
return result
515516

517+
def create_honeytoken_with_context(
518+
self,
519+
*,
520+
name: str,
521+
type_: str,
522+
description: Optional[str] = None,
523+
project_extensions: List[str] = [],
524+
filename: Optional[str] = None,
525+
language: Optional[str] = None,
526+
extra_headers: Optional[Dict[str, str]] = None,
527+
) -> Union[Detail, HoneytokenWithContextResponse]:
528+
"""
529+
Create a honeytoken via the /honeytokens/with-context endpoint of the API,
530+
the honeytoken is inserted in a file.
531+
532+
:param name: the honeytoken name
533+
:param type_: the honeytoken type
534+
:param description: the honeytoken description
535+
:param project_extensions: list of file extensions in the project
536+
:param filename: name of requested file
537+
:param language: language of requested file
538+
:param extra_headers: additional headers to add to the request
539+
:return: Detail or Honeytoken with context response and status code
540+
"""
541+
try:
542+
resp = self.post(
543+
endpoint="honeytokens/with-context",
544+
extra_headers=extra_headers,
545+
data={
546+
"name": name,
547+
"type": type_,
548+
"description": description,
549+
"project_extensions": ",".join(project_extensions),
550+
"filename": filename,
551+
"language": language,
552+
},
553+
)
554+
except requests.exceptions.ReadTimeout:
555+
result = Detail("The request timed out.")
556+
result.status_code = 504
557+
else:
558+
if resp.ok:
559+
result = HoneytokenWithContextResponse.from_dict(resp.json())
560+
else:
561+
result = load_detail(resp)
562+
result.status_code = resp.status_code
563+
return result
564+
516565
def iac_directory_scan(
517566
self,
518567
directory: Path,

pygitguardian/models.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,65 @@ def __repr__(self) -> str:
616616
return f"honeytoken:{self.id} {self.name}"
617617

618618

619+
class HoneytokenWithContextResponseSchema(BaseSchema):
620+
content = fields.String()
621+
filename = fields.String()
622+
language = fields.String()
623+
suggested_commit_message = fields.String()
624+
honeytoken_id = fields.UUID()
625+
gitguardian_url = fields.URL()
626+
627+
@post_load
628+
def make_honeytoken_with_context_response(
629+
self, data: Dict[str, Any], **kwargs: Any
630+
) -> "HoneytokenWithContextResponse":
631+
return HoneytokenWithContextResponse(**data)
632+
633+
634+
class HoneytokenWithContextResponse(Base, FromDictMixin):
635+
"""
636+
honeytoken creation with context in the GitGuardian API.
637+
Allows users to get a file where a new honeytoken is.
638+
Example:
639+
{
640+
"content": "def return_aws_credentials():\n \
641+
aws_access_key_id = XXXXXXXX\n \
642+
aws_secret_access_key = XXXXXXXX\n \
643+
aws_region = us-west-2",\n \
644+
return (aws_access_key_id, aws_secret_access_key, aws_region)\n",
645+
"filename": "aws.py",
646+
"language": "python",
647+
"suggested_commit_message": "Add AWS credentials",
648+
"honeytoken_id": "d45a123f-b15d-4fea-abf6-ff2a8479de5b",
649+
"gitguardian_url":
650+
"https://dashboard.gitguardian.com/workspace/1/honeytokens/d45a123f-b15d-4fea-abf6-ff2a8479de5b",
651+
}
652+
"""
653+
654+
SCHEMA = HoneytokenWithContextResponseSchema()
655+
656+
def __init__(
657+
self,
658+
content: str,
659+
filename: str,
660+
language: str,
661+
suggested_commit_message: str,
662+
honeytoken_id: UUID,
663+
gitguardian_url: str,
664+
**kwargs: Any,
665+
) -> None:
666+
super().__init__()
667+
self.content = content
668+
self.filename = filename
669+
self.language = language
670+
self.suggested_commit_message = suggested_commit_message
671+
self.honeytoken_id = honeytoken_id
672+
self.gitguardian_url = gitguardian_url
673+
674+
def __repr__(self) -> str:
675+
return f"honeytoken_context:{self.filename}"
676+
677+
619678
class HealthCheckResponseSchema(BaseSchema):
620679
detail = fields.String(allow_none=False)
621680
status_code = fields.Int(allow_none=False)

tests/test_client.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from pygitguardian.models import (
2323
Detail,
2424
HoneytokenResponse,
25+
HoneytokenWithContextResponse,
2526
JWTResponse,
2627
JWTService,
2728
MultiScanResult,
@@ -818,6 +819,74 @@ def test_create_honeytoken_error(
818819
assert isinstance(result, Detail)
819820

820821

822+
@responses.activate
823+
def test_create_honeytoken_with_context(
824+
client: GGClient,
825+
):
826+
"""
827+
GIVEN a ggclient
828+
WHEN calling create_honeytoken_with_context with parameters
829+
THEN the parameters are passed in the request
830+
AND the returned honeytoken use the parameters
831+
"""
832+
mock_response = responses.post(
833+
url=client._url_from_endpoint("honeytokens/with-context", "v1"),
834+
content_type="application/json",
835+
status=201,
836+
json={
837+
"content": "def return_aws_credentials():\n \
838+
aws_access_key_id = XXXXXXXX\n \
839+
aws_secret_access_key = XXXXXXXX\n \
840+
aws_region = us-west-2,\n \
841+
return (aws_access_key_id, aws_secret_access_key, aws_region)\n",
842+
"filename": "aws.py",
843+
"language": "python",
844+
"suggested_commit_message": "Add AWS credentials",
845+
"honeytoken_id": "d45a123f-b15d-4fea-abf6-ff2a8479de5b",
846+
"gitguardian_url": "https://dashboard.gitguardian.com/workspace/1/honeytokens/d45a123f-b15d-4fea-abf6-ff2a8479de5b", # noqa: E501
847+
},
848+
)
849+
850+
result = client.create_honeytoken_with_context(
851+
name="honeytoken A",
852+
description="honeytoken used in the repository AA",
853+
type_="AWS",
854+
filename="aws.yaml",
855+
)
856+
857+
assert mock_response.call_count == 1
858+
assert isinstance(result, HoneytokenWithContextResponse)
859+
860+
861+
@responses.activate
862+
def test_create_honeytoken_with_context_error(
863+
client: GGClient,
864+
):
865+
"""
866+
GIVEN a ggclient
867+
WHEN calling create_honeytoken_with_context with parameters without the right access
868+
THEN I get a Detail object containing the error detail
869+
"""
870+
mock_response = responses.post(
871+
url=client._url_from_endpoint("honeytokens/with-context", "v1"),
872+
content_type="application/json",
873+
status=400,
874+
json={
875+
"detail": "Not authorized",
876+
},
877+
)
878+
879+
result = client.create_honeytoken_with_context(
880+
name="honeytoken A",
881+
description="honeytoken used in the repository AA",
882+
type_="AWS",
883+
filename="aws.yaml",
884+
)
885+
886+
assert mock_response.call_count == 1
887+
assert isinstance(result, Detail)
888+
889+
821890
@responses.activate
822891
def test_create_jwt(
823892
client: GGClient,

tests/test_models.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
HealthCheckResponseSchema,
1111
HoneytokenResponse,
1212
HoneytokenResponseSchema,
13+
HoneytokenWithContextResponse,
14+
HoneytokenWithContextResponseSchema,
1315
Match,
1416
MatchSchema,
1517
MultiScanResult,
@@ -159,6 +161,22 @@ def test_document_handle_surrogates(self):
159161
"tags": ["publicly_exposed"],
160162
},
161163
),
164+
(
165+
HoneytokenWithContextResponseSchema,
166+
HoneytokenWithContextResponse,
167+
{
168+
"content": "def return_aws_credentials():\n \
169+
aws_access_key_id = XXXXXXXX\n \
170+
aws_secret_access_key = XXXXXXXX\n \
171+
aws_region = us-west-2\n \
172+
return (aws_access_key_id, aws_secret_access_key, aws_region)\n",
173+
"filename": "aws.py",
174+
"language": "python",
175+
"suggested_commit_message": "Add AWS credentials",
176+
"honeytoken_id": "d45a123f-b15d-4fea-abf6-ff2a8479de5b",
177+
"gitguardian_url": "https://dashboard.gitguardian.com/workspace/1/honeytokens/d45a123f-b15d-4fea-abf6-ff2a8479de5b", # noqa: E501
178+
},
179+
),
162180
],
163181
)
164182
def test_schema_loads(self, schema_klass, expected_klass, instance_data):

0 commit comments

Comments
 (0)