Skip to content

Projects API

reptor.api.ProjectsAPI.ProjectsAPI

API client for interacting with SysReptor projects.

Example
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from reptor import Reptor

reptor = Reptor(
    server=os.environ.get("REPTOR_SERVER"),
    token=os.environ.get("REPTOR_TOKEN"),
    project_id="41c09e60-44f1-453b-98f3-3f1875fe90fe",
)

# ProjectsAPI is available as reptor.api.projects, e.g.:
reptor.api.projects.fetch_project()
Source code in reptor/api/ProjectsAPI.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
class ProjectsAPI(APIClient):
    """API client for interacting with SysReptor projects.

    Example:
        ```python
        from reptor import Reptor

        reptor = Reptor(
            server=os.environ.get("REPTOR_SERVER"),
            token=os.environ.get("REPTOR_TOKEN"),
            project_id="41c09e60-44f1-453b-98f3-3f1875fe90fe",
        )

        # ProjectsAPI is available as reptor.api.projects, e.g.:
        reptor.api.projects.fetch_project()
        ```
    """

    # Initialization & Configuration
    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
        self._init_attrs()

    def _init_attrs(self) -> None:
        self.project_design = None

        if not (server := self.reptor.get_config().get_server()):
            raise ValueError("No SysReptor server configured. Try 'reptor conf'.")

        self.base_endpoint = f"{server}/api/v1/pentestprojects"
        self.debug(self.base_endpoint)

    @property
    def object_endpoint(self) -> str:
        return urljoin(self.base_endpoint, self.project_id)

    def search(self, search_term: typing.Optional[str] = "", finished: typing.Optional[bool] = None) -> typing.List[ProjectOverview]:
        """Searches projects by search term and retrieves all projects that match.

        Args:
            search_term (typing.Optional[str], optional): Search Term to look for. Defaults to None.
            finished (bool, optional): Filter for (un)finished projects. Defaults to None.

        Returns:
            List of project overviews (without sections, findings) that match

        Example:
            ```python
            projects = reptor.api.projects.search()
            ```
        """
        params={"search": search_term}
        if finished is not None:
            params["readonly"] = finished
        projects_raw = self.get_paginated(self.base_endpoint, params=params)
        return [ProjectOverview(project_raw) for project_raw in projects_raw]

    def fetch_project(self, project_id: typing.Optional[str] = None, html: bool=False) -> Project:
        """Fetches the project in context from SysReptor.

        Args:
            project_id (str, optional): ID of the project to fetch. If not provided, it uses the project in context.
            html (bool, optional): If True, fetches markdown fields as HTML. Defaults to False.

        Returns:
            Project object with sections and findings.
        """
        return Project(
            self._fetch_project_dict(project_id=project_id, html=html),
            self.reptor.api.project_designs.project_design,
        )

    def check_report(self, group_messages=False) -> dict:
        url = urljoin(self.base_endpoint, f"{self.project_id}/check")
        data = self.get(url).json()
        if group_messages:
            data = data.get("messages")
            # data is a list of dicts. group by "message" key
            grouped = dict()
            for item in data:
                grouped.setdefault(item["message"], []).append(item)
            """
            {
              "Empty field": [
                {
                  "level": "warning",
                  "message": "Empty field",
                  "details": null,
                  "location": {
                      "type": "section",
                      "id": "other",
                      "name": "General",
                      "path": "report_date"
                  }
                }
              ]
            }
            """
            return grouped
        """
        {
          "messages": [
            {
              "level": "warning",
              "message": "Empty field",
              "details": null,
              "location": {
                  "type": "section",
                  "id": "other",
                  "name": "General",
                  "path": "report_date"
              }
            }
          ]
        }
        """
        return data

    def get_enabled_language_codes(self) -> list:
        url = urljoin(self.reptor.get_config().get_server(), "api/v1/utils/settings/")
        settings = self.get(url).json()
        languages = [
            language["code"] for language in settings.get("languages", list()) if language["enabled"] is True
        ]
        return languages

    def init_project(self, new_project_id) -> None:
        """Switches the current project context to a new project ID.

        Args:
            new_project_id (str): Project ID to switch to.

        Example:
            ```python
            reptor.api.projects.init_project("41c09e60-44f1-453b-98f3-3f1875fe90fe")
            ```
        """
        self.reptor._config._raw_config["project_id"] = new_project_id
        self._project_id = new_project_id
        self._init_attrs()
        self.reptor._api = None

    # Project Lifecycle Management
    def create_project(
        self,
        name: str,
        project_design: str,
        tags: typing.Optional[typing.List[str]] = None,
    ) -> Project:
        """Creates a new project in SysReptor.

        Args:
            name (str): Project name
            project_design (str): Project Design ID
            tags (List[str] | None, optional): Project tags, defaults to None.

        Returns:
            Project object of the newly created project.

        Example:
            ```python
            project = reptor.api.projects.create_project(
                name="My New Project",
                project_design="081e2b21-cc41-4ade-8987-e75417cac76b",
                tags=["webapp", "ticket-3321"]
            )
            ```
        """
        data = {
            "name": name,
            "project_type": project_design,
            "tags": tags or list(),
        }
        return Project(self.post(self.base_endpoint, json=data).json(), ProjectDesign())

    def duplicate_project(self, project_id: typing.Optional[str] = None) -> Project:
        """Duplicates a project in SysReptor.

        Args:
            project_id (str, optional): Project ID to duplicate. If None, duplicates current project. Defaults to None.

        Returns:
            Project object of the duplicated project.

        Example:
            ```python
            # Duplicate current project
            duplicated_project = reptor.api.projects.duplicate_project()
            print(f"Duplicated to project ID: {duplicated_project.id}")

            # Duplicate specific project
            duplicated_project = reptor.api.projects.duplicate_project("41c09e60-44f1-453b-98f3-3f1875fe90fe")
            ```
        """
        url = urljoin(self.base_endpoint, f"{project_id or self.project_id}/copy/")
        duplicated_project = self.post(url).json()
        return Project(
            duplicated_project,
            self.reptor.api.project_designs.project_design,
        )

    @contextmanager
    def duplicate_and_cleanup(self):
        """Context manager that duplicates current project, switches to it, and cleans up on exit.

        Example:
            ```python
            with reptor.api.projects.duplicate_and_cleanup():
                # Work with duplicated project
                reptor.api.projects.update_project({"name": "Test Project"})
                # Project is automatically deleted when exiting context
            ```
        """
        original_project_id = self.project_id
        duplicated_project = self.duplicate_project()
        self.init_project(duplicated_project.id)
        self.log.info(f"Duplicated project to {duplicated_project.id}")

        yield

        self.delete_project()
        self.init_project(original_project_id)
        self.log.info("Cleaned up duplicated project")

    def finish_project(self, project_id: typing.Optional[str] = None, unfinish: bool = False) -> bool:
        """Marks a project as finished (readonly) or unfinished.

        Args:
            project_id (str, optional): Project ID to finish. If None, uses current project. Defaults to None.
            unfinish (bool, optional): Marks project as unfinished. Defaults to False.

        Returns:
            True if project is readonly/finished, False if unfinished.

        Example:
            ```python
            # Finish current project
            is_finished = reptor.api.projects.finish_project()

            # Unfinish a specific project
            is_finished = reptor.api.projects.finish_project(
                project_id="41c09e60-44f1-453b-98f3-3f1875fe90fe",
                unfinish=True
            )
            ```
        """
        if project_id:
            url = urljoin(self.base_endpoint, project_id)
        else:
            url = self.object_endpoint
        url = urljoin(url, "readonly/")
        return self.patch(
            url,
            json={"readonly": not unfinish}
        ).json().get("readonly")

    def delete_project(self, project_id: typing.Optional[str] = None) -> None:
        """Deletes a project from SysReptor.

        Args:
            project_id (str, optional): Project ID to delete. If None, deletes current project. Defaults to None.

        Returns:
            :

        Example:
            ```python
            # Delete current project
            reptor.api.projects.delete_project()

            # Delete specific project
            reptor.api.projects.delete_project("41c09e60-44f1-453b-98f3-3f1875fe90fe")
            ```
        """
        if project_id:
            url = urljoin(self.base_endpoint, project_id)
        else:
            url = self.object_endpoint
        self.delete(url)

    # Project Data Operations
    @cached_property
    def project(self) -> Project:
        return self.fetch_project()

    @cached_property
    def _project_dict(self) -> dict:
        return self._fetch_project_dict()

    def _fetch_project_dict(self, project_id: typing.Optional[str] = None, html=False) -> dict:
        """Fetches the project dictionary from the API"""
        if project_id is None:
            project_id = self.project_id
        if html:
            url = urljoin(self.base_endpoint, f"{project_id}/md2html/")
            return self.post(url).json()
        else:
            url = urljoin(self.base_endpoint, project_id)
            return self.get(url).json()

    def update_project(self, data: dict) -> Project:
        """Updates project metadata of the current project.

        Args:
            data (dict): Project data to update (name, tags, etc.).

        Returns:
            Updated project object.

        Example:
            ```python
            updated_project = reptor.api.projects.update_project({
                "name": "Updated Project Name",
                "tags": ["webapp", "internal"]
            })
            ```
        """
        url = urljoin(self.base_endpoint, f"{self.project_id}/")
        return Project(
            self.patch(url, json=data).json(),
            self.reptor.api.project_designs.project_design,
        )

    def update_project_design(self, design_id, force=False) -> Project:
        """Updates the project design (template) of the current project.

        Args:
            design_id (str): ID of the new project design.
            force (bool, optional): Force change even if designs are incompatible (might lead to data loss). Defaults to False.

        Returns:
            Updated project object.

        Example:
            ```python
            updated_project = reptor.api.projects.update_project_design(
                "b0a54c7d-ca54-4629-bb1d-36d7e5e88bf7",
            )
            ```
        """
        data = {
            "project_type": design_id,
            "force_change_project_type": True if force else False,
        }
        try:
            return self.update_project(data)
        except HTTPError as e:
            raise (HTTPError(e.response.text))

    def export(self, project_id: typing.Optional[str] = None) -> bytes:
        """Exports a Project in archive format (tar.gz).

        Args:
            project_id (str, optional): ID of the project to export. If not provided, it uses the project in context.

        Returns:
            Project archive content.

        Example:
            ```python
            # Export current project
            project_archive = reptor.api.projects.export()
            with open("project.tar.gz", "wb") as f:
                f.write(project_archive)

            # Export specific project
            other_project = reptor.api.projects.export("41c09e60-44f1-453b-98f3-3f1875fe90fe")
            with open("other_project.tar.gz", "wb") as f:
                f.write(other_project)
            ```
        """
        if project_id is None:
            project_id = self.project_id
        url = urljoin(self.base_endpoint, f"{project_id}/export/all")
        return self.post(url).content

    def render(self, project_id: typing.Optional[str] = None) -> bytes:
        """Renders project to PDF.

        Args:
            project_id (str, optional): ID of the project to render. If not provided, it uses the project in context.

        Returns:
            PDF content of the project report.

        Example:
            ```python
            # Render current project
            my_report = reptor.api.projects.render()
            with open("my_report.pdf", "wb") as f:
                f.write(my_report)

            # Render specific project
            other_report = reptor.api.projects.render("41c09e60-44f1-453b-98f3-3f1875fe90fe")
            with open("other_report.pdf", "wb") as f:
                f.write(other_report)
            ```
        """
        # Get report checks
        project_id = project_id or self.project_id

        checks = self.check_report(group_messages=True)
        for check, warnings in checks.items():
            if any([w.get("level") == "warning" for w in warnings]):
                self.log.warning(f'Report Check Warning: "{check}" (x{len(warnings)})')

        # Render report
        url = urljoin(self.base_endpoint, f"{project_id}/generate/")
        try:
            return self.post(url).content
        except HTTPError as e:
            try:
                for msg in e.response.json().get("messages", []):
                    if msg.get("level") == "error":
                        self.log.error(msg.get("message"))
                    elif msg.get("level") == "warning":
                        self.log.warning(msg.get("message"))
            except Exception:
                pass
            raise e

    # Section Operations
    def get_sections(self) -> typing.List[Section]:
        """Gets all sections of the current project.

        Returns:
            List of sections for this project.

        Example:
            ```python
            sections = reptor.api.projects.get_sections()
            ```
        """
        return_data = list()
        url = urljoin(self.base_endpoint, f"{self.project_id}/sections/")
        response = self.get(url).json()

        if not response:
            return return_data

        if not self.project_design:
            self.project_design = self.reptor.api.project_designs.project_design

        for item in response:
            section = Section(SectionRaw(item), self.project_design)
            return_data.append(section)
        return return_data

    def update_section(self, section_id: str, data: dict) -> SectionRaw:
        """Updates a section with new data.

        Args:
            section_id (str): ID of the section to update.
            data (dict): Section data to update.

        Returns:
            Updated section object.

        Example:
            ```python
            updated_section = reptor.api.projects.update_section(
                "other",
                {"data": {"report_date": "2024-01-15"}}
            )
            ```
        """
        url = urljoin(self.base_endpoint, f"{self.project_id}/sections/{section_id}/")
        return SectionRaw(self.patch(url, json=data).json())

    def update_sections(self, sections: typing.List[dict]) -> typing.List[SectionRaw]:
        """Updates multiple sections with new data.

        Args:
            sections (typing.List[dict]): List of section data dictionaries to update.

        Returns:
            List of updated section objects.

        Example:
            ```python
            updated_sections = reptor.api.projects.update_sections([
                {"id": "other", "data": {"report_date": "2024-01-15"}},
                {"id": "executive_summary", "data": {"summary": "Updated summary"}}
            ])
            ```
        """
        project_design = self.reptor.api.project_designs.project_design
        updated_sections = list()
        for section_data in sections:
            Section(
                section_data,
                project_design,
                strict_type_check=False,
            )  # Raises ValueError if invalid
            if not section_data.get("id"):
                raise ValueError("Section data must contain an 'id' field.")
        for section_data in sections:
            # Iterate a second time to check that all sections are valid
            updated_sections.append(self.update_section(section_data.get("id"), section_data))
        return updated_sections

    def update_report_fields(self, data: dict) -> typing.List[SectionRaw]:
        self.log.warning(
            "update_report_fields() is deprecated and will be removed in a future version. "
            "Use update_sections() instead for more reliable field updates."
        )
        # Get project data to map report fields to sections
        project = self.reptor.api.projects.project
        project_design = self.reptor.api.project_designs.project_design
        # Map fields to sections
        sections_data = dict()
        for section in project.sections:
            sections_data[section.id] = {"data": {}}
            for report_field_name, report_field_data in data.items():
                if report_field_name in section.fields:
                    sections_data[section.id]["data"][
                        report_field_name
                    ] = report_field_data

        # Check for valid report field data format
        for _, section_data in sections_data.items():
            Section(
                section_data,
                project_design,
                strict_type_check=False,
            )  # Raises ValueError if invalid

        # Upload
        sections = list()
        for section_id, section_data in sections_data.items():
            if section_data["data"]:
                sections.append(self.update_section(section_id, section_data))
        return sections

    # Finding Operations
    def get_findings(self) -> typing.List[FindingRaw]:
        """Gets all findings of the current project.

        Returns:
            List of findings for this project.

        Example:
            ```python
            findings = reptor.api.projects.get_findings()
            for finding in findings:
                print(f"Finding: {finding.data.get('title', 'Untitled')}")
            ```
        """
        url = urljoin(self.base_endpoint, f"{self.project_id}/findings/")
        response = self.get(url).json()

        if not response:
            return []
        return [FindingRaw(f) for f in response]

    def get_finding(self, finding_id: str) -> FindingRaw:
        """Gets a single finding by ID.

        Args:
            finding_id (str): ID of the finding to retrieve.

        Returns:
            Finding object.

        Example:
            ```python
            finding = reptor.api.projects.get_finding("3294a042-0ab6-4463-a95d-1915561d2820")
            print(finding.data.get('title'))
            ```
        """
        url = urljoin(self.base_endpoint, f"{self.project_id}/findings/{finding_id}/")
        return FindingRaw(self.get(url).json())

    def create_finding(self, data: dict) -> FindingRaw:
        """Creates a new finding in the current project.

        Args:
            data (dict): Finding data for the new finding.

        Returns:
            Created finding object.

        Example:
            ```python
            new_finding = reptor.api.projects.create_finding({
                "title": "SQL Injection",
                "severity": "high",
                "description": "Found SQL injection vulnerability..."
            })
            ```
        """
        url = urljoin(self.base_endpoint, f"{self.project_id}/findings/")
        return FindingRaw(self.post(url, json=data).json())

    def create_finding_from_template(self, template_id: str, language: typing.Optional[str] = None) -> FindingRaw:
        """Creates a new finding from a template.

        Args:
            template_id (str): Finding template ID.
            language (str, optional): Language code for the template. Defaults to None.

        Returns:
            Created finding object.

        Example:
            ```python
            finding = reptor.api.projects.create_finding_from_template(
                "38cbd644-c83c-4157-a27c-df3ee9472f92",
                language="en-US"
            )
            ```
        """
        url = urljoin(self.base_endpoint, f"{self.project_id}/findings/fromtemplate/")
        data = {"template": template_id}
        if language:
            data["template_language"] = language
        return FindingRaw(self.post(url, json=data).json())

    def update_finding(self, finding_id: str, data: dict) -> FindingRaw:
        """Updates an existing finding with new data.

        Args:
            finding_id (str): ID of the finding to update.
            data (dict): Finding data to update.

        Returns:
            Updated finding object.

        Example:
            ```python
            updated_finding = reptor.api.projects.update_finding(
                "3294a042-0ab6-4463-a95d-1915561d2820",
                {"title": "Updated Title", "severity": "high"}
            )
            ```
        """
        url = urljoin(self.base_endpoint, f"{self.project_id}/findings/{finding_id}/")
        return FindingRaw(self.patch(url, json=data).json())

    def delete_finding(self, finding_id: str) -> None:
        """Deletes a finding from the current project.

        Args:
            finding_id (str): ID of the finding to delete.

        Returns:
            :

        Example:
            ```python
            reptor.api.projects.delete_finding("finding-uuid-here")
            ```
        """
        url = urljoin(self.base_endpoint, f"{self.project_id}/findings/{finding_id}/")
        self.delete(url)

