pyattackforge 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/PKG-INFO +1 -1
  2. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/client.py +2 -0
  3. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/config.py +1 -1
  4. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/resources/__init__.py +2 -0
  5. pyattackforge-0.2.2/pyattackforge/resources/groups.py +20 -0
  6. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/resources/projects.py +40 -0
  7. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge.egg-info/PKG-INFO +1 -1
  8. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge.egg-info/SOURCES.txt +1 -0
  9. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyproject.toml +1 -1
  10. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/LICENSE +0 -0
  11. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/README.md +0 -0
  12. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/__init__.py +0 -0
  13. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/cache.py +0 -0
  14. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/exceptions.py +0 -0
  15. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/resources/assets.py +0 -0
  16. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/resources/base.py +0 -0
  17. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/resources/findings.py +0 -0
  18. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/resources/notes.py +0 -0
  19. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/resources/reports.py +0 -0
  20. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/resources/users.py +0 -0
  21. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/resources/writeups.py +0 -0
  22. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge/transport.py +0 -0
  23. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge.egg-info/dependency_links.txt +0 -0
  24. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge.egg-info/requires.txt +0 -0
  25. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/pyattackforge.egg-info/top_level.txt +0 -0
  26. {pyattackforge-0.2.1 → pyattackforge-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyattackforge
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Python SDK for the AttackForge SSAPI
5
5
  Author: hackman238
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -11,6 +11,7 @@ from .transport import AttackForgeTransport
11
11
  from .resources import (
12
12
  AssetsResource,
13
13
  ProjectsResource,
14
+ GroupsResource,
14
15
  FindingsResource,
15
16
  WriteupsResource,
16
17
  TestcasesResource,
@@ -51,6 +52,7 @@ class AttackForgeClient:
51
52
 
52
53
  self.assets = AssetsResource(self._transport)
53
54
  self.projects = ProjectsResource(self._transport)
55
+ self.groups = GroupsResource(self._transport)
54
56
  self.findings = FindingsResource(self._transport)
55
57
  self.writeups = WriteupsResource(self._transport)
56
58
  self.testcases = TestcasesResource(self._transport)
@@ -16,7 +16,7 @@ class ClientConfig:
16
16
  timeout: float = 30.0
17
17
  max_retries: int = 3
18
18
  backoff_factor: float = 0.5
19
- user_agent: str = "pyattackforge/0.2.1"
19
+ user_agent: str = "pyattackforge/0.2.2"
20
20
  http2: bool = True
21
21
  # Default visibility for newly created findings. False = pending/hidden.
22
22
  default_findings_visible: bool = False
@@ -2,6 +2,7 @@
2
2
 
3
3
  from .assets import AssetsResource
4
4
  from .projects import ProjectsResource
5
+ from .groups import GroupsResource
5
6
  from .findings import FindingsResource
6
7
  from .writeups import WriteupsResource
7
8
  from .testcases import TestcasesResource
@@ -13,6 +14,7 @@ from .reports import ReportsResource
13
14
  __all__ = [
14
15
  "AssetsResource",
15
16
  "ProjectsResource",
17
+ "GroupsResource",
16
18
  "FindingsResource",
17
19
  "WriteupsResource",
18
20
  "TestcasesResource",
@@ -0,0 +1,20 @@
1
+ """Resource: groups."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Optional
6
+
7
+ from .base import BaseResource
8
+
9
+
10
+ class GroupsResource(BaseResource):
11
+ """Groups API resource wrapper."""
12
+
13
+ def create_group(self, payload: Dict[str, Any]) -> Any:
14
+ return self._post("/api/ss/group", json=payload)
15
+
16
+ def get_groups(self, params: Optional[Dict[str, Any]] = None) -> Any:
17
+ return self._get("/api/ss/groups", params=params)
18
+
19
+ def get_group(self, group_id: str, params: Optional[Dict[str, Any]] = None) -> Any:
20
+ return self._get(f"/api/ss/group/{group_id}", params=params)
@@ -130,6 +130,46 @@ class ProjectsResource(BaseResource):
130
130
  def update_user_access_on_project(self, project_id: str, user_id: str, payload: Dict[str, Any]) -> Any:
131
131
  return self._put(f"/api/ss/project/{project_id}/access/{user_id}", json=payload)
132
132
 
133
+ def add_project_to_group(
134
+ self,
135
+ project_id: str,
136
+ group_id: str,
137
+ *,
138
+ project_data: Optional[Dict[str, Any]] = None,
139
+ ) -> Any:
140
+ """
141
+ Convenience helper to add a project to a group by updating the project's groups list.
142
+ """
143
+ if not group_id:
144
+ raise ValueError("group_id is required")
145
+ data = project_data if project_data is not None else self.get_project(project_id)
146
+ project = data
147
+ if isinstance(project, dict) and isinstance(project.get("data"), dict):
148
+ project = project["data"]
149
+ if isinstance(project, dict) and isinstance(project.get("project"), dict):
150
+ project = project["project"]
151
+ groups = project.get("groups") or project.get("project_groups") or []
152
+ group_ids: List[str] = []
153
+ if isinstance(groups, list):
154
+ for entry in groups:
155
+ if isinstance(entry, str) and entry:
156
+ group_ids.append(entry)
157
+ continue
158
+ if isinstance(entry, dict):
159
+ candidate = entry.get("id") or entry.get("group_id") or entry.get("_id")
160
+ if isinstance(candidate, str) and candidate:
161
+ group_ids.append(candidate)
162
+ seen: set = set()
163
+ deduped: List[str] = []
164
+ for value in group_ids:
165
+ if value in seen:
166
+ continue
167
+ seen.add(value)
168
+ deduped.append(value)
169
+ if group_id not in seen:
170
+ deduped.append(group_id)
171
+ return self.update_project(project_id, {"groups": deduped})
172
+
133
173
  def extract_projects_list(self, projects_data: Any) -> List[Dict[str, Any]]:
134
174
  if not isinstance(projects_data, dict):
135
175
  return []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyattackforge
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Python SDK for the AttackForge SSAPI
5
5
  Author: hackman238
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -16,6 +16,7 @@ pyattackforge/resources/__init__.py
16
16
  pyattackforge/resources/assets.py
17
17
  pyattackforge/resources/base.py
18
18
  pyattackforge/resources/findings.py
19
+ pyattackforge/resources/groups.py
19
20
  pyattackforge/resources/notes.py
20
21
  pyattackforge/resources/projects.py
21
22
  pyattackforge/resources/reports.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyattackforge"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Python SDK for the AttackForge SSAPI"
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
File without changes
File without changes
File without changes