Merge branch 'license-overhaul-2' into 'master'

license overhaul: require FSF or OSI approved license + add override option for default license white-list

See merge request fdroid/fdroidserver!682
This commit is contained in:
Hans-Christoph Steiner 2019-10-03 22:01:07 +00:00
commit 17f37414db
4 changed files with 292 additions and 353 deletions

View file

@ -330,3 +330,20 @@ The repository of older versions of applications from the main demo repository.
# 'com.facebook.orca', # 'com.facebook.orca',
# 'com.android.vending', # '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',
# )

View file

@ -57,6 +57,7 @@ from pyasn1_modules import rfc2315
from pyasn1.error import PyAsn1Error from pyasn1.error import PyAsn1Error
import fdroidserver.metadata import fdroidserver.metadata
import fdroidserver.lint
from fdroidserver import _ from fdroidserver import _
from fdroidserver.exception import FDroidException, VCSException, NoSubmodulesException,\ from fdroidserver.exception import FDroidException, VCSException, NoSubmodulesException,\
BuildException, VerificationException BuildException, VerificationException
@ -145,6 +146,7 @@ default_config = {
using the tools on https://gitlab.com/u/fdroid. using the tools on https://gitlab.com/u/fdroid.
''', ''',
'archive_older': 0, 'archive_older': 0,
'lint_licenses': fdroidserver.lint.APPROVED_LICENSES,
} }

View file