search

search(search_term='', finished=None)

Searches projects by search term and retrieves all projects that match.

Parameters:

  • search_term (Optional[str], default: '' ) –

    Search Term to look for. Defaults to None.

  • finished (bool, default: None ) –

    Filter for (un)finished projects. Defaults to None.

Returns:

  • List[ProjectOverview]

    List of project overviews (without sections, findings) that match

Example
1
projects = reptor.api.projects.search()
Source code in reptor/api/ProjectsAPI.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def search(self, search_term: typing.Optional[str] = "", finished: typing.Optional[bool] = None) -> typing.List[ProjectOverview]:
    """Searches projects by search term and retrieves all projects that match.

    Args:
        search_term (typing.Optional[str], optional): Search Term to look for. Defaults to None.
        finished (bool, optional): Filter for (un)finished projects. Defaults to None.

    Returns:
        List of project overviews (without sections, findings) that match

    Example:
        ```python
        projects = reptor.api.projects.search()
        ```
    """
    params={"search": search_term}
    if finished is not None:
        params["readonly"] = finished
    projects_raw = self.get_paginated(self.base_endpoint, params=params)
    return [ProjectOverview(project_raw) for project_raw in projects_raw]

fetch_project

fetch_project(project_id=None, html=False)

Fetches the project in context from SysReptor.

