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 81794840..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 FSF_APPROVED_SPDX: - yield _('Unexpected license tag "{}"! Use only FSF approved tags ' - 'from https://spdx.org/license-list').format(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,38 +629,175 @@ def main(): sys.exit(1) -# A compiled, public domain list of official SPDX license tags. -# generated using: `python3 -m spdx_license_list print --filter-fsf` -# Only contains licenes approved by FSF to be libre/free software -FSF_APPROVED_SPDX = ['AFL-1.1', 'AFL-1.2', 'AFL-2.0', 'AFL-2.1', 'AFL-3.0', - 'AGPL-3.0-only', 'AGPL-3.0-or-later', 'APSL-2.0', - 'Apache-1.0', 'Apache-1.1', 'Apache-2.0', 'Artistic-2.0', - 'BSD-2-Clause-FreeBSD', 'BSD-3-Clause', - 'BSD-3-Clause-Clear', 'BSD-4-Clause', 'BSL-1.0', - 'BitTorrent-1.1', 'CC-BY-4.0', 'CC-BY-SA-4.0', 'CC0-1.0', - 'CDDL-1.0', 'CECILL-2.0', 'CECILL-B', 'CECILL-C', - 'CPAL-1.0', 'CPL-1.0', 'ClArtistic', 'Condor-1.1', - 'ECL-2.0', 'EFL-2.0', 'EPL-1.0', 'EPL-2.0', 'EUDatagrid', - 'EUPL-1.1', 'EUPL-1.2', 'FSFAP', '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', '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.1-only', 'LGPL-2.1-or-later', 'LGPL-3.0-only', - 'LGPL-3.0-or-later', 'LPL-1.02', 'LPPL-1.2', 'LPPL-1.3a', - 'MIT', 'MPL-1.1', 'MPL-2.0', 'MS-PL', 'MS-RL', 'NCSA', - 'NOSL', 'NPL-1.0', 'NPL-1.1', 'Nokia', 'ODbL-1.0', - 'OFL-1.0', 'OFL-1.1', 'OLDAP-2.3', 'OLDAP-2.7', 'OSL-1.0', - 'OSL-1.1', 'OSL-2.0', 'OSL-2.1', 'OSL-3.0', 'OpenSSL', - 'PHP-3.01', 'Python-2.0', 'QPL-1.0', 'RPSL-1.0', 'Ruby', - 'SGI-B-2.0', 'SISSL', 'SMLNJ', 'SPL-1.0', 'Sleepycat', - 'UPL-1.0', 'Unlicense', 'Vim', 'W3C', 'WTFPL', 'X11', - 'XFree86-1.1', 'YPL-1.1', 'ZPL-2.0', 'ZPL-2.1', - 'Zend-2.0', 'Zimbra-1.3', 'Zlib', 'gnuplot', 'iMatix', - 'xinetd'] +# 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 -FSF_APPROVED_SPDX.append("PublicDomain") +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__))