|
88 | 88 | from rq.exceptions import NoSuchJobError |
89 | 89 | from rq.job import Job |
90 | 90 | from rq.job import JobStatus |
| 91 | +from scorecode.contrib.django.models import PackageScoreMixin |
| 92 | +from scorecode.contrib.django.models import ScorecardChecksMixin |
91 | 93 | from taggit.managers import TaggableManager |
92 | 94 | from taggit.models import GenericUUIDTaggedItemBase |
93 | 95 | from taggit.models import TaggedItemBase |
@@ -3932,9 +3934,9 @@ class DiscoveredDependency( |
3932 | 3934 |
|
3933 | 3935 | 3. Dependencies can be either direct or transitive: |
3934 | 3936 | - A **direct dependency** is explicitly declared in a package manifest or |
3935 | | - lockfile. |
| 3937 | + lockfile. |
3936 | 3938 | - A **transitive dependency** is not declared directly, but is required by one |
3937 | | - of the project's direct dependencies. |
| 3939 | + of the project's direct dependencies. |
3938 | 3940 |
|
3939 | 3941 | Understanding the distinction between direct and transitive dependencies is |
3940 | 3942 | important for analyzing dependency trees, resolving version conflicts, and |
@@ -4760,3 +4762,99 @@ def create_auth_token(sender, instance=None, created=False, **kwargs): |
4760 | 4762 | """Create an API key token on user creation, using the signal system.""" |
4761 | 4763 | if created: |
4762 | 4764 | Token.objects.create(user_id=instance.pk) |
| 4765 | + |
| 4766 | + |
| 4767 | +class DiscoveredPackageScore(UUIDPKModel, PackageScoreMixin): |
| 4768 | + """Represents a security or quality score for a DiscoveredPackage.""" |
| 4769 | + |
| 4770 | + discovered_package = models.ForeignKey( |
| 4771 | + DiscoveredPackage, |
| 4772 | + related_name="scores", |
| 4773 | + help_text=_("The package for which the score is given"), |
| 4774 | + on_delete=models.CASCADE, |
| 4775 | + editable=False, |
| 4776 | + ) |
| 4777 | + |
| 4778 | + class Meta: |
| 4779 | + verbose_name = "discovered package score" |
| 4780 | + verbose_name_plural = "discovered package scores" |
| 4781 | + ordering = ["-score"] |
| 4782 | + indexes = [ |
| 4783 | + models.Index(fields=["score"]), |
| 4784 | + models.Index(fields=["scoring_tool_version"]), |
| 4785 | + ] |
| 4786 | + |
| 4787 | + def __str__(self): |
| 4788 | + return self.score or str(self.uuid) |
| 4789 | + |
| 4790 | + @classmethod |
| 4791 | + def create_from_scorecard_data( |
| 4792 | + cls, discovered_package, scorecard_data, scoring_tool="ossf-scorecard" |
| 4793 | + ): |
| 4794 | + """Create ScoreCard object from scorecard data and discovered package""" |
| 4795 | + final_data = { |
| 4796 | + "score": scorecard_data.score, |
| 4797 | + "scoring_tool_version": scorecard_data.scoring_tool_version, |
| 4798 | + "scoring_tool_documentation_url": ( |
| 4799 | + scorecard_data.scoring_tool_documentation_url |
| 4800 | + ), |
| 4801 | + "score_date": cls.parse_score_date(scorecard_data.score_date), |
| 4802 | + } |
| 4803 | + |
| 4804 | + scorecard_object = cls.objects.create( |
| 4805 | + **final_data, |
| 4806 | + discovered_package=discovered_package, |
| 4807 | + scoring_tool=scoring_tool, |
| 4808 | + ) |
| 4809 | + |
| 4810 | + for check in scorecard_data.checks: |
| 4811 | + ScorecardCheck.create_from_data(package_score=scorecard_object, check=check) |
| 4812 | + |
| 4813 | + return scorecard_object |
| 4814 | + |
| 4815 | + @classmethod |
| 4816 | + def create_from_package_and_scorecard(cls, scorecard_data, package): |
| 4817 | + score_object = cls.create_from_scorecard_data( |
| 4818 | + discovered_package=package, |
| 4819 | + scorecard_data=scorecard_data, |
| 4820 | + scoring_tool="ossf-scorecard", |
| 4821 | + ) |
| 4822 | + return score_object |
| 4823 | + |
| 4824 | + |
| 4825 | +class ScorecardCheck(UUIDPKModel, ScorecardChecksMixin): |
| 4826 | + """ |
| 4827 | + Represents an individual check within a Scorecard evaluation for a |
| 4828 | + DiscoveredPackageScore. |
| 4829 | + """ |
| 4830 | + |
| 4831 | + package_score = models.ForeignKey( |
| 4832 | + DiscoveredPackageScore, |
| 4833 | + related_name="checks", |
| 4834 | + help_text=_("The checks for which the score is given"), |
| 4835 | + on_delete=models.CASCADE, |
| 4836 | + editable=False, |
| 4837 | + ) |
| 4838 | + |
| 4839 | + class Meta: |
| 4840 | + verbose_name = "scorecard check" |
| 4841 | + verbose_name_plural = "scorecard checks" |
| 4842 | + ordering = ["-check_score"] |
| 4843 | + indexes = [ |
| 4844 | + models.Index(fields=["check_score"]), |
| 4845 | + models.Index(fields=["check_name"]), |
| 4846 | + ] |
| 4847 | + |
| 4848 | + def __str__(self): |
| 4849 | + return self.check_score or str(self.uuid) |
| 4850 | + |
| 4851 | + @classmethod |
| 4852 | + def create_from_data(cls, package_score, check): |
| 4853 | + """Create a ScorecardCheck instance from provided data.""" |
| 4854 | + return cls.objects.create( |
| 4855 | + check_name=check.check_name, |
| 4856 | + check_score=check.check_score, |
| 4857 | + reason=check.reason or "", |
| 4858 | + details=check.details or [], |
| 4859 | + package_score=package_score, |
| 4860 | + ) |
0 commit comments