Parameters:

  • project_id (str, default: None ) –

    ID of the project to fetch. If not provided, it uses the project in context.

  • html (bool, default: False ) –

    If True, fetches markdown fields as HTML. Defaults to False.

Returns:

  • Project

    Project object with sections and findings.

Source code in reptor/api/ProjectsAPI.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def fetch_project(self, project_id: typing.Optional[str] = None, html: bool=False) -> Project:
    """Fetches the project in context from SysReptor.

    Args:
        project_id (str, optional): ID of the project to fetch. If not provided, it uses the project in context.
        html (bool, optional): If True, fetches markdown fields as HTML. Defaults to False.

    Returns:
        Project object with sections and findings.
    """
    return Project(
        self._fetch_project_dict(project_id=project_id, html=html),
        self.reptor.api.project_designs.project_design,
    )

init_project

init_project(new_project_id)

Switches the current project context to a new project ID.

Parameters:

  • new_project_id (str) –

    Project ID to switch to.

Example
1
reptor.api.projects.init_project("41c09e60-44f1-453b-98f3-3f1875fe90fe")
Source code in reptor/api/ProjectsAPI.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def init_project(self, new_project_id) -> None:
    """Switches the current project context to a new project ID.

    Args:
        new_project_id (str): Project ID to switch to.

    Example:
        ```python
        reptor.api.projects.init_project("41c09e60-44f1-453b-98f3-3f1875fe90fe")
        ```
    """
    self.reptor._config._raw_config["project_id"] = new_project_id
    self._project_id = new_project_id
    self._init_attrs()
    self.reptor._api = None

