App licensing

Overview

In order of app developers to restrict the usage of their apps, we provide an API for reading and verifying license files. The licensing mechanism allows the app developer to limit which system the app is run on, by providing a site name that has to match the name in portal.conf as well as the site name in the main site license file.

In addition to that, a configurable set of properties can be added to a license file and these can then be used in the app to enable or disable certain features. Properties contains type information and can store string, integer, float or boolean values.

When a license is issued by the app developer, it is signed using a private key which is kept secret. When the license is loaded into the app it is verified using the corresponding public key which ensures that the license file has been issued by the app developer and that it has not been modified.

Generate license signing keys

To start the license generation process, the app developer has to generate a public/private key pair. The private part of this key pair is used when issuing a license to a customer. This key pair can be used to issue licenses for multiple apps.

You must have a Cantemo installation in order to issue licenses.

This is done by issuing the command:

$ python /opt/cantemo/portal/manage.py generate_license_signing_key

Creating a new license key.
Sucessfully wrote public key to /etc/cantemo/portal/public_license_signing_key.pem
Sucessfully wrote private key to /etc/cantemo/portal/private_license_signing_key.pem

Your keys are now stored in /etc/cantemo/portal/

NOTE: Make sure you create a backup copy of these keys. Without them you will not be able to issue licenses without releasing a new version of your app.

Create a license configuration

The license generation is configured using an xml-file which contains information about which app the license should be generated for and which properties should be configured.

The following is an example of such an xml file.

<licenseConfig>
  <appname>testapp</appname>
  <property>
    <name>users</name>
    <question>Enter number of users (Leave empty for unlimited)</question>
    <type>int</type>
    <default>-1</default>
  </property>
  <property>
    <name>candoX</name>
    <question>Should function X enabled?</question>
    <type>bool</type>
    <default>false</default>
  </property>
  <property>
    <name>maxstorage</name>
    <question>How much data can be stored?</question>
    <type>float</type>
    <default>23.5</default>
  </property>
</licenseConfig>

Store this file as /etc/cantemo/portal/license-config.xml

You can now issue a license via the following command:

$ python /opt/cantemo/portal/manage.py generate_license
Define license validity date (YYYYMMDD or leave empty for unlimited) 20121231
Please enter the site name:  Portal
Enter number of users (Leave empty for unlimited) (default: -1) 10
Should function X enabled? (y/N) y
How much data can be stored? (default: 23.5)
Wrote license file to testapp.key

The file testapp.key now contains

[testapp]
version = 2
validitydate = 20121231
property_sitename = string:Portal
property_users = int:10
property_candoX = bool:True
property_maxstorage = float:23.5
signature = 539cf058500d139eacf944254aa72bd3adc578c2f83f3c6090e5829cc897f9be75ec8159
0243d34a490fd03a06313c8476ad8850d82725a12a09f72c23fa48d3b489a72f972842e2c75db749afb2
8bb4f104363e8f61e3a82e7ce8c50b6fac4632d3bf913752355d9e7ca43fc8e5efdbae6ef5ad95fc9dca
0dca230b8202fcf5

This file should be installed as /etc/cantemo/portal/testapp.key on the customer’s system.

The command generate_license takes the following command arguments.

Command

Description

–base-path

The base directory where license keys and configuration resides.

–output

The filename where you want your license to be stored. Defaults to appname.key in the current directory.

–config

The path to a configuration file. Defaults to license-config.xml in the base directory. If the path starts with a slash, it is interpreted as an absolute path

Programming Examples

In order to verify a license and make use of the properties inside your app, you can call the load_app_license(appname, public_key) function in the portal.licensing.utils module. This will return a portal.generic.general.PortalLicense object which is the object representation of the license file. If no exception is returned, the license file was found and the signature was valid. However, the license can still be expired or invalid for some other reason, so you should check the attribute is_valid. If the license turns out to be invalid, and the function notvalid_reason() gives a localized message that can be presented to the user.

Because the way plugins are loaded in Portal, the license cannot be loaded in the __init__ method. This method is executed too early for the license verification to be in place. This may change in future versions, but currently the best way to load a license file is to call it from the file models.py. This file is executed after the boot process is completed but before the first request is served so all required functionality is in place. This can be done by adding the following lines to your models.py:

from .plugin import initialize
initialize()

This will call the function initialize() from your plugin.py which can be defined as:

def initialize():
  global app_license

  try:
      app_license = load_app_license("testapp", "2d2d2d2d2d424547494e205055424c49432
      04b45592d2d2d2d2d0a4d4947664d413047435371475349623344514542415155414134474e414
      4434269514b42675143396e417165374b657077597244586250467168384d4b67786d0a7a77506
      f752f79446a6f4e7733484b51384d30644172493234346b4c71746932796c47444e624b72354d7
      447734656612f4c6f534e41706b6736484a4f5a486e0a665642725148454e785951353678476e6
      4616c386c3238585a356e643042794178464861516b52694d71765167336c50557a6f43566b487
      54738355757536f7a0a4636624f37487857782f69675636684477514944415141420a2d2d2d2d2
      d454e44205055424c4943204b45592d2d2d2d2d0a")
  except Exception, e:
      log.error("Faild to load plugin license: %s", str(e))
      app_license = None

This now makes app_license available in all parts of your app. If you want to check if a user is entitled to use a certain functionality, you can do that with the following code example:

if app_license and app_license.get_property('candoX'):
  doX()

Apps license API

These are the programming APIs available for the licensing module.

portal.licensing.utils

portal.licensing.utils.extract_site_key(license_string)

Remove all parts of the site key which aren’t relevant for validating the signature.

portal.licensing.utils.load_app_license(appname, public_key, search_path=None)

Loads an app license and validates it using the public key

Args: * appname - The name of the app as it appears in the section header in the license file * public_key - The encoded public key * search_path - An additional file system path where the function should look for the key

Returns: * A portal.generic.general.PortalLicense object representing the license

portal.licensing.utils.load_product_license(path, sitename=None)

Loads the main product license.

NOTE: This function is only used internally and is called by the bootstrap process. Other users should access settings.SITE_LICENSE_KEY instead. This function’s signature may change in future updates.

Args: * path - The path where a legacy key file can be found * sitename - A sitename to use when verifying the license

Returns: * A portal.generic.general.PortalLicense object representing the site license.

portal.licensing.utils.parse_new_license(license)

Parses a version 2/3 license file

portal.licensing.utils.parse_prop_value(value_string)

Parses an individual property value from a version 3 license file.

portal.generic.general.PortalLicense

class portal.generic.general.PortalLicense(sitename='', development=False, users=2, use_i18n=False, validitydate=None, is_valid=True, invalid_reason=None)

The main license class

expires_soon(validitydate=None)

Returns true if the license expires within 7 days.

get_property(prop_name)

Returns the value of a property.

Args: * prop_name - The name of the property

Returns: * The property value casted to its specified type

has_expired()

Returns true if the license has expired.

property is_valid

Is this license valid?

notvalid_reason()

Returns the reason why this license is invalid. Returns None if the license is not invalid

time_until_expiry()

Returns a timedelta object representing the time until this license expires

property version

The version of the license object

class portal.generic.general.PortalLicenseNew(appname, public_key)