diff --git a/NOTICE b/NOTICE index 76c7f2c7673..2ef9115ec9a 100644 --- a/NOTICE +++ b/NOTICE @@ -2,7 +2,7 @@ DefectDojo is licensed under the 3-Clause BSD License: https://github.com/Defect However, DefectDojo's dependencies may have different licensing requirements and terms. -Complete source code for DefectDojo dependencies are made available on PyPi: https://pypi.org/ +Complete source code for DefectDojo dependencies are made available on PyPi: https://pypi.org/ THIRD-PARTY SOFTWARE NOTICES FOR DEFECTDOJO @@ -69,31 +69,31 @@ BSD License Copyright (C) 2008, 2009 Michael Trier and contributors All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name of the GitPython project nor the names of -its contributors may be used to endorse or promote products derived +* Neither the name of the GitPython project nor the names of +its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @@ -288,7 +288,7 @@ files names COPYING in subdirectories where applicable. Copyright © 2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020 Google, Inc. Copyright © 2018,2019,2020 Ebrahim Byagowi -Copyright © 2019,2020 Facebook, Inc. +Copyright © 2019,2020 Facebook, Inc. Copyright © 2012 Mozilla Foundation Copyright © 2011 Codethink Limited Copyright © 2008,2010 Nokia Corporation and/or its subsidiary(-ies) @@ -458,7 +458,7 @@ LIBTIFF Copyright (c) 1988-1997 Sam Leffler Copyright (c) 1991-1997 Silicon Graphics, Inc. -Permission to use, copy, modify, distribute, and sell this software and +Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that (i) the above copyright notices and this permission notice appear in all copies of the software and related documentation, and (ii) the names of @@ -466,15 +466,15 @@ Sam Leffler and Silicon Graphics may not be used in any advertising or publicity relating to the software without the specific, prior written permission of Sam Leffler and Silicon Graphics. -THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, -EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY -WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. +THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, +EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY +WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF -LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF +LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---- @@ -516,8 +516,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OPENJPEG * - * The copyright in this software is being made available under the 2-clauses - * BSD License, included below. This software may be subject to other third + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third * party and contributor rights, including patent rights, and no such rights * are granted under this license. * @@ -528,7 +528,7 @@ OPENJPEG * Copyright (c) 2005, Herve Drolon, FreeImage Team * Copyright (c) 2002-2003, Yannick Verschueren * Copyright (c) 2001-2003, David Janssens - * Copyright (c) 2011-2012, Centre National d'Etudes Spatiales (CNES), France + * Copyright (c) 2011-2012, Centre National d'Etudes Spatiales (CNES), France * Copyright (c) 2012, CS Systemes d'Information, France * * All rights reserved. @@ -1916,32 +1916,6 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -asteval -0.9.25 -OSI Approved :: MIT License -The MIT License - -Copyright (c) 2021 Matthew Newville, The University of Chicago - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - attrs 21.2.0 MIT License @@ -2136,27 +2110,27 @@ MIT License Except when otherwise stated (look for LICENSE files in directories or information at the beginning of each file) all software and -documentation is licensed as follows: +documentation is licensed as follows: The MIT License - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or - sell copies of the Software, and to permit persons to whom the + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -2889,8 +2863,8 @@ license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) -2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Python Software Foundation; -All Rights Reserved" are retained in Python alone or in any derivative +2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on @@ -3119,10 +3093,10 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. @@ -3256,26 +3230,26 @@ BSD License Copyright (c) Alex Gaynor and individual contributors. All rights reserved. -Redistribution and use in source and binary forms, with or without +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * The names of its contributors may not be used to endorse or promote products + * The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @@ -3617,10 +3591,10 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. @@ -3814,10 +3788,10 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. @@ -3999,7 +3973,7 @@ Copyright (c) 2001-2011 Python Software Foundation License: PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 See http://www.opensource.org/licenses/Python-2.0 for full terms Note: backport changes by Raymond were originally distributed under MIT - license, but since the original license for Python is more + license, but since the original license for Python is more restrictive than MIT, code cannot be released under its terms and still adheres to the limitations of Python license. @@ -4010,44 +3984,44 @@ BSD License Copyright (C) 2010, 2011 Sebastian Thiel and contributors All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name of the GitDB project nor the names of -its contributors may be used to endorse or promote products derived +* Neither the name of the GitDB project nor the names of +its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Additional Licenses ------------------- -The files at +The files at gitdb/test/fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx -and -gitdb/test/fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack -are licensed under GNU GPL as part of the git source repository, +and +gitdb/test/fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack +are licensed under GNU GPL as part of the git source repository, see http://en.wikipedia.org/wiki/Git_%28software%29 for more information. -They are not required for the actual operation, which is why they are not found +They are not required for the actual operation, which is why they are not found in the distribution package. @@ -6397,15 +6371,15 @@ of the rest of the package. The isoschematron implementation uses several XSL and RelaxNG resources: * The (XML syntax) RelaxNG schema for schematron, copyright International - Organization for Standardization (see + Organization for Standardization (see src/lxml/isoschematron/resources/rng/iso-schematron.rng for the license text) * The skeleton iso-schematron-xlt1 pure-xslt schematron implementation xsl stylesheets, copyright Rick Jelliffe and Academia Sinica Computing - Center, Taiwan (see the xsl files here for the license text: + Center, Taiwan (see the xsl files here for the license text: src/lxml/isoschematron/resources/xsl/iso-schematron-xslt1/) * The xsd/rng schema schematron extraction xsl transformations are unlicensed - and copyright the respective authors as noted (see + and copyright the respective authors as noted (see src/lxml/isoschematron/resources/xsl/RNG2Schtrn.xsl and src/lxml/isoschematron/resources/xsl/XSD2Schtrn.xsl) @@ -7071,7 +7045,7 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, @@ -7079,8 +7053,8 @@ modification, are permitted provided that the following conditions are met: and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF @@ -7088,7 +7062,7 @@ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +POSSIBILITY OF SUCH DAMAGE. pyasn1-modules @@ -7100,7 +7074,7 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, @@ -7108,8 +7082,8 @@ modification, are permitted provided that the following conditions are met: and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF @@ -7117,7 +7091,7 @@ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +POSSIBILITY OF SUCH DAMAGE. pycparser @@ -7131,24 +7105,24 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, this +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name of Eli Bendersky nor the names of its contributors may - be used to endorse or promote products derived from this software without +* Neither the name of Eli Bendersky nor the names of its contributors may + be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @@ -8226,31 +8200,31 @@ BSD License Copyright (C) 2010, 2011 Sebastian Thiel and contributors All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name of the async project nor the names of -its contributors may be used to endorse or promote products derived +* Neither the name of the async project nor the names of +its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/content/supported_tools/parsers/file/cloudflare_insights.md b/docs/content/supported_tools/parsers/file/cloudflare_insights.md index 035ed4f7033..1204ea9bda8 100644 --- a/docs/content/supported_tools/parsers/file/cloudflare_insights.md +++ b/docs/content/supported_tools/parsers/file/cloudflare_insights.md @@ -3,7 +3,7 @@ title: "Cloudflare Insights" toc_hide: true --- -Import Cloudflare Insights findings using the **CSV export** provided by Cloudflare. +Import Cloudflare Insights findings using the **CSV export** or via api the **JSON output** provided by Cloudflare. ### Sample Scan Data Sample Cloudflare Insights files can be found [here](https://github.com/DefectDojo/django-DefectDojo/tree/master/unittests/scans/cloudflare_insights). diff --git a/dojo/__init__.py b/dojo/__init__.py index fe74ae3fec3..31d0198e16f 100644 --- a/dojo/__init__.py +++ b/dojo/__init__.py @@ -5,5 +5,5 @@ from .celery import app as celery_app # noqa: F401 __version__ = "2.55.0-dev" -__url__ = "https://github.com/DefectDojo/django-DefectDojo" -__docs__ = "https://documentation.defectdojo.com" +__url__ = "https://github.com/DefectDojo/django-DefectDojo" # noqa: RUF067 +__docs__ = "https://documentation.defectdojo.com" # noqa: RUF067 diff --git a/dojo/api_v2/permissions.py b/dojo/api_v2/permissions.py index 421fb87b526..905ddf99b58 100644 --- a/dojo/api_v2/permissions.py +++ b/dojo/api_v2/permissions.py @@ -1,5 +1,6 @@ import re +from django.db.models import Model from django.shortcuts import get_object_or_404 from rest_framework import permissions, serializers from rest_framework.exceptions import ( @@ -7,6 +8,7 @@ PermissionDenied, ValidationError, ) +from rest_framework.request import Request from dojo.authorization.authorization import ( user_has_configuration_permission, @@ -29,7 +31,7 @@ ) -def check_post_permission(request, post_model, post_pk, post_permission): +def check_post_permission(request: Request, post_model: Model, post_pk: str | list[str], post_permission: int) -> bool: if request.method == "POST": if request.data.get(post_pk) is None: msg = f"Unable to check for permissions: Attribute '{post_pk}' is required" @@ -40,13 +42,13 @@ def check_post_permission(request, post_model, post_pk, post_permission): def check_object_permission( - request, - obj, - get_permission, - put_permission, - delete_permission, - post_permission=None, -): + request: Request, + obj: Model, + get_permission: int, + put_permission: int, + delete_permission: int, + post_permission: int | None = None, +) -> bool: if request.method == "GET": return user_has_permission(request.user, obj, get_permission) if request.method in {"PUT", "PATCH"}: @@ -507,6 +509,25 @@ def has_object_permission(self, request, view, obj): ) +class UserHasAssetPermission(permissions.BasePermission): + def has_permission(self, request, view): + return check_post_permission( + request, + Product_Type, + "organization", + Permissions.Product_Type_Add_Product, + ) + + def has_object_permission(self, request, view, obj): + return check_object_permission( + request, + obj, + Permissions.Product_View, + Permissions.Product_Edit, + Permissions.Product_Delete, + ) + + class UserHasProductMemberPermission(permissions.BasePermission): def has_permission(self, request, view): return check_post_permission( @@ -523,6 +544,22 @@ def has_object_permission(self, request, view, obj): ) +class UserHasAssetMemberPermission(permissions.BasePermission): + def has_permission(self, request, view): + return check_post_permission( + request, Product, "asset", Permissions.Product_Manage_Members, + ) + + def has_object_permission(self, request, view, obj): + return check_object_permission( + request, + obj, + Permissions.Product_View, + Permissions.Product_Manage_Members, + Permissions.Product_Member_Delete, + ) + + class UserHasProductGroupPermission(permissions.BasePermission): def has_permission(self, request, view): return check_post_permission( @@ -539,6 +576,22 @@ def has_object_permission(self, request, view, obj): ) +class UserHasAssetGroupPermission(permissions.BasePermission): + def has_permission(self, request, view): + return check_post_permission( + request, Product, "asset", Permissions.Product_Group_Add, + ) + + def has_object_permission(self, request, view, obj): + return check_object_permission( + request, + obj, + Permissions.Product_Group_View, + Permissions.Product_Group_Edit, + Permissions.Product_Group_Delete, + ) + + class UserHasProductTypePermission(permissions.BasePermission): def has_permission(self, request, view): if request.method == "POST": @@ -557,6 +610,24 @@ def has_object_permission(self, request, view, obj): ) +class UserHasOrganizationPermission(permissions.BasePermission): + def has_permission(self, request, view): + if request.method == "POST": + return user_has_global_permission( + request.user, Permissions.Product_Type_Add, + ) + return True + + def has_object_permission(self, request, view, obj): + return check_object_permission( + request, + obj, + Permissions.Product_Type_View, + Permissions.Product_Type_Edit, + Permissions.Product_Type_Delete, + ) + + class UserHasProductTypeMemberPermission(permissions.BasePermission): def has_permission(self, request, view): return check_post_permission( @@ -576,6 +647,25 @@ def has_object_permission(self, request, view, obj): ) +class UserHasOrganizationMemberPermission(permissions.BasePermission): + def has_permission(self, request, view): + return check_post_permission( + request, + Product_Type, + "organization", + Permissions.Product_Type_Manage_Members, + ) + + def has_object_permission(self, request, view, obj): + return check_object_permission( + request, + obj, + Permissions.Product_Type_View, + Permissions.Product_Type_Manage_Members, + Permissions.Product_Type_Member_Delete, + ) + + class UserHasProductTypeGroupPermission(permissions.BasePermission): def has_permission(self, request, view): return check_post_permission( @@ -595,6 +685,25 @@ def has_object_permission(self, request, view, obj): ) +class UserHasOrganizationGroupPermission(permissions.BasePermission): + def has_permission(self, request, view): + return check_post_permission( + request, + Product_Type, + "organization", + Permissions.Product_Type_Group_Add, + ) + + def has_object_permission(self, request, view, obj): + return check_object_permission( + request, + obj, + Permissions.Product_Type_Group_View, + Permissions.Product_Type_Group_Edit, + Permissions.Product_Type_Group_Delete, + ) + + class UserHasReimportPermission(permissions.BasePermission): def has_permission(self, request, view): # permission check takes place before validation, so we don't have access to serializer.validated_data() @@ -739,6 +848,25 @@ def has_object_permission(self, request, view, obj): ) +class UserHasAssetAPIScanConfigurationPermission(permissions.BasePermission): + def has_permission(self, request, view): + return check_post_permission( + request, + Product, + "asset", + Permissions.Product_API_Scan_Configuration_Add, + ) + + def has_object_permission(self, request, view, obj): + return check_object_permission( + request, + obj, + Permissions.Product_API_Scan_Configuration_View, + Permissions.Product_API_Scan_Configuration_Edit, + Permissions.Product_API_Scan_Configuration_Delete, + ) + + class UserHasJiraProductPermission(permissions.BasePermission): def has_permission(self, request, view): if request.method == "POST": diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index 6bc18b5115f..9a17e4be985 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -1472,8 +1472,15 @@ class Meta: exclude = ("inherited_tags",) +class TestTypeCreateSerializer(serializers.ModelSerializer): + + class Meta: + model = Test_Type + exclude = ("dynamically_generated",) + + class TestTypeSerializer(serializers.ModelSerializer): - tags = TagListSerializerField(required=False) + name = serializers.ReadOnlyField() class Meta: model = Test_Type diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index e8c7d278e65..382a8952dc7 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -751,7 +751,7 @@ def download_proof(self, request, pk=None): # send file response = FileResponse( file_handle, - content_type=f"{mimetypes.guess_type(str(file_path))}", + content_type=mimetypes.guess_type(str(file_path))[0] or "application/octet-stream", status=status.HTTP_200_OK, ) response["Content-Length"] = file_object.size @@ -2263,6 +2263,11 @@ class TestTypesViewSet( def get_queryset(self): return Test_Type.objects.all().order_by("id") + def get_serializer_class(self): + if self.action == "create": + return serializers.TestTypeCreateSerializer + return serializers.TestTypeSerializer + # @extend_schema_view(**schema_with_prefetch()) # Nested models with prefetch make the response schema too long for Swagger UI diff --git a/dojo/asset/api/filters.py b/dojo/asset/api/filters.py index 991fd329ac8..91281a9ebe2 100644 --- a/dojo/asset/api/filters.py +++ b/dojo/asset/api/filters.py @@ -8,12 +8,13 @@ CharFieldInFilter, DateRangeFilter, DojoFilter, + MultipleChoiceFilter, NumberInFilter, ProductSLAFilter, - custom_filter, ) from dojo.labels import get_labels from dojo.models import ( + Product, Product_API_Scan_Configuration, Product_Group, Product_Member, @@ -38,18 +39,18 @@ class ApiAssetFilter(DojoFilter): name = CharFilter(lookup_expr="icontains") name_exact = CharFilter(field_name="name", lookup_expr="iexact") description = CharFilter(lookup_expr="icontains") - business_criticality = CharFilter(method=custom_filter, field_name="business_criticality") - platform = CharFilter(method=custom_filter, field_name="platform") - lifecycle = CharFilter(method=custom_filter, field_name="lifecycle") - origin = CharFilter(method=custom_filter, field_name="origin") + business_criticality = MultipleChoiceFilter(choices=Product.BUSINESS_CRITICALITY_CHOICES) + platform = MultipleChoiceFilter(choices=Product.PLATFORM_CHOICES) + lifecycle = MultipleChoiceFilter(choices=Product.LIFECYCLE_CHOICES) + origin = MultipleChoiceFilter(choices=Product.ORIGIN_CHOICES) # NumberInFilter id = NumberInFilter(field_name="id", lookup_expr="in") asset_manager = NumberInFilter(field_name="product_manager", lookup_expr="in") technical_contact = NumberInFilter(field_name="technical_contact", lookup_expr="in") team_manager = NumberInFilter(field_name="team_manager", lookup_expr="in") - prod_type = NumberInFilter(field_name="prod_type", lookup_expr="in") + organization = NumberInFilter(field_name="prod_type", lookup_expr="in") tid = NumberInFilter(field_name="tid", lookup_expr="in") - prod_numeric_grade = NumberInFilter(field_name="prod_numeric_grade", lookup_expr="in") + asset_numeric_grade = NumberInFilter(field_name="prod_numeric_grade", lookup_expr="in") user_records = NumberInFilter(field_name="user_records", lookup_expr="in") regulations = NumberInFilter(field_name="regulations", lookup_expr="in") @@ -80,7 +81,7 @@ class ApiAssetFilter(DojoFilter): ("tid", "tid"), ("name", "name"), ("created", "created"), - ("prod_numeric_grade", "prod_numeric_grade"), + ("prod_numeric_grade", "asset_numeric_grade"), ("business_criticality", "business_criticality"), ("platform", "platform"), ("lifecycle", "lifecycle"), @@ -97,8 +98,8 @@ class ApiAssetFilter(DojoFilter): ("team_manager", "team_manager"), ("team_manager__first_name", "team_manager__first_name"), ("team_manager__last_name", "team_manager__last_name"), - ("prod_type", "prod_type"), - ("prod_type__name", "prod_type__name"), + ("prod_type", "organization"), + ("prod_type__name", "organization__name"), ("updated", "updated"), ("user_records", "user_records"), ), diff --git a/dojo/asset/api/serializers.py b/dojo/asset/api/serializers.py index 688d772ce9b..4b3bd0e9d5d 100644 --- a/dojo/asset/api/serializers.py +++ b/dojo/asset/api/serializers.py @@ -37,11 +37,17 @@ class AssetSerializer(serializers.ModelSerializer): # V3 fields asset_meta = ProductMetaSerializer(source="product_meta", read_only=True, many=True) organization = RelatedOrganizationField(source="prod_type") - asset_numeric_grade = serializers.IntegerField(source="prod_numeric_grade") - enable_asset_tag_inheritance = serializers.BooleanField(source="enable_product_tag_inheritance") + asset_numeric_grade = serializers.IntegerField(source="prod_numeric_grade", required=False, allow_null=True) + enable_asset_tag_inheritance = serializers.BooleanField(source="enable_product_tag_inheritance", required=False, default=False) asset_managers = serializers.PrimaryKeyRelatedField( source="product_manager", - queryset=Dojo_User.objects.exclude(is_active=False)) + queryset=Dojo_User.objects.exclude(is_active=False), + required=False, allow_null=True, + ) + business_criticality = serializers.ChoiceField(choices=Product.BUSINESS_CRITICALITY_CHOICES, allow_blank=True, allow_null=True, required=False) + platform = serializers.ChoiceField(choices=Product.PLATFORM_CHOICES, allow_blank=True, allow_null=True, required=False) + lifecycle = serializers.ChoiceField(choices=Product.LIFECYCLE_CHOICES, allow_blank=True, allow_null=True, required=False) + origin = serializers.ChoiceField(choices=Product.ORIGIN_CHOICES, allow_blank=True, allow_null=True, required=False) class Meta: model = Product diff --git a/dojo/asset/api/views.py b/dojo/asset/api/views.py index d3a873f97da..0e01499c466 100644 --- a/dojo/asset/api/views.py +++ b/dojo/asset/api/views.py @@ -43,7 +43,7 @@ class AssetAPIScanConfigurationViewSet( filterset_class = AssetAPIScanConfigurationFilterSet permission_classes = ( IsAuthenticated, - permissions.UserHasProductAPIScanConfigurationPermission, + permissions.UserHasAssetAPIScanConfigurationPermission, ) def get_queryset(self): @@ -68,7 +68,7 @@ class AssetViewSet( filterset_class = ApiAssetFilter permission_classes = ( IsAuthenticated, - permissions.UserHasProductPermission, + permissions.UserHasAssetPermission, ) def get_queryset(self): @@ -138,7 +138,7 @@ class AssetMemberViewSet( filterset_class = AssetMemberFilterSet permission_classes = ( IsAuthenticated, - permissions.UserHasProductMemberPermission, + permissions.UserHasAssetMemberPermission, ) def get_queryset(self): @@ -166,7 +166,7 @@ class AssetGroupViewSet( filterset_class = AssetGroupFilterSet permission_classes = ( IsAuthenticated, - permissions.UserHasProductGroupPermission, + permissions.UserHasAssetGroupPermission, ) def get_queryset(self): diff --git a/dojo/authorization/authorization.py b/dojo/authorization/authorization.py index b410bb3a95d..840eeb7ea35 100644 --- a/dojo/authorization/authorization.py +++ b/dojo/authorization/authorization.py @@ -1,4 +1,5 @@ from django.core.exceptions import PermissionDenied +from django.db.models import Model, QuerySet from dojo.authorization.roles_permissions import ( Permissions, @@ -11,6 +12,7 @@ Cred_Mapping, Dojo_Group, Dojo_Group_Member, + Dojo_User, Endpoint, Engagement, Finding, @@ -30,7 +32,7 @@ from dojo.request_cache import cache_for_request -def user_has_configuration_permission(user, permission): +def user_has_configuration_permission(user: Dojo_User, permission: str): if not user: return False @@ -40,7 +42,7 @@ def user_has_configuration_permission(user, permission): return user.has_perm(permission) -def user_is_superuser_or_global_owner(user): +def user_is_superuser_or_global_owner(user: Dojo_User) -> bool: """ Returns True if the user is a superuser or has a global role (directly or via group membership) whose Role.is_owner is True. @@ -69,7 +71,7 @@ def user_is_superuser_or_global_owner(user): return False -def user_has_permission(user, obj, permission): +def user_has_permission(user: Dojo_User, obj: Model, permission: int) -> bool: if user.is_anonymous: return False @@ -229,7 +231,7 @@ def user_has_permission(user, obj, permission): raise NoAuthorizationImplementedError(msg) -def user_has_global_permission(user, permission): +def user_has_global_permission(user: Dojo_User, permission: int) -> bool: if not user: return False @@ -263,22 +265,22 @@ def user_has_global_permission(user, permission): return False -def user_has_configuration_permission_or_403(user, permission): +def user_has_configuration_permission_or_403(user: Dojo_User, permission: str) -> None: if not user_has_configuration_permission(user, permission): raise PermissionDenied -def user_has_permission_or_403(user, obj, permission): +def user_has_permission_or_403(user: Dojo_User, obj: Model, permission: int) -> None: if not user_has_permission(user, obj, permission): raise PermissionDenied -def user_has_global_permission_or_403(user, permission): +def user_has_global_permission_or_403(user: Dojo_User, permission: int) -> None: if not user_has_global_permission(user, permission): raise PermissionDenied -def get_roles_for_permission(permission): +def get_roles_for_permission(permission: int) -> set[int]: if not Permissions.has_value(permission): msg = f"Permission {permission} does not exist" raise PermissionDoesNotExistError(msg) @@ -291,7 +293,7 @@ def get_roles_for_permission(permission): return roles_for_permissions -def role_has_permission(role, permission): +def role_has_permission(role: int, permission: int) -> bool: if role is None: return False if not Roles.has_value(role): @@ -304,7 +306,7 @@ def role_has_permission(role, permission): return permission in permissions -def role_has_global_permission(role, permission): +def role_has_global_permission(role: int, permission: int) -> bool: if role is None: return False if not Roles.has_value(role): @@ -332,12 +334,12 @@ def __init__(self, message): self.message = message -def get_product_member(user, product): +def get_product_member(user: Dojo_User, product: Product) -> Product_Member | None: return get_product_member_dict(user).get(product.id) @cache_for_request -def get_product_member_dict(user): +def get_product_member_dict(user: Dojo_User) -> dict[int, Product_Member]: pm_dict = {} for product_member in ( Product_Member.objects.select_related("product") @@ -348,12 +350,12 @@ def get_product_member_dict(user): return pm_dict -def get_product_type_member(user, product_type): +def get_product_type_member(user: Dojo_User, product_type: Product_Type) -> Product_Type_Member | None: return get_product_type_member_dict(user).get(product_type.id) @cache_for_request -def get_product_type_member_dict(user): +def get_product_type_member_dict(user: Dojo_User) -> dict[int, Product_Type_Member]: ptm_dict = {} for product_type_member in ( Product_Type_Member.objects.select_related("product_type") @@ -364,12 +366,12 @@ def get_product_type_member_dict(user): return ptm_dict -def get_product_groups(user, product): +def get_product_groups(user: Dojo_User, product: Product) -> list[Product_Group]: return get_product_groups_dict(user).get(product.id, []) @cache_for_request -def get_product_groups_dict(user): +def get_product_groups_dict(user: Dojo_User) -> dict[int, list[Product_Group]]: pg_dict = {} for product_group in ( Product_Group.objects.select_related("product") @@ -382,12 +384,12 @@ def get_product_groups_dict(user): return pg_dict -def get_product_type_groups(user, product_type): +def get_product_type_groups(user: Dojo_User, product_type: Product_Type) -> list[Product_Type_Group]: return get_product_type_groups_dict(user).get(product_type.id, []) @cache_for_request -def get_product_type_groups_dict(user): +def get_product_type_groups_dict(user: Dojo_User) -> dict[int, list[Product_Type_Group]]: pgt_dict = {} for product_type_group in ( Product_Type_Group.objects.select_related("product_type") @@ -404,16 +406,16 @@ def get_product_type_groups_dict(user): @cache_for_request -def get_groups(user): +def get_groups(user: Dojo_User) -> QuerySet[Dojo_Group]: return Dojo_Group.objects.select_related("global_role").filter(users=user) -def get_group_member(user, group): +def get_group_member(user: Dojo_User, group: Dojo_Group) -> dict[int, Dojo_Group_Member]: return get_group_members_dict(user).get(group.id) @cache_for_request -def get_group_members_dict(user): +def get_group_members_dict(user: Dojo_User) -> dict[int, Dojo_Group_Member]: gu_dict = {} for group_member in ( Dojo_Group_Member.objects.select_related("group") diff --git a/dojo/engagement/views.py b/dojo/engagement/views.py index ebc2e09ce6b..405f3953347 100644 --- a/dojo/engagement/views.py +++ b/dojo/engagement/views.py @@ -1568,7 +1568,7 @@ def download_risk_acceptance(request, eid, raid): (Path(settings.MEDIA_ROOT) / "risk_acceptance.path.name").open(mode="rb"))) response["Content-Disposition"] = f'attachment; filename="{risk_acceptance.filename()}"' mimetype, _encoding = mimetypes.guess_type(risk_acceptance.path.name) - response["Content-Type"] = mimetype + response["Content-Type"] = mimetype or "application/octet-stream" return response diff --git a/dojo/finding/views.py b/dojo/finding/views.py index b5bfb593043..ae224afde53 100644 --- a/dojo/finding/views.py +++ b/dojo/finding/views.py @@ -2175,7 +2175,7 @@ def templates(request): @user_has_global_permission(Permissions.Finding_Edit) def export_templates_to_json(request): leads_as_json = serializers.serialize("json", Finding_Template.objects.all()) - return HttpResponse(leads_as_json, content_type="json") + return HttpResponse(leads_as_json, content_type="application/json") def ensure_template_tags_in_finding_model(template): @@ -2444,7 +2444,7 @@ class Original(ImageSpec): response = StreamingHttpResponse(FileIterWrapper(image)) response["Content-Disposition"] = "inline" mimetype, _encoding = mimetypes.guess_type(file_name) - response["Content-Type"] = mimetype + response["Content-Type"] = mimetype or "application/octet-stream" return response diff --git a/dojo/forms.py b/dojo/forms.py index b2b39509933..000dec362a0 100644 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -324,6 +324,17 @@ class Meta: model = Test_Type exclude = ["dynamically_generated"] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + if self.instance.pk: + self.fields["name"].widget.attrs["readonly"] = True + + def clean_name(self): + if self.instance.pk: + return self.instance.name + return self.cleaned_data["name"] + class Development_EnvironmentForm(forms.ModelForm): class Meta: diff --git a/dojo/importers/base_importer.py b/dojo/importers/base_importer.py index 1c0b84687b1..1866daeecb8 100644 --- a/dojo/importers/base_importer.py +++ b/dojo/importers/base_importer.py @@ -767,11 +767,13 @@ def process_finding_groups( self, finding: Finding, group_names_to_findings_dict: dict, - ) -> None: + ) -> bool: """ Determines how to handle an incoming finding with respect to grouping if finding groups are enabled, use the supplied grouping mechanism to - store a reference of how the finding should be grouped + store a reference of how the finding should be grouped. + + Returns True if the finding was added to a group, False otherwise. """ if self.findings_groups_enabled and self.group_by: # If finding groups are enabled, group all findings by group name @@ -781,6 +783,8 @@ def process_finding_groups( group_names_to_findings_dict[name].append(finding) else: group_names_to_findings_dict[name] = [finding] + return True + return False def process_request_response_pairs( self, diff --git a/dojo/importers/default_importer.py b/dojo/importers/default_importer.py index 95c6adeed3c..2198ba3a703 100644 --- a/dojo/importers/default_importer.py +++ b/dojo/importers/default_importer.py @@ -211,7 +211,13 @@ def process_findings( if self.service is not None: unsaved_finding.service = self.service - # Force parsers to use unsaved_tags (stored in below after saving) + # Parsers shouldn't use the tags field, and use unsaved_tags instead. + # Merge any tags set by parser into unsaved_tags + tags_from_parser = unsaved_finding.tags if isinstance(unsaved_finding.tags, list) else [] + unsaved_tags_from_parser = unsaved_finding.unsaved_tags if isinstance(unsaved_finding.unsaved_tags, list) else [] + merged_tags = unsaved_tags_from_parser + tags_from_parser + if merged_tags: + unsaved_finding.unsaved_tags = merged_tags unsaved_finding.tags = None finding = self.process_cve(unsaved_finding) # Calculate hash_code before saving based on unsaved_endpoints and unsaved_vulnerability_ids @@ -221,7 +227,7 @@ def process_findings( unsaved_finding.save_no_options() # Determine how the finding should be grouped - self.process_finding_groups( + finding_will_be_grouped = self.process_finding_groups( finding, group_names_to_findings_dict, ) @@ -244,7 +250,7 @@ def process_findings( # all data is already saved on the finding, we only need to trigger post processing in batches logger.debug("process_findings: self.push_to_jira=%s, self.findings_groups_enabled=%s, self.group_by=%s", self.push_to_jira, self.findings_groups_enabled, self.group_by) - push_to_jira = self.push_to_jira and (not self.findings_groups_enabled or not self.group_by) + push_to_jira = self.push_to_jira and ((not self.findings_groups_enabled or not self.group_by) or not finding_will_be_grouped) logger.debug("process_findings: computed push_to_jira=%s", push_to_jira) batch_finding_ids.append(finding.id) diff --git a/dojo/importers/default_reimporter.py b/dojo/importers/default_reimporter.py index 1ddabf5e87f..d80b0de8b55 100644 --- a/dojo/importers/default_reimporter.py +++ b/dojo/importers/default_reimporter.py @@ -358,6 +358,8 @@ def process_findings( unsaved_finding, existing_finding, ) + # Findings that have already exist cannot be moved to into a group + finding_will_be_grouped = False # Determine if we should skip the rest of the loop if force_continue: continue @@ -373,7 +375,7 @@ def process_findings( self.user, ) else: - finding = self.process_finding_that_was_not_matched(unsaved_finding) + finding, finding_will_be_grouped = self.process_finding_that_was_not_matched(unsaved_finding) # Add newly created finding to candidates for subsequent findings in this batch self.add_new_finding_to_candidates( @@ -391,7 +393,7 @@ def process_findings( unsaved_finding, ) # all data is already saved on the finding, we only need to trigger post processing in batches - push_to_jira = self.push_to_jira and (not self.findings_groups_enabled or not self.group_by) + push_to_jira = self.push_to_jira and ((not self.findings_groups_enabled or not self.group_by) or not finding_will_be_grouped) batch_finding_ids.append(finding.id) # Post-processing batches (deduplication, rules, etc.) are separate from matching batches. @@ -816,7 +818,7 @@ def process_matched_active_finding( def process_finding_that_was_not_matched( self, unsaved_finding: Finding, - ) -> Finding: + ) -> tuple[Finding, bool]: """Create a new finding from the one parsed from the report""" # Set some explicit settings unsaved_finding.reporter = self.user @@ -844,7 +846,7 @@ def process_finding_that_was_not_matched( f"({finding.component_name} - {finding.component_version})", ) # Manage the finding grouping selection - self.process_finding_groups( + finding_will_be_grouped = self.process_finding_groups( unsaved_finding, self.group_names_to_findings_dict, ) @@ -852,7 +854,7 @@ def process_finding_that_was_not_matched( self.new_items.append(unsaved_finding) # Process any request/response pairs self.process_request_response_pairs(unsaved_finding) - return unsaved_finding + return unsaved_finding, finding_will_be_grouped def reconcile_vulnerability_ids( self, @@ -901,7 +903,13 @@ def finding_post_processing( self.endpoint_manager.chunk_endpoints_and_disperse(finding, finding_from_report.unsaved_endpoints) if len(self.endpoints_to_add) > 0: self.endpoint_manager.chunk_endpoints_and_disperse(finding, self.endpoints_to_add) - # Parsers must use unsaved_tags to store tags, so we can clean them + # Parsers shouldn't use the tags field, and use unsaved_tags instead. + # Merge any tags set by parser into unsaved_tags + tags_from_parser = finding_from_report.tags if isinstance(finding_from_report.tags, list) else [] + unsaved_tags_from_parser = finding_from_report.unsaved_tags if isinstance(finding_from_report.unsaved_tags, list) else [] + merged_tags = unsaved_tags_from_parser + tags_from_parser + if merged_tags: + finding_from_report.unsaved_tags = merged_tags if finding_from_report.unsaved_tags: cleaned_tags = clean_tags(finding_from_report.unsaved_tags) if isinstance(cleaned_tags, list): diff --git a/dojo/notes/views.py b/dojo/notes/views.py index ca91d8332cb..dc8cc6ef6a6 100644 --- a/dojo/notes/views.py +++ b/dojo/notes/views.py @@ -1,10 +1,11 @@ # Standard library imports import logging +from typing import Literal, get_args # Third party imports from django.contrib import messages from django.core.exceptions import PermissionDenied -from django.http import HttpResponseRedirect +from django.http import Http404, HttpRequest, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.utils import timezone @@ -12,40 +13,55 @@ from dojo.authorization.authorization import user_has_permission_or_403 from dojo.authorization.roles_permissions import Permissions +from dojo.cred.queries import get_authorized_cred_mappings +from dojo.engagement.queries import get_authorized_engagements +from dojo.finding.queries import get_authorized_findings # Local application/library imports from dojo.forms import DeleteNoteForm, NoteForm, TypedNoteForm from dojo.models import Cred_User, Engagement, Finding, Note_Type, NoteHistory, Notes, Test +from dojo.test.queries import get_authorized_tests logger = logging.getLogger(__name__) +SUPPORTED_PAGES = Literal["engagement", "test", "finding", "cred"] -def delete_note(request, note_id, page, objid): - note = get_object_or_404(Notes, id=note_id) - reverse_url = None - object_id = None +def _get_page_details(request: HttpRequest, note_id: int, page: SUPPORTED_PAGES | None, objid: int) -> tuple[Notes, Engagement | Test | Finding | Cred_User, int, str]: + note = get_object_or_404(Notes, id=note_id) + # Quick check to make sure we have a valid page + if page is None or page not in get_args(SUPPORTED_PAGES): + raise PermissionDenied + # Get the real object based on page type if page == "engagement": - obj = get_object_or_404(Engagement, id=objid) - object_id = obj.id + obj = get_authorized_engagements(Permissions.Engagement_View).filter(id=objid).first() reverse_url = "view_engagement" elif page == "test": - obj = get_object_or_404(Test, id=objid) - object_id = obj.id + obj = get_authorized_tests(Permissions.Test_View).filter(id=objid).first() reverse_url = "view_test" elif page == "finding": - obj = get_object_or_404(Finding, id=objid) - object_id = obj.id + obj = get_authorized_findings(Permissions.Finding_View).filter(id=objid).first() reverse_url = "view_finding" elif page == "cred": - obj = get_object_or_404(Cred_User, id=objid) - object_id = obj.id + obj = get_authorized_cred_mappings(Permissions.Cred_View).filter(id=objid).first() reverse_url = "view_cred_details" + else: + # If we get here, something is wrong, so let's just raise PermissionDenied + raise PermissionDenied + # Ensure we have an object back, and if not, raise the 404 like before + if obj is None: + raise Http404 + # Next check if the note supplied is even attached to the object + if obj.notes.filter(id=note.id).exists() is False: + raise PermissionDenied + + return note, obj, obj.id, reverse_url + +def delete_note(request: HttpRequest, note_id: int, page: SUPPORTED_PAGES, objid: int) -> HttpResponseRedirect: + note, obj, object_id, reverse_url = _get_page_details(request, note_id, page, objid) form = DeleteNoteForm(request.POST, instance=note) - if page is None: - raise PermissionDenied if str(request.user) != note.author.username: user_has_permission_or_403(request.user, obj, Permissions.Note_Delete) @@ -64,26 +80,8 @@ def delete_note(request, note_id, page, objid): return HttpResponseRedirect(reverse(reverse_url, args=(object_id, ))) -def edit_note(request, note_id, page, objid): - note = get_object_or_404(Notes, id=note_id) - reverse_url = None - object_id = None - - if page is None: - raise PermissionDenied - - if page == "engagement": - obj = get_object_or_404(Engagement, id=objid) - object_id = obj.id - reverse_url = "view_engagement" - elif page == "test": - obj = get_object_or_404(Test, id=objid) - object_id = obj.id - reverse_url = "view_test" - elif page == "finding": - obj = get_object_or_404(Finding, id=objid) - object_id = obj.id - reverse_url = "view_finding" +def edit_note(request: HttpRequest, note_id: int, page: SUPPORTED_PAGES, objid: int) -> HttpResponseRedirect: + note, obj, object_id, reverse_url = _get_page_details(request, note_id, page, objid) if str(request.user) != note.author.username: user_has_permission_or_403(request.user, obj, Permissions.Note_Edit) @@ -141,26 +139,9 @@ def edit_note(request, note_id, page, objid): }) -def note_history(request, note_id, page, objid): - note = get_object_or_404(Notes, id=note_id) - reverse_url = None - object_id = None - - if page == "engagement": - obj = get_object_or_404(Engagement, id=objid) - object_id = obj.id - reverse_url = "view_engagement" - elif page == "test": - obj = get_object_or_404(Test, id=objid) - object_id = obj.id - reverse_url = "view_test" - elif page == "finding": - obj = get_object_or_404(Finding, id=objid) - object_id = obj.id - reverse_url = "view_finding" +def note_history(request: HttpRequest, note_id: int, page: SUPPORTED_PAGES, objid: int) -> HttpResponseRedirect: + note, obj, object_id, reverse_url = _get_page_details(request, note_id, page, objid) - if page is None: - raise PermissionDenied if str(request.user) != note.author.username: user_has_permission_or_403(request.user, obj, Permissions.Note_View_History) diff --git a/dojo/organization/api/serializers.py b/dojo/organization/api/serializers.py index d624c72524d..73eb68e0294 100644 --- a/dojo/organization/api/serializers.py +++ b/dojo/organization/api/serializers.py @@ -51,7 +51,7 @@ def validate(self, data): if self.instance is not None and not data.get("role").is_owner: owners = ( Product_Type_Member.objects.filter( - product_type=data.get("organization"), role__is_owner=True, + product_type=data.get("organization", data.get("product_type")), role__is_owner=True, ) .exclude(id=self.instance.id) .count() @@ -115,8 +115,8 @@ def validate(self, data): class OrganizationSerializer(serializers.ModelSerializer): - critical_asset = serializers.BooleanField(source="critical_product") - key_asset = serializers.BooleanField(source="key_product") + critical_asset = serializers.BooleanField(source="critical_product", default=False) + key_asset = serializers.BooleanField(source="key_product", default=False) class Meta: model = Product_Type diff --git a/dojo/organization/api/views.py b/dojo/organization/api/views.py index dc9f3fc0cc2..0cbbf561eaf 100644 --- a/dojo/organization/api/views.py +++ b/dojo/organization/api/views.py @@ -17,6 +17,7 @@ ) from dojo.organization.api import serializers from dojo.organization.api.filters import ( + OrganizationFilterSet, OrganizationGroupFilterSet, OrganizationMemberFilterSet, ) @@ -36,17 +37,10 @@ class OrganizationViewSet( serializer_class = serializers.OrganizationSerializer queryset = Product_Type.objects.none() filter_backends = (DjangoFilterBackend,) - filterset_fields = [ - "id", - "name", - "critical_product", - "key_product", - "created", - "updated", - ] + filterset_class = OrganizationFilterSet permission_classes = ( IsAuthenticated, - permissions.UserHasProductTypePermission, + permissions.UserHasOrganizationPermission, ) def get_queryset(self): @@ -60,6 +54,9 @@ def perform_create(self, serializer): product_type_data = serializer.data product_type_data.pop("authorization_groups") product_type_data.pop("members") + # Manage custom fields separately with default fields of false + product_type_data["critical_product"] = product_type_data.pop("critical_asset", False) + product_type_data["key_product"] = product_type_data.pop("key_asset", False) member = Product_Type_Member() member.user = self.request.user member.product_type = Product_Type(**product_type_data) @@ -124,7 +121,7 @@ class OrganizationMemberViewSet( filterset_class = OrganizationMemberFilterSet permission_classes = ( IsAuthenticated, - permissions.UserHasProductTypeMemberPermission, + permissions.UserHasOrganizationMemberPermission, ) def get_queryset(self): @@ -166,7 +163,7 @@ class OrganizationGroupViewSet( filterset_class = OrganizationGroupFilterSet permission_classes = ( IsAuthenticated, - permissions.UserHasProductTypeGroupPermission, + permissions.UserHasOrganizationGroupPermission, ) def get_queryset(self): diff --git a/dojo/request_cache/__init__.py b/dojo/request_cache/__init__.py index d39ef4e406f..320f9acd87f 100644 --- a/dojo/request_cache/__init__.py +++ b/dojo/request_cache/__init__.py @@ -5,7 +5,7 @@ # the code has been copied to DefectDojo, to be able to fix issues ourselves. -def get_request_cache(): +def get_request_cache(): # noqa: RUF067 """ Return the current requests cache :return: @@ -13,10 +13,10 @@ def get_request_cache(): return getattr(get_current_request(), "cache", None) -cache_args_kwargs_marker = object() # marker for separating args from kwargs (needs to be global) +cache_args_kwargs_marker = object() # noqa: RUF067 marker for separating args from kwargs (needs to be global) -def cache_calculate_key(*args, **kwargs): +def cache_calculate_key(*args, **kwargs): # noqa: RUF067 """ Calculate the cache key of a function call with args and kwargs Taken from lru_cache @@ -31,7 +31,7 @@ def cache_calculate_key(*args, **kwargs): return str(key) -def cache_for_request(fn): +def cache_for_request(fn): # noqa: RUF067 """ Decorator that allows to cache a function call with parameters and its result only for the current request The result is stored in the memory of the current process diff --git a/dojo/templates/base.html b/dojo/templates/base.html index c562b598cd9..7f9a44c750d 100644 --- a/dojo/templates/base.html +++ b/dojo/templates/base.html @@ -343,7 +343,7 @@ {% trans "Finding Groups" %} - +