create_project

create_project(name, project_design, tags=None)

Creates a new project in SysReptor.

Parameters:

  • name (str) –

    Project name

  • project_design (str) –

    Project Design ID

  • tags (List[str] | None, default: None ) –

    Project tags, defaults to None.

Returns:

  • Project

    Project object of the newly created project.

Example
1
2
3
4
5
project = reptor.api.projects.create_project(
    name="My New Project",
    project_design="081e2b21-cc41-4ade-8987-e75417cac76b",
    tags=["webapp", "ticket-3321"]
)
Source code in reptor/api/ProjectsAPI.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def create_project(
    self,
    name: str,
    project_design: str,
    tags: typing.Optional[typing.List[str]] = None,
) -> Project:
    """Creates a new project in SysReptor.

    Args:
        name (str): Project name
        project_design (str): Project Design ID
        tags (List[str] | None, optional): Project tags, defaults to None.

    Returns:
        Project object of the newly created project.

    Example:
        ```python
        project = reptor.api.projects.create_project(
            name="My New Project",
            project_design="081e2b21-cc41-4ade-8987-e75417cac76b",
            tags=["webapp", "ticket-3321"]
        )
        ```
    """
    data = {
        "name": name,
        "project_type": project_design,
        "tags": tags or list(),
    }
    return Project(self.post(self.base_endpoint, json=data).json(), ProjectDesign())