@ -440,10 +440,17 @@ def check_format(app):
def check_license_tag(app): def check_license_tag(app):
'''Ensure all license tags are in https://spdx.org/license-list''' '''Ensure all license tags contain only valid/approved values'''
if app.License.rstrip('+') not in SPDX: if config['lint_licenses'] is None:
yield _('Invalid license tag "%s"! Use only tags from https://spdx.org/license-list') \ return
% (app.License) 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): def check_extlib_dir(apps):
@ -622,356 +629,175 @@ def main():
sys.exit(1) sys.exit(1)
# A compiled, public domain list of official SPDX license tags from: # A compiled, public domain list of official SPDX license tags. generated
# https://github.com/sindresorhus/spdx-license-list/blob/v4.0.0/spdx-simple.json # using: `python3 -m spdx_license_list print --filter-fsf-or-osi` Only contains
# The deprecated license tags have been removed from the list, they are at the # licenes approved by either FSF to be free/libre software or OSI to be open
# bottom, starting after the last license tags that start with Z. # source
# This is at the bottom, since its a long list of data APPROVED_LICENSES = [
SPDX = [ '0BSD',
"PublicDomain", # an F-Droid addition, until we can enforce a better option 'AAL',
"0BSD", 'AFL-1.1',
"AAL", 'AFL-1.2',
"Abstyles", 'AFL-2.0',
"Adobe-2006", 'AFL-2.1',
"Adobe-Glyph", 'AFL-3.0',
"ADSL", 'AGPL-3.0-only',
"AFL-1.1", 'AGPL-3.0-or-later',
"AFL-1.2", 'APL-1.0',
"AFL-2.0", 'APSL-1.0',
"AFL-2.1", 'APSL-1.1',
"AFL-3.0", 'APSL-1.2',
"Afmparse", 'APSL-2.0',
"AGPL-1.0", 'Apache-1.0',
"AGPL-3.0-only", 'Apache-1.1',
"AGPL-3.0-or-later", 'Apache-2.0',
"Aladdin", 'Artistic-1.0',
"AMDPLPA", 'Artistic-1.0-Perl',
"AML", 'Artistic-1.0-cl8',
"AMPAS", 'Artistic-2.0',
"ANTLR-PD", 'BSD-2-Clause',
"Apache-1.0", 'BSD-2-Clause-FreeBSD',
"Apache-1.1", 'BSD-2-Clause-Patent',
"Apache-2.0", 'BSD-3-Clause',
"APAFML", 'BSD-3-Clause-Clear',
"APL-1.0", 'BSD-3-Clause-LBNL',
"APSL-1.0", 'BSD-4-Clause',
"APSL-1.1", 'BSL-1.0',
"APSL-1.2", 'BitTorrent-1.1',
"APSL-2.0", 'CATOSL-1.1',
"Artistic-1.0-cl8", 'CC-BY-4.0',
"Artistic-1.0-Perl", 'CC-BY-SA-4.0',
"Artistic-1.0", 'CC0-1.0',
"Artistic-2.0", 'CDDL-1.0',
"Bahyph", 'CECILL-2.0',
"Barr", 'CECILL-2.1',
"Beerware", 'CECILL-B',
"BitTorrent-1.0", 'CECILL-C',
"BitTorrent-1.1", 'CNRI-Python',
"Borceux", 'CPAL-1.0',
"BSD-1-Clause", 'CPL-1.0',
"BSD-2-Clause-FreeBSD", 'CUA-OPL-1.0',
"BSD-2-Clause-NetBSD", 'ClArtistic',
"BSD-2-Clause-Patent", 'Condor-1.1',
"BSD-2-Clause", 'ECL-1.0',
"BSD-3-Clause-Attribution", 'ECL-2.0',
"BSD-3-Clause-Clear", 'EFL-1.0',
"BSD-3-Clause-LBNL", 'EFL-2.0',
"BSD-3-Clause-No-Nuclear-License-2014", 'EPL-1.0',
"BSD-3-Clause-No-Nuclear-License", 'EPL-2.0',
"BSD-3-Clause-No-Nuclear-Warranty", 'EUDatagrid',
"BSD-3-Clause", 'EUPL-1.1',
"BSD-4-Clause-UC", 'EUPL-1.2',
"BSD-4-Clause", 'Entessa',
"BSD-Protection", 'FSFAP',
"BSD-Source-Code", 'FTL',
"BSL-1.0", 'Fair',
"bzip2-1.0.5", 'Frameworx-1.0',
"bzip2-1.0.6", 'GFDL-1.1-only',
"Caldera", 'GFDL-1.1-or-later',
"CATOSL-1.1", 'GFDL-1.2-only',
"CC-BY-1.0", 'GFDL-1.2-or-later',
"CC-BY-2.0", 'GFDL-1.3-only',
"CC-BY-2.5", 'GFDL-1.3-or-later',
"CC-BY-3.0", 'GPL-2.0-only',
"CC-BY-4.0", 'GPL-2.0-or-later',
"CC-BY-NC-1.0", 'GPL-3.0-only',
"CC-BY-NC-2.0", 'GPL-3.0-or-later',
"CC-BY-NC-2.5", 'HPND',
"CC-BY-NC-3.0", 'IJG',
"CC-BY-NC-4.0", 'IPA',
"CC-BY-NC-ND-1.0", 'IPL-1.0',
"CC-BY-NC-ND-2.0", 'ISC',
"CC-BY-NC-ND-2.5", 'Imlib2',
"CC-BY-NC-ND-3.0", 'Intel',
"CC-BY-NC-ND-4.0", 'LGPL-2.0-only',
"CC-BY-NC-SA-1.0", 'LGPL-2.0-or-later',
"CC-BY-NC-SA-2.0", 'LGPL-2.1-only',
"CC-BY-NC-SA-2.5", 'LGPL-2.1-or-later',
"CC-BY-NC-SA-3.0", 'LGPL-3.0-only',
"CC-BY-NC-SA-4.0", 'LGPL-3.0-or-later',
"CC-BY-ND-1.0", 'LPL-1.0',
"CC-BY-ND-2.0", 'LPL-1.02',
"CC-BY-ND-2.5", 'LPPL-1.2',
"CC-BY-ND-3.0", 'LPPL-1.3a',
"CC-BY-ND-4.0", 'LPPL-1.3c',
"CC-BY-SA-1.0", 'LiLiQ-P-1.1',
"CC-BY-SA-2.0", 'LiLiQ-R-1.1',
"CC-BY-SA-2.5", 'LiLiQ-Rplus-1.1',
"CC-BY-SA-3.0", 'MIT',
"CC-BY-SA-4.0", 'MIT-0',
"CC0-1.0", 'MPL-1.0',
"CDDL-1.0", 'MPL-1.1',
"CDDL-1.1", 'MPL-2.0',
"CDLA-Permissive-1.0", 'MPL-2.0-no-copyleft-exception',
"CDLA-Sharing-1.0", 'MS-PL',
"CECILL-1.0", 'MS-RL',
"CECILL-1.1", 'MirOS',
"CECILL-2.0", 'Motosoto',
"CECILL-2.1", 'Multics',
"CECILL-B", 'NASA-1.3',
"CECILL-C", 'NCSA',
"ClArtistic", 'NGPL',
"CNRI-Jython", 'NOSL',
"CNRI-Python-GPL-Compatible", 'NPL-1.0',
"CNRI-Python", 'NPL-1.1',
"Condor-1.1", 'NPOSL-3.0',
"CPAL-1.0", 'NTP',
"CPL-1.0", 'Naumen',
"CPOL-1.02", 'Nokia',
"Crossword", 'OCLC-2.0',
"CrystalStacker", 'ODbL-1.0',
"CUA-OPL-1.0", 'OFL-1.0',
"Cube", 'OFL-1.1',
"curl", 'OGTSL',
"D-FSL-1.0", 'OLDAP-2.3',
"diffmark", 'OLDAP-2.7',
"DOC", 'OSET-PL-2.1',
"Dotseqn", 'OSL-1.0',
"DSDP", 'OSL-1.1',
"dvipdfm", 'OSL-2.0',
"ECL-1.0", 'OSL-2.1',
"ECL-2.0", 'OSL-3.0',
"EFL-1.0", 'OpenSSL',
"EFL-2.0", 'PHP-3.0',
"eGenix", 'PHP-3.01',
"Entessa", 'PostgreSQL',
"EPL-1.0", 'Python-2.0',
"EPL-2.0", 'QPL-1.0',
"ErlPL-1.1", 'RPL-1.1',
"EUDatagrid", 'RPL-1.5',
"EUPL-1.0", 'RPSL-1.0',
"EUPL-1.1", 'RSCPL',
"EUPL-1.2", 'Ruby',
"Eurosym", 'SGI-B-2.0',
"Fair", 'SISSL',
"Frameworx-1.0", 'SMLNJ',
"FreeImage", 'SPL-1.0',
"FSFAP", 'SimPL-2.0',
"FSFUL", 'Sleepycat',
"FSFULLR", 'UPL-1.0',
"FTL", 'Unlicense',
"GFDL-1.1-only", 'VSL-1.0',
"GFDL-1.1-or-later", 'Vim',
"GFDL-1.2-only", 'W3C',
"GFDL-1.2-or-later", 'WTFPL',
"GFDL-1.3-only", 'Watcom-1.0',
"GFDL-1.3-or-later", 'X11',
"Giftware", 'XFree86-1.1',
"GL2PS", 'Xnet',
"Glide", 'YPL-1.1',
"Glulxe", 'ZPL-2.0',
"gnuplot", 'ZPL-2.1',
"GPL-1.0-only", 'Zend-2.0',
"GPL-1.0-or-later", 'Zimbra-1.3',
"GPL-2.0-only", 'Zlib',
"GPL-2.0-or-later", 'gnuplot',
"GPL-3.0-only", 'iMatix',
"GPL-3.0-or-later", 'xinetd',
"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",
] ]
# an F-Droid addition, until we can enforce a better option
APPROVED_LICENSES.append("PublicDomain")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -179,6 +179,100 @@ class LintTest(unittest.TestCase):
logging.debug(warn) logging.debug(warn)
self.assertTrue(anywarns) 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__": if __name__ == "__main__":
os.chdir(os.path.dirname(__file__)) os.chdir(os.path.dirname(__file__))