diff --git a/examples/config.py b/examples/config.py index b5793eeb..c766eab2 100644 --- a/examples/config.py +++ b/examples/config.py @@ -330,3 +330,20 @@ The repository of older versions of applications from the main demo repository. # 'com.facebook.orca', # 'com.android.vending', # ) + +# `fdroid lint` checks licenses in metadata against a built white list. By +# default we will require license metadata to be present and only allow +# licenses approved either by FSF or OSI. We're using the standardized SPDX +# license IDs. (https://spdx.org/licenses/) +# +# We use `python3 -m spdx-license-list print --filter-fsf-or-osi` for +# generating our default list. (https://pypi.org/project/spdx-license-list) +# +# You can override our default list of allowed licenes by setting this option. +# Just supply a custom list of licene names you would like to allow. Setting +# this to `None` disables this lint check. +# +# lint_licenses = ( +# 'Custom-License-A', +# 'Another-License', +# ) diff --git a/fdroidserver/common.py b/fdroidserver/common.py index 55052355..059a641f 100644 --- a/fdroidserver/common.py +++ b/fdroidserver/common.py @@ -57,6 +57,7 @@ from pyasn1_modules import rfc2315 from pyasn1.error import PyAsn1Error import fdroidserver.metadata +import fdroidserver.lint from fdroidserver import _ from fdroidserver.exception import FDroidException, VCSException, NoSubmodulesException,\ BuildException, VerificationException @@ -145,6 +146,7 @@ default_config = { using the tools on https://gitlab.com/u/fdroid. ''', 'archive_older': 0, + 'lint_licenses': fdroidserver.lint.APPROVED_LICENSES, } diff --git a/fdroidserver/lint.py b/fdroidserver/lint.py index 465954cc..86246d36 100644 --- a/fdroidserver/lint.py +++ b/fdroidserver/lint.py @@ -440,10 +440,17 @@ def check_format(app): def check_license_tag(app): - '''Ensure all license tags are in https://spdx.org/license-list''' - if app.License.rstrip('+') not in SPDX: - yield _('Invalid license tag "%s"! Use only tags from https://spdx.org/license-list') \ - % (app.License) + '''Ensure all license tags contain only valid/approved values''' + if config['lint_licenses'] is None: + return + if app.License not in config['lint_licenses']: + if config['lint_licenses'] == APPROVED_LICENSES: + yield _('Unexpected license tag "{}"! Only use FSF or OSI ' + 'approved tags from https://spdx.org/license-list') \ + .format(app.License) + else: + yield _('Unexpected license tag "{}"! Only use license tags ' + 'configured in your config file').format(app.License) def check_extlib_dir(apps): @@ -622,356 +629,175 @@ def main(): sys.exit(1) -# A compiled, public domain list of official SPDX license tags from: -# https://github.com/sindresorhus/spdx-license-list/blob/v4.0.0/spdx-simple.json -# The deprecated license tags have been removed from the list, they are at the -# bottom, starting after the last license tags that start with Z. -# This is at the bottom, since its a long list of data -SPDX = [ - "PublicDomain", # an F-Droid addition, until we can enforce a better option - "0BSD", - "AAL", - "Abstyles", - "Adobe-2006", - "Adobe-Glyph", - "ADSL", - "AFL-1.1", - "AFL-1.2", - "AFL-2.0", - "AFL-2.1", - "AFL-3.0", - "Afmparse", - "AGPL-1.0", - "AGPL-3.0-only", - "AGPL-3.0-or-later", - "Aladdin", - "AMDPLPA", - "AML", - "AMPAS", - "ANTLR-PD", - "Apache-1.0", - "Apache-1.1", - "Apache-2.0", - "APAFML", - "APL-1.0", - "APSL-1.0", - "APSL-1.1", - "APSL-1.2", - "APSL-2.0", - "Artistic-1.0-cl8", - "Artistic-1.0-Perl", - "Artistic-1.0", - "Artistic-2.0", - "Bahyph", - "Barr", - "Beerware", - "BitTorrent-1.0", - "BitTorrent-1.1", - "Borceux", - "BSD-1-Clause", - "BSD-2-Clause-FreeBSD", - "BSD-2-Clause-NetBSD", - "BSD-2-Clause-Patent", - "BSD-2-Clause", - "BSD-3-Clause-Attribution", - "BSD-3-Clause-Clear", - "BSD-3-Clause-LBNL", - "BSD-3-Clause-No-Nuclear-License-2014", - "BSD-3-Clause-No-Nuclear-License", - "BSD-3-Clause-No-Nuclear-Warranty", - "BSD-3-Clause", - "BSD-4-Clause-UC", - "BSD-4-Clause", - "BSD-Protection", - "BSD-Source-Code", - "BSL-1.0", - "bzip2-1.0.5", - "bzip2-1.0.6", - "Caldera", - "CATOSL-1.1", - "CC-BY-1.0", - "CC-BY-2.0", - "CC-BY-2.5", - "CC-BY-3.0", - "CC-BY-4.0", - "CC-BY-NC-1.0", - "CC-BY-NC-2.0", - "CC-BY-NC-2.5", - "CC-BY-NC-3.0", - "CC-BY-NC-4.0", - "CC-BY-NC-ND-1.0", - "CC-BY-NC-ND-2.0", - "CC-BY-NC-ND-2.5", - "CC-BY-NC-ND-3.0", - "CC-BY-NC-ND-4.0", - "CC-BY-NC-SA-1.0", - "CC-BY-NC-SA-2.0", - "CC-BY-NC-SA-2.5", - "CC-BY-NC-SA-3.0", - "CC-BY-NC-SA-4.0", - "CC-BY-ND-1.0", - "CC-BY-ND-2.0", - "CC-BY-ND-2.5", - "CC-BY-ND-3.0", - "CC-BY-ND-4.0", - "CC-BY-SA-1.0", - "CC-BY-SA-2.0", - "CC-BY-SA-2.5", - "CC-BY-SA-3.0", - "CC-BY-SA-4.0", - "CC0-1.0", - "CDDL-1.0", - "CDDL-1.1", - "CDLA-Permissive-1.0", - "CDLA-Sharing-1.0", - "CECILL-1.0", - "CECILL-1.1", - "CECILL-2.0", - "CECILL-2.1", - "CECILL-B", - "CECILL-C", - "ClArtistic", - "CNRI-Jython", - "CNRI-Python-GPL-Compatible", - "CNRI-Python", - "Condor-1.1", - "CPAL-1.0", - "CPL-1.0", - "CPOL-1.02", - "Crossword", - "CrystalStacker", - "CUA-OPL-1.0", - "Cube", - "curl", - "D-FSL-1.0", - "diffmark", - "DOC", - "Dotseqn", - "DSDP", - "dvipdfm", - "ECL-1.0", - "ECL-2.0", - "EFL-1.0", - "EFL-2.0", - "eGenix", - "Entessa", - "EPL-1.0", - "EPL-2.0", - "ErlPL-1.1", - "EUDatagrid", - "EUPL-1.0", - "EUPL-1.1", - "EUPL-1.2", - "Eurosym", - "Fair", - "Frameworx-1.0", - "FreeImage", - "FSFAP", - "FSFUL", - "FSFULLR", - "FTL", - "GFDL-1.1-only", - "GFDL-1.1-or-later", - "GFDL-1.2-only", - "GFDL-1.2-or-later", - "GFDL-1.3-only", - "GFDL-1.3-or-later", - "Giftware", - "GL2PS", - "Glide", - "Glulxe", - "gnuplot", - "GPL-1.0-only", - "GPL-1.0-or-later", - "GPL-2.0-only", - "GPL-2.0-or-later", - "GPL-3.0-only", - "GPL-3.0-or-later", - "gSOAP-1.3b", - "HaskellReport", - "HPND", - "IBM-pibs", - "ICU", - "IJG", - "ImageMagick", - "iMatix", - "Imlib2", - "Info-ZIP", - "Intel-ACPI", - "Intel", - "Interbase-1.0", - "IPA", - "IPL-1.0", - "ISC", - "JasPer-2.0", - "JSON", - "LAL-1.2", - "LAL-1.3", - "Latex2e", - "Leptonica", - "LGPL-2.0-only", - "LGPL-2.0-or-later", - "LGPL-2.1-only", - "LGPL-2.1-or-later", - "LGPL-3.0-only", - "LGPL-3.0-or-later", - "LGPLLR", - "Libpng", - "libtiff", - "LiLiQ-P-1.1", - "LiLiQ-R-1.1", - "LiLiQ-Rplus-1.1", - "LPL-1.0", - "LPL-1.02", - "LPPL-1.0", - "LPPL-1.1", - "LPPL-1.2", - "LPPL-1.3a", - "LPPL-1.3c", - "MakeIndex", - "MirOS", - "MIT-advertising", - "MIT-CMU", - "MIT-enna", - "MIT-feh", - "MIT", - "MITNFA", - "Motosoto", - "mpich2", - "MPL-1.0", - "MPL-1.1", - "MPL-2.0-no-copyleft-exception", - "MPL-2.0", - "MS-PL", - "MS-RL", - "MTLL", - "Multics", - "Mup", - "NASA-1.3", - "Naumen", - "NBPL-1.0", - "NCSA", - "Net-SNMP", - "NetCDF", - "Newsletr", - "NGPL", - "NLOD-1.0", - "NLPL", - "Nokia", - "NOSL", - "Noweb", - "NPL-1.0", - "NPL-1.1", - "NPOSL-3.0", - "NRL", - "NTP", - "OCCT-PL", - "OCLC-2.0", - "ODbL-1.0", - "OFL-1.0", - "OFL-1.1", - "OGTSL", - "OLDAP-1.1", - "OLDAP-1.2", - "OLDAP-1.3", - "OLDAP-1.4", - "OLDAP-2.0.1", - "OLDAP-2.0", - "OLDAP-2.1", - "OLDAP-2.2.1", - "OLDAP-2.2.2", - "OLDAP-2.2", - "OLDAP-2.3", - "OLDAP-2.4", - "OLDAP-2.5", - "OLDAP-2.6", - "OLDAP-2.7", - "OLDAP-2.8", - "OML", - "OpenSSL", - "OPL-1.0", - "OSET-PL-2.1", - "OSL-1.0", - "OSL-1.1", - "OSL-2.0", - "OSL-2.1", - "OSL-3.0", - "PDDL-1.0", - "PHP-3.0", - "PHP-3.01", - "Plexus", - "PostgreSQL", - "psfrag", - "psutils", - "Python-2.0", - "Qhull", - "QPL-1.0", - "Rdisc", - "RHeCos-1.1", - "RPL-1.1", - "RPL-1.5", - "RPSL-1.0", - "RSA-MD", - "RSCPL", - "Ruby", - "SAX-PD", - "Saxpath", - "SCEA", - "Sendmail", - "SGI-B-1.0", - "SGI-B-1.1", - "SGI-B-2.0", - "SimPL-2.0", - "SISSL-1.2", - "SISSL", - "Sleepycat", - "SMLNJ", - "SMPPL", - "SNIA", - "Spencer-86", - "Spencer-94", - "Spencer-99", - "SPL-1.0", - "SugarCRM-1.1.3", - "SWL", - "TCL", - "TCP-wrappers", - "TMate", - "TORQUE-1.1", - "TOSL", - "Unicode-DFS-2015", - "Unicode-DFS-2016", - "Unicode-TOU", - "Unlicense", - "UPL-1.0", - "Vim", - "VOSTROM", - "VSL-1.0", - "W3C-19980720", - "W3C-20150513", - "W3C", - "Watcom-1.0", - "Wsuipa", - "WTFPL", - "X11", - "Xerox", - "XFree86-1.1", - "xinetd", - "Xnet", - "xpp", - "XSkat", - "YPL-1.0", - "YPL-1.1", - "Zed", - "Zend-2.0", - "Zimbra-1.3", - "Zimbra-1.4", - "zlib-acknowledgement", - "Zlib", - "ZPL-1.1", - "ZPL-2.0", - "ZPL-2.1", +# A compiled, public domain list of official SPDX license tags. generated +# using: `python3 -m spdx_license_list print --filter-fsf-or-osi` Only contains +# licenes approved by either FSF to be free/libre software or OSI to be open +# source +APPROVED_LICENSES = [ + '0BSD', + 'AAL', + 'AFL-1.1', + 'AFL-1.2', + 'AFL-2.0', + 'AFL-2.1', + 'AFL-3.0', + 'AGPL-3.0-only', + 'AGPL-3.0-or-later', + 'APL-1.0', + 'APSL-1.0', + 'APSL-1.1', + 'APSL-1.2', + 'APSL-2.0', + 'Apache-1.0', + 'Apache-1.1', + 'Apache-2.0', + 'Artistic-1.0', + 'Artistic-1.0-Perl', + 'Artistic-1.0-cl8', + 'Artistic-2.0', + 'BSD-2-Clause', + 'BSD-2-Clause-FreeBSD', + 'BSD-2-Clause-Patent', + 'BSD-3-Clause', + 'BSD-3-Clause-Clear', + 'BSD-3-Clause-LBNL', + 'BSD-4-Clause', + 'BSL-1.0', + 'BitTorrent-1.1', + 'CATOSL-1.1', + 'CC-BY-4.0', + 'CC-BY-SA-4.0', + 'CC0-1.0', + 'CDDL-1.0', + 'CECILL-2.0', + 'CECILL-2.1', + 'CECILL-B', + 'CECILL-C', + 'CNRI-Python', + 'CPAL-1.0', + 'CPL-1.0', + 'CUA-OPL-1.0', + 'ClArtistic', + 'Condor-1.1', + 'ECL-1.0', + 'ECL-2.0', + 'EFL-1.0', + 'EFL-2.0', + 'EPL-1.0', + 'EPL-2.0', + 'EUDatagrid', + 'EUPL-1.1', + 'EUPL-1.2', + 'Entessa', + 'FSFAP', + 'FTL', + 'Fair', + 'Frameworx-1.0', + 'GFDL-1.1-only', + 'GFDL-1.1-or-later', + 'GFDL-1.2-only', + 'GFDL-1.2-or-later', + 'GFDL-1.3-only', + 'GFDL-1.3-or-later', + 'GPL-2.0-only', + 'GPL-2.0-or-later', + 'GPL-3.0-only', + 'GPL-3.0-or-later', + 'HPND', + 'IJG', + 'IPA', + 'IPL-1.0', + 'ISC', + 'Imlib2', + 'Intel', + 'LGPL-2.0-only', + 'LGPL-2.0-or-later', + 'LGPL-2.1-only', + 'LGPL-2.1-or-later', + 'LGPL-3.0-only', + 'LGPL-3.0-or-later', + 'LPL-1.0', + 'LPL-1.02', + 'LPPL-1.2', + 'LPPL-1.3a', + 'LPPL-1.3c', + 'LiLiQ-P-1.1', + 'LiLiQ-R-1.1', + 'LiLiQ-Rplus-1.1', + 'MIT', + 'MIT-0', + 'MPL-1.0', + 'MPL-1.1', + 'MPL-2.0', + 'MPL-2.0-no-copyleft-exception', + 'MS-PL', + 'MS-RL', + 'MirOS', + 'Motosoto', + 'Multics', + 'NASA-1.3', + 'NCSA', + 'NGPL', + 'NOSL', + 'NPL-1.0', + 'NPL-1.1', + 'NPOSL-3.0', + 'NTP', + 'Naumen', + 'Nokia', + 'OCLC-2.0', + 'ODbL-1.0', + 'OFL-1.0', + 'OFL-1.1', + 'OGTSL', + 'OLDAP-2.3', + 'OLDAP-2.7', + 'OSET-PL-2.1', + 'OSL-1.0', + 'OSL-1.1', + 'OSL-2.0', + 'OSL-2.1', + 'OSL-3.0', + 'OpenSSL', + 'PHP-3.0', + 'PHP-3.01', + 'PostgreSQL', + 'Python-2.0', + 'QPL-1.0', + 'RPL-1.1', + 'RPL-1.5', + 'RPSL-1.0', + 'RSCPL', + 'Ruby', + 'SGI-B-2.0', + 'SISSL', + 'SMLNJ', + 'SPL-1.0', + 'SimPL-2.0', + 'Sleepycat', + 'UPL-1.0', + 'Unlicense', + 'VSL-1.0', + 'Vim', + 'W3C', + 'WTFPL', + 'Watcom-1.0', + 'X11', + 'XFree86-1.1', + 'Xnet', + 'YPL-1.1', + 'ZPL-2.0', + 'ZPL-2.1', + 'Zend-2.0', + 'Zimbra-1.3', + 'Zlib', + 'gnuplot', + 'iMatix', + 'xinetd', ] +# an F-Droid addition, until we can enforce a better option +APPROVED_LICENSES.append("PublicDomain") + if __name__ == "__main__": main() diff --git a/tests/lint.TestCase b/tests/lint.TestCase index 4bdee948..f5dd2c30 100755 --- a/tests/lint.TestCase +++ b/tests/lint.TestCase @@ -179,6 +179,100 @@ class LintTest(unittest.TestCase): logging.debug(warn) self.assertTrue(anywarns) + def test_check_license_tag_no_custom_pass(self): + config = dict() + fdroidserver.common.fill_config_defaults(config) + fdroidserver.common.config = config + fdroidserver.lint.config = config + + app = fdroidserver.metadata.App() + app.License = "GPL-3.0-or-later" + + anywarns = False + for warn in fdroidserver.lint.check_license_tag(app): + anywarns = True + logging.debug(warn) + self.assertFalse(anywarns) + + def test_check_license_tag_no_custom_fail(self): + config = dict() + fdroidserver.common.fill_config_defaults(config) + fdroidserver.common.config = config + fdroidserver.lint.config = config + + app = fdroidserver.metadata.App() + app.License = "Adobe-2006" + + anywarns = False + for warn in fdroidserver.lint.check_license_tag(app): + anywarns = True + logging.debug(warn) + self.assertTrue(anywarns) + + def test_check_license_tag_with_custom_pass(self): + config = dict() + fdroidserver.common.fill_config_defaults(config) + fdroidserver.common.config = config + fdroidserver.lint.config = config + config['lint_licenses'] = ['fancy-license', 'GPL-3.0-or-later'] + + app = fdroidserver.metadata.App() + app.License = "fancy-license" + + anywarns = False + for warn in fdroidserver.lint.check_license_tag(app): + anywarns = True + logging.debug(warn) + self.assertFalse(anywarns) + + def test_check_license_tag_with_custom_fail(self): + config = dict() + fdroidserver.common.fill_config_defaults(config) + fdroidserver.common.config = config + fdroidserver.lint.config = config + config['lint_licenses'] = ['fancy-license', 'GPL-3.0-or-later'] + + app = fdroidserver.metadata.App() + app.License = "Apache-2.0" + + anywarns = False + for warn in fdroidserver.lint.check_license_tag(app): + anywarns = True + logging.debug(warn) + self.assertTrue(anywarns) + + def test_check_license_tag_with_custom_empty(self): + config = dict() + fdroidserver.common.fill_config_defaults(config) + fdroidserver.common.config = config + fdroidserver.lint.config = config + config['lint_licenses'] = [] + + app = fdroidserver.metadata.App() + app.License = "Apache-2.0" + + anywarns = False + for warn in fdroidserver.lint.check_license_tag(app): + anywarns = True + logging.debug(warn) + self.assertTrue(anywarns) + + def test_check_license_tag_disabled(self): + config = dict() + fdroidserver.common.fill_config_defaults(config) + fdroidserver.common.config = config + fdroidserver.lint.config = config + config['lint_licenses'] = None + + app = fdroidserver.metadata.App() + app.License = "Apache-2.0" + + anywarns = False + for warn in fdroidserver.lint.check_license_tag(app): + anywarns = True + logging.debug(warn) + self.assertFalse(anywarns) + if __name__ == "__main__": os.chdir(os.path.dirname(__file__))