duplicate_project

duplicate_project(project_id=None)

Duplicates a project in SysReptor.

Parameters:

  • project_id (str, default: None ) –

    Project ID to duplicate. If None, duplicates current project. Defaults to None.

Returns:

  • Project

    Project object of the duplicated project.

Example
1
2
3
4
5
6
# Duplicate current project
duplicated_project = reptor.api.projects.duplicate_project()
print(f"Duplicated to project ID: {duplicated_project.id}")

# Duplicate specific project
duplicated_project = reptor.api.projects.duplicate_project("41c09e60-44f1-453b-98f3-3f1875fe90fe")
Source code in reptor/api/ProjectsAPI.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
def duplicate_project(self, project_id: typing.Optional[str] = None) -> Project:
    """Duplicates a project in SysReptor.

    Args:
        project_id (str, optional): Project ID to duplicate. If None, duplicates current project. Defaults to None.

    Returns:
        Project object of the duplicated project.

    Example:
        ```python
        # Duplicate current project
        duplicated_project = reptor.api.projects.duplicate_project()
        print(f"Duplicated to project ID: {duplicated_project.id}")

        # Duplicate specific project
        duplicated_project = reptor.api.projects.duplicate_project("41c09e60-44f1-453b-98f3-3f1875fe90fe")
        ```
    """
    url = urljoin(self.base_endpoint, f"{project_id or self.project_id}/copy/")
    duplicated_project = self.post(url).json()
    return Project(
        duplicated_project,
        self.reptor.api.project_designs.project_design,
    )

duplicate_and_cleanup

duplicate_and_cleanup()

Context manager that duplicates current project, switches to it, and cleans up on exit.

Example
1
2
3
4
with reptor.api.projects.duplicate_and_cleanup():
    # Work with duplicated project
    reptor.api.projects.update_project({"name": "Test Project"})
    # Project is automatically deleted when exiting context
Source code in reptor/api/ProjectsAPI.py
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
@contextmanager
def duplicate_and_cleanup(self):
    """Context manager that duplicates current project, switches to it, and cleans up on exit.

    Example:
        ```python
        with reptor.api.projects.duplicate_and_cleanup():
            # Work with duplicated project
            reptor.api.projects.update_project({"name": "Test Project"})
            # Project is automatically deleted when exiting context
        ```
    """
    original_project_id = self.project_id
    duplicated_project = self.duplicate_project()
    self.init_project(duplicated_project.id)
    self.log.info(f"Duplicated project to {duplicated_project.id}")

    yield

    self.delete_project()
    self.init_project(original_project_id)
    self.log.info("Cleaned up duplicated project")

finish_project

finish_project(project_id=None, unfinish=False)

Marks a project as finished (readonly) or unfinished.

Parameters:

  • project_id (str, default: None ) –

    Project ID to finish. If None, uses current project. Defaults to None.

  • unfinish (bool, default: False ) –

    Marks project as unfinished. Defaults to False.

Returns:

  • bool

    True if project is readonly/finished, False if unfinished.

Example
1
2
3
4
5
6
7
8
# Finish current project
is_finished = reptor.api.projects.finish_project()

# Unfinish a specific project
is_finished = reptor.api.projects.finish_project(
    project_id="41c09e60-44f1-453b-98f3-3f1875fe90fe",
    unfinish=True
)
Source code in reptor/api/ProjectsAPI.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
def finish_project(self, project_id: typing.Optional[str] = None, unfinish: bool = False) -> bool:
    """Marks a project as finished (readonly) or unfinished.

    Args:
        project_id (str, optional): Project ID to finish. If None, uses current project. Defaults to None.
        unfinish (bool, optional): Marks project as unfinished. Defaults to False.

    Returns:
        True if project is readonly/finished, False if unfinished.

    Example:
        ```python
        # Finish current project
        is_finished = reptor.api.projects.finish_project()

        # Unfinish a specific project
        is_finished = reptor.api.projects.finish_project(
            project_id="41c09e60-44f1-453b-98f3-3f1875fe90fe",
            unfinish=True
        )
        ```
    """
    if project_id:
        url = urljoin(self.base_endpoint, project_id)
    else:
        url = self.object_endpoint
    url = urljoin(url, "readonly/")
    return self.patch(
        url,
        json={"readonly": not unfinish}
    ).json().get("readonly")

delete_project

delete_project(project_id=None)

Deletes a project from SysReptor.

Parameters:

  • project_id (str, default: None ) –

    Project ID to delete. If None, deletes current project. Defaults to None.

Returns:

  • None
Example
1
2
3
4
5
# Delete current project
reptor.api.projects.delete_project()

# Delete specific project
reptor.api.projects.delete_project("41c09e60-44f1-453b-98f3-3f1875fe90fe")
Source code in reptor/api/ProjectsAPI.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
def delete_project(self, project_id: typing.Optional[str] = None) -> None:
    """Deletes a project from SysReptor.

    Args:
        project_id (str, optional): Project ID to delete. If None, deletes current project. Defaults to None.

    Returns:
        :

    Example:
        ```python
        # Delete current project
        reptor.api.projects.delete_project()

        # Delete specific project
        reptor.api.projects.delete_project("41c09e60-44f1-453b-98f3-3f1875fe90fe")
        ```
    """
    if project_id:
        url = urljoin(self.base_endpoint, project_id)
    else:
        url = self.object_endpoint
    self.delete(url)

update_project

update_project(data)

Updates project metadata of the current project.

Parameters:

  • data (dict) –

    Project data to update (name, tags, etc.).

Returns:

  • Project

    Updated project object.

Example
1
2
3
4
updated_project = reptor.api.projects.update_project({
    "name": "Updated Project Name",
    "tags": ["webapp", "internal"]
})
Source code in reptor/api/ProjectsAPI.py
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
def update_project(self, data: dict) -> Project:
    """Updates project metadata of the current project.

    Args:
        data (dict): Project data to update (name, tags, etc.).

    Returns:
        Updated project object.

    Example:
        ```python
        updated_project = reptor.api.projects.update_project({
            "name": "Updated Project Name",
            "tags": ["webapp", "internal"]
        })
        ```
    """
    url = urljoin(self.base_endpoint, f"{self.project_id}/")
    return Project(
        self.patch(url, json=data).json(),
        self.reptor.api.project_designs.project_design,
    )

update_project_design

update_project_design(design_id, force=False)

Updates the project design (template) of the current project.

Parameters:

  • design_id (str) –

    ID of the new project design.

  • force (bool, default: False ) –

    Force change even if designs are incompatible (might lead to data loss). Defaults to False.

Returns:

  • Project

    Updated project object.

Example
1
2
3
updated_project = reptor.api.projects.update_project_design(
    "b0a54c7d-ca54-4629-bb1d-36d7e5e88bf7",
)
Source code in reptor/api/ProjectsAPI.py
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
def update_project_design(self, design_id, force=False) -> Project:
    """Updates the project design (template) of the current project.

    Args:
        design_id (str): ID of the new project design.
        force (bool, optional): Force change even if designs are incompatible (might lead to data loss). Defaults to False.

    Returns:
        Updated project object.

    Example:
        ```python
        updated_project = reptor.api.projects.update_project_design(
            "b0a54c7d-ca54-4629-bb1d-36d7e5e88bf7",
        )
        ```
    """
    data = {
        "project_type": design_id,
        "force_change_project_type": True if force else False,
    }
    try:
        return self.update_project(data)
    except HTTPError as e:
        raise (HTTPError(e.response.text))

export

export(project_id=None)

Exports a Project in archive format (tar.gz).

Parameters:

  • project_id (str, default: None ) –

    ID of the project to export. If not provided, it uses the project in context.

Returns:

  • bytes

    Project archive content.

Example
1
2
3
4
5
6
7
8
9
# Export current project
project_archive = reptor.api.projects.export()
with open("project.tar.gz", "wb") as f:
    f.write(project_archive)

# Export specific project
other_project = reptor.api.projects.export("41c09e60-44f1-453b-98f3-3f1875fe90fe")
with open("other_project.tar.gz", "wb") as f:
    f.write(other_project)
Source code in reptor/api/ProjectsAPI.py
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
def export(self, project_id: typing.Optional[str] = None) -> bytes:
    """Exports a Project in archive format (tar.gz).

    Args:
        project_id (str, optional): ID of the project to export. If not provided, it uses the project in context.

    Returns:
        Project archive content.

    Example:
        ```python
        # Export current project
        project_archive = reptor.api.projects.export()
        with open("project.tar.gz", "wb") as f:
            f.write(project_archive)

        # Export specific project
        other_project = reptor.api.projects.export("41c09e60-44f1-453b-98f3-3f1875fe90fe")
        with open("other_project.tar.gz", "wb") as f:
            f.write(other_project)
        ```
    """
    if project_id is None:
        project_id = self.project_id
    url = urljoin(self.base_endpoint, f"{project_id}/export/all")
    return self.post(url).content

render

render(project_id=None)

Renders project to PDF.

Parameters:

  • project_id (str, default: None ) –

    ID of the project to render. If not provided, it uses the project in context.

Returns:

  • bytes

    PDF content of the project report.

Example
1
2
3
4
5
6
7
8
9
# Render current project
my_report = reptor.api.projects.render()
with open("my_report.pdf", "wb") as f:
    f.write(my_report)

# Render specific project
other_report = reptor.api.projects.render("41c09e60-44f1-453b-98f3-3f1875fe90fe")
with open("other_report.pdf", "wb") as f:
    f.write(other_report)
Source code in reptor/api/ProjectsAPI.py
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
def render(self, project_id: typing.Optional[str] = None) -> bytes:
    """Renders project to PDF.

    Args:
        project_id (str, optional): ID of the project to render. If not provided, it uses the project in context.

    Returns:
        PDF content of the project report.

    Example:
        ```python
        # Render current project
        my_report = reptor.api.projects.render()
        with open("my_report.pdf", "wb") as f:
            f.write(my_report)

        # Render specific project
        other_report = reptor.api.projects.render("41c09e60-44f1-453b-98f3-3f1875fe90fe")
        with open("other_report.pdf", "wb") as f:
            f.write(other_report)
        ```
    """
    # Get report checks
    project_id = project_id or self.project_id

    checks = self.check_report(group_messages=True)
    for check, warnings in checks.items():
        if any([w.get("level") == "warning" for w in warnings]):
            self.log.warning(f'Report Check Warning: "{check}" (x{len(warnings)})')

    # Render report
    url = urljoin(self.base_endpoint, f"{project_id}/generate/")
    try:
        return self.post(url).content
    except HTTPError as e:
        try:
            for msg in e.response.json().get("messages", []):
                if msg.get("level") == "error":
                    self.log.error(msg.get("message"))
                elif msg.get("level") == "warning":
                    self.log.warning(msg.get("message"))
        except Exception:
            pass
        raise e

get_sections

get_sections()

Gets all sections of the current project.

Returns:

  • List[Section]

    List of sections for this project.

Example
1
sections = reptor.api.projects.get_sections()
Source code in reptor/api/ProjectsAPI.py
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
def get_sections(self) -> typing.List[Section]:
    """Gets all sections of the current project.

    Returns:
        List of sections for this project.

    Example:
        ```python
        sections = reptor.api.projects.get_sections()
        ```
    """
    return_data = list()
    url = urljoin(self.base_endpoint, f"{self.project_id}/sections/")
    response = self.get(url).json()

    if not response:
        return return_data

    if not self.project_design:
        self.project_design = self.reptor.api.project_designs.project_design

    for item in response:
        section = Section(SectionRaw(item), self.project_design)
        return_data.append(section)
    return return_data

update_section

update_section(section_id, data)

Updates a section with new data.

Parameters:

  • section_id (str) –

    ID of the section to update.

  • data (dict) –

    Section data to update.

Returns:

Example
1
2
3
4
updated_section = reptor.api.projects.update_section(
    "other",
    {"data": {"report_date": "2024-01-15"}}
)
Source code in reptor/api/ProjectsAPI.py
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
def update_section(self, section_id: str, data: dict) -> SectionRaw:
    """Updates a section with new data.

    Args:
        section_id (str): ID of the section to update.
        data (dict): Section data to update.

    Returns:
        Updated section object.

    Example:
        ```python
        updated_section = reptor.api.projects.update_section(
            "other",
            {"data": {"report_date": "2024-01-15"}}
        )
        ```
    """
    url = urljoin(self.base_endpoint, f"{self.project_id}/sections/{section_id}/")
    return SectionRaw(self.patch(url, json=data).json())

update_sections

update_sections(sections)

Updates multiple sections with new data.

Parameters:

  • sections (List[dict]) –

    List of section data dictionaries to update.

Returns:

  • List[SectionRaw]

    List of updated section objects.

Example
1
2
3
4
updated_sections = reptor.api.projects.update_sections([
    {"id": "other", "data": {"report_date": "2024-01-15"}},
    {"id": "executive_summary", "data": {"summary": "Updated summary"}}
])
Source code in reptor/api/ProjectsAPI.py
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
def update_sections(self, sections: typing.List[dict]) -> typing.List[SectionRaw]:
    """Updates multiple sections with new data.

    Args:
        sections (typing.List[dict]): List of section data dictionaries to update.

    Returns:
        List of updated section objects.

    Example:
        ```python
        updated_sections = reptor.api.projects.update_sections([
            {"id": "other", "data": {"report_date": "2024-01-15"}},
            {"id": "executive_summary", "data": {"summary": "Updated summary"}}
        ])
        ```
    """
    project_design = self.reptor.api.project_designs.project_design
    updated_sections = list()
    for section_data in sections:
        Section(
            section_data,
            project_design,
            strict_type_check=False,
        )  # Raises ValueError if invalid
        if not section_data.get("id"):
            raise ValueError("Section data must contain an 'id' field.")
    for section_data in sections:
        # Iterate a second time to check that all sections are valid
        updated_sections.append(self.update_section(section_data.get("id"), section_data))
    return updated_sections

get_findings

get_findings()

Gets all findings of the current project.

Returns:

  • List[FindingRaw]

    List of findings for this project.

Example
1
2
3
findings = reptor.api.projects.get_findings()
for finding in findings:
    print(f"Finding: {finding.data.get('title', 'Untitled')}")
Source code in reptor/api/ProjectsAPI.py
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
def get_findings(self) -> typing.List[FindingRaw]:
    """Gets all findings of the current project.

    Returns:
        List of findings for this project.

    Example:
        ```python
        findings = reptor.api.projects.get_findings()
        for finding in findings:
            print(f"Finding: {finding.data.get('title', 'Untitled')}")
        ```
    """
    url = urljoin(self.base_endpoint, f"{self.project_id}/findings/")
    response = self.get(url).json()

    if not response:
        return []
    return [FindingRaw(f) for f in response]

get_finding

get_finding(finding_id)

Gets a single finding by ID.

Parameters:

  • finding_id (str) –

    ID of the finding to retrieve.

Returns:

Example
1
2
finding = reptor.api.projects.get_finding("3294a042-0ab6-4463-a95d-1915561d2820")
print(finding.data.get('title'))
Source code in reptor/api/ProjectsAPI.py
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
def get_finding(self, finding_id: str) -> FindingRaw:
    """Gets a single finding by ID.

    Args:
        finding_id (str): ID of the finding to retrieve.

    Returns:
        Finding object.

    Example:
        ```python
        finding = reptor.api.projects.get_finding("3294a042-0ab6-4463-a95d-1915561d2820")
        print(finding.data.get('title'))
        ```
    """
    url = urljoin(self.base_endpoint, f"{self.project_id}/findings/{finding_id}/")
    return FindingRaw(self.get(url).json())

create_finding

create_finding(data)

Creates a new finding in the current project.

Parameters:

  • data (dict) –

    Finding data for the new finding.

Returns:

Example
1
2
3
4
5
new_finding = reptor.api.projects.create_finding({
    "title": "SQL Injection",
    "severity": "high",
    "description": "Found SQL injection vulnerability..."
})
Source code in reptor/api/ProjectsAPI.py
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
def create_finding(self, data: dict) -> FindingRaw:
    """Creates a new finding in the current project.

    Args:
        data (dict): Finding data for the new finding.

    Returns:
        Created finding object.

    Example:
        ```python
        new_finding = reptor.api.projects.create_finding({
            "title": "SQL Injection",
            "severity": "high",
            "description": "Found SQL injection vulnerability..."
        })
        ```
    """
    url = urljoin(self.base_endpoint, f"{self.project_id}/findings/")
    return FindingRaw(self.post(url, json=data).json())

create_finding_from_template

create_finding_from_template(template_id, language=None)

Creates a new finding from a template.

Parameters:

  • template_id (str) –

    Finding template ID.

  • language (str, default: None ) –

    Language code for the template. Defaults to None.

Returns:

Example
1
2
3
4
finding = reptor.api.projects.create_finding_from_template(
    "38cbd644-c83c-4157-a27c-df3ee9472f92",
    language="en-US"
)
Source code in reptor/api/ProjectsAPI.py
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
def create_finding_from_template(self, template_id: str, language: typing.Optional[str] = None) -> FindingRaw:
    """Creates a new finding from a template.

    Args:
        template_id (str): Finding template ID.
        language (str, optional): Language code for the template. Defaults to None.

    Returns:
        Created finding object.

    Example:
        ```python
        finding = reptor.api.projects.create_finding_from_template(
            "38cbd644-c83c-4157-a27c-df3ee9472f92",
            language="en-US"
        )
        ```
    """
    url = urljoin(self.base_endpoint, f"{self.project_id}/findings/fromtemplate/")
    data = {"template": template_id}
    if language:
        data["template_language"] = language
    return FindingRaw(self.post(url, json=data).json())

update_finding

update_finding(finding_id, data)

Updates an existing finding with new data.

Parameters:

  • finding_id (str) –

    ID of the finding to update.

  • data (dict) –

    Finding data to update.

Returns:

Example
1
2
3
4
updated_finding = reptor.api.projects.update_finding(
    "3294a042-0ab6-4463-a95d-1915561d2820",
    {"title": "Updated Title", "severity": "high"}
)
Source code in reptor/api/ProjectsAPI.py
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
def update_finding(self, finding_id: str, data: dict) -> FindingRaw:
    """Updates an existing finding with new data.

    Args:
        finding_id (str): ID of the finding to update.
        data (dict): Finding data to update.

    Returns:
        Updated finding object.

    Example:
        ```python
        updated_finding = reptor.api.projects.update_finding(
            "3294a042-0ab6-4463-a95d-1915561d2820",
            {"title": "Updated Title", "severity": "high"}
        )
        ```
    """
    url = urljoin(self.base_endpoint, f"{self.project_id}/findings/{finding_id}/")
    return FindingRaw(self.patch(url, json=data).json())

delete_finding

delete_finding(finding_id)

Deletes a finding from the current project.

Parameters:

  • finding_id (str) –

    ID of the finding to delete.

Returns:

  • None
Example
1
reptor.api.projects.delete_finding("finding-uuid-here")
Source code in reptor/api/ProjectsAPI.py
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
def delete_finding(self, finding_id: str) -> None:
    """Deletes a finding from the current project.

    Args:
        finding_id (str): ID of the finding to delete.

    Returns:
        :

    Example:
        ```python
        reptor.api.projects.delete_finding("finding-uuid-here")
        ```
    """
    url = urljoin(self.base_endpoint, f"{self.project_id}/findings/{finding_id}/")
    self.delete(url)