REST API

Hermine’s API uses the Djano REST framework. There is an auto-generated doc for the API using drf-yasg available at {HERMINE_HOST}/api-doc/for users with admin rights.

Danger

Most API endpoints have a trailing slash. If you omit it, you may get a 301 redirect, which may cause problems in case of POST requests.

Authentication via Token

The most convenient way to authenticate to the API is probably to use a token. You can get such token by making a POST request on api/token-auth/ with the fields username and password in a form-data, using your Hermine credentials.

In Python, using requests, it would look like:

import requests

HERMINE_HOST = "https://your.hermine.instance.org/"
CREDENTIALS = {"username": "admin", "password": "your_admin_pass"}
ENDPOINT = "api/token-auth/"

url = HERMINE_HOST + ENDPOINT
r = requests.post(url, data=CREDENTIALS)

print(r.json())

You will get a response like :

{
    "token": "1273e3f6XXXXXXXXXXXXXXXX71209ac3bf29"
}

This token can then be passed in a HTTP Header to authenticate your API calls:

"Authorization" : "Token 1273e3f6XXXXXXXXXXXXXXXX71209ac3bf29".

This could look like:

import requests

HERMINE_HOST = "https://your.hermine.instance.org/"
ENDPOINT = "api/releases/"
url = HERMINE_HOST + ENDPOINT

headers = {"Authorization": "Token 1273e3f6XXXXXXXXXXXXXXXX71209ac3bf29"}
r = requests.get(url, headers=headers)

Uploading a BOM file

ORT Evaluated model

POST /api/upload_ort/

Adds the content of the SBOM as EvaluatedModel to a given release

Parameters:
  • release (int) – required – the id of the release the BOM will be added to

  • replace (bool) – default = false – if true, existing components in the release will be deleted.

  • linking (str) – Enum:”Aggregation”|”Dynamic”|”Static”|”Mingled” – the type of linking for the components in the BOM

  • ort_file (file) – the evaluated-model.json file generated by ORT

Returns:

the status of the upload

An example would be:

endpoint = "api/upload_ort/"
url = HERMINE_HOST + endpoint
data = {
    "release": 33,
}
evmodel_path = "path/to/your/evaluated-model.json"
with open(evmodel_path, "rb") as ort_file:
    upload_response = requests.post(
        url, files={"ort_file": ort_file}, data=data, headers=headers
    )

SPDX file

POST /api/upload_spdx/

Adds the content of the SBOM as SPDX to a given release

Parameters:
  • release (int) – required – the id of the release the BOM will be added to

  • replace (bool) – default = false – if true, existing components in the release will be deleted.

  • linking (str) – Enum:”Aggregation”|”Dynamic”|”Static”|”Mingled” – the type of linking for the components in the BOM

  • spdx_file (file) – the SPDX BOM

Returns:

the status of the upload

An example would be:

endpoint = "api/upload_spdx/"
url = HERMINE_HOST + endpoint
data = {
    "release": 33,
}
spdx_path = "path/to/your/spdx.json"
with open(spdx_path, "rb") as spdx_file:
    upload_response = requests.post(
        url, files={"spdx_file": spdx_file}, data=data, headers=headers
    )

Checking the validation steps

The validation process of a release is divided in 5 steps. You need to complete them in the right order:

When you encounter a result that does not fit requirements to go to next step, you’ll need to make the appropriate work inside Hermine UI.

Every endpoint has a “valid” field that is set to True if every action that should be done has been done, and False otherwise.

Step 1 – Check that components have valid SPDX license expressions

GET api/releases/<int:release_id>/validation_1/

Gets the global status of SPDX validity of all the components’ licenses for the targeted release and the components with invalid SPDX license expression and those with corrected SPDX license expression.

Parameters:

release (int) – the id of the release whose components’ licenses validation status is checked;

Returns:

  • valid is set to true if all components have a valid SPDX expression;

  • details prompts the relative url to Hermine’s UI for validation of the targeted release;

  • invalid_expressions is an array of Version objects for which license expression is empty or invalid;

  • fixed_expressions is an array of Version objects for which there is a corrected_license and no spdx_valid_license_expr.

API endpoint that allows to know if there are components with license information which is not a valid SPDX expression (present or not in your Hermine instance).

An example would be:

endpoint = "api/releases/<int:release_id>/validation_1/"
url = HERMINE_HOST + endpoint


get_response = requests.get(url, headers=headers)
data = get_response.json()

if data["valid"]:
    print("All components have a valid SPDX license expression")
else:
    nb_components = len(data["invalid_expressions"])
    url = HERMINE_HOST + data["details"]
    print(f"{nb_components} components don't have a valid SPDX license expression")
    print(f"To solve these issues you can use Hermine's UI : {url}")

The response type is application/json with the following schema :

{
  "valid": true,
  "details": "/relative/url/to/release/validation/page",
  "invalid_expressions": [
    {
      "id": 0,
      "component": 0,
      "version_number": "string",
      "declared_license_expr": "string",
      "spdx_valid_license_expr": "string",
      "corrected_license": "string",
      "purl": "string"
    }
  ],
  "fixed_expressions": [
    {
      "id": 0,
      "component": 0,
      "version_number": "string",
      "declared_license_expr": "string",
      "spdx_valid_license_expr": "string",
      "corrected_license": "string",
      "purl": "string"
    }
  ]
}

Step 2 – Confirm ANDs

GET api/releases/<int:release_id>/validation_2/

Gets the list of SPDX license expression containing ANDs which are not yet confirmed as actual ANDs.

Parameters:

release (int) – the id of the release whose licenses containing ANDs validation status is checked;

Returns:

  • valid is set to true if no SPDX expression with ANDs yet to confirmed are left;

  • details prompts the relative url to Hermine’s UI for validation of the targeted release;

  • to_confirm is an array of Version objects for which license expression contains ANDs to be confirmed.

Confirm ANDs operators in SPDX expressions are not poorly registered ORs.

An example would be:

endpoint = "api/releases/<int:release_id>/validation_2/"
url = HERMINE_HOST + endpoint


get_response = requests.get(url, headers=headers)
data = get_response.json()

if data["valid"]:
    print("No components have a SPDX license expression with ANDs to confirm")
else:
    nb_components = len(data["to_confirm"])
    url = HERMINE_HOST + data["details"]
    print(f"{nb_components} components have a SPDX license expression containing ANDs to be confirmed")
    print(f"To solve these issues you can use Hermine's UI : {url}")

The response type is application/json with the following schema :

{
  "valid": true,
  "details": "/relative/url/to/release/validation/page",
  "to_confirm": [
    {
      "id": 0,
      "release": 0,
      "version": 0,
      "status": "Auto",
      "addition_method": "Scan",
      "addition_date": "2019-08-24T14:15:22Z",
      "linking": "Aggregation",
      "component_modified": "Altered",
      "exploitation": "DistributionSourceDistributionNonSource",
      "description": "string",
      "licenses_chosen": [
        0
      ],
      "license_choices": [
        {
          "id": 0,
          "version_constraint": "string",
          "created": "2019-08-24T14:15:22Z",
          "updated": "2019-08-24T14:15:22Z",
          "scope": "string",
          "exploitation": "DistributionSourceDistributionNonSource",
          "expression_in": "string",
          "expression_out": "string",
          "explanation": "string",
          "component": 0,
          "version": 0,
          "author": 0,
          "category": 0,
          "product": 0,
          "release": 0
        }
      ],
      "scope": "string",
      "project": "string"
    }
  ]
}

Meaning of the different fields:

Step 3 – Defined exploitation mode for each scope

GET api/releases/<int:release_id>/validation_3/

Gets the list of scopes with their associated default exploitation mode and the list of scopes whose default exploitation mode is not set.

Parameters:

release (int) – the id of the release whose scopes’ default exploitation mode is checked;

Returns:

  • valid is set to true if all scopes in the release have a default exploitation mode defined;

  • details prompts the relative url to Hermine’s UI for validation of the targeted release;

  • exploitations is an array of truple defining for each scope project couple the associated exploitation;

  • unset_scopes is an array of names of scopes that have no default exploitation mode defined.

Check that all scopes in the release have a default exploitation mode defined.

An example would be:

endpoint = "api/releases/<int:release_id>/validation_3/"
url = HERMINE_HOST + endpoint


get_response = requests.get(url, headers=headers)
data = get_response.json()

if data["valid"]:
    print("All scopes have a default exploitation mode defined")
else:
    nb_scopes = len(data["unset_scopes"])
    url = HERMINE_HOST + data["details"]
    print(f"{nb_scopes} scopes have no default exploitation mode defined")
    print(f"To solve these issues you can use Hermine's UI : {url}")

The response type is application/json with the following schema :

{
  "valid": true,
  "details": "/relative/url/to/release/validation/page",
  "exploitations": [
    {
      "scope": "string",
      "project": "string",
      "exploitation": "DistributionSourceDistributionNonSource"
    }
  ],
  "unset_scopes": [
    "string"
  ]
}

Step 4 – Resolve license choices for this release

GET api/releases/<int:release_id>/validation_4/

Gets the list of components requiring a choice of license and the list of components with exisiting choice of license.

Parameters:

release (int) – the id of the release whose components’ license choices are checked;

Returns:

  • valid is set to true if no compo;

  • details prompts the relative url to Hermine’s UI for validation of the targeted release;

  • to_resolve is an array of usages linked to a version that either has a complex “corrected_license” other a “spdx_valid_license_expr” field, and for which no explicit choice as been made;

  • resolved is an array of usages linked to a version hat either has a complex “corrected_license” other a “spdx_valid_license_expr” field, and for which an explicit choice as been made.

An example would be:

endpoint = "api/releases/<int:release_id>/validation_4/"
url = HERMINE_HOST + endpoint


get_response = requests.get(url, headers=headers)
data = get_response.json()

if data["valid"]:
    print("No component require a choice of license")
else:
    nb_components = len(data["to_resolve"])
    url = HERMINE_HOST + data["details"]
    print(f"{nb_scopes} components require a choice of license")
    print(f"To solve these issues you can use Hermine's UI : {url}")

The response type is application/json with the following schema :

{
  "valid": true,
  "details": "/relative/url/to/release/validation/page",
  "to_resolve": [
    {
      "id": 0,
      "release": 0,
      "version": 0,
      "status": "Auto",
      "addition_method": "Scan",
      "addition_date": "2019-08-24T14:15:22Z",
      "linking": "Aggregation",
      "component_modified": "Altered",
      "exploitation": "DistributionSourceDistributionNonSource",
      "description": "string",
      "licenses_chosen": [
        0
      ],
      "license_choices": [
        {
          "id": 0,
          "version_constraint": "string",
          "created": "2019-08-24T14:15:22Z",
          "updated": "2019-08-24T14:15:22Z",
          "scope": "string",
          "exploitation": "DistributionSourceDistributionNonSource",
          "expression_in": "string",
          "expression_out": "string",
          "explanation": "string",
          "component": 0,
          "version": 0,
          "author": 0,
          "category": 0,
          "product": 0,
          "release": 0
        }
      ],
      "scope": "string",
      "project": "string"
    }
  ],
  "resolved": [
    {
      "id": 0,
      "release": 0,
      "version": 0,
      "status": "Auto",
      "addition_method": "Scan",
      "addition_date": "2019-08-24T14:15:22Z",
      "linking": "Aggregation",
      "component_modified": "Altered",
      "exploitation": "DistributionSourceDistributionNonSource",
      "description": "string",
      "licenses_chosen": [
        0
      ],
      "license_choices": [
        {
          "id": 0,
          "version_constraint": "string",
          "created": "2019-08-24T14:15:22Z",
          "updated": "2019-08-24T14:15:22Z",
          "scope": "string",
          "exploitation": "DistributionSourceDistributionNonSource",
          "expression_in": "string",
          "expression_out": "string",
          "explanation": "string",
          "component": 0,
          "version": 0,
          "author": 0,
          "category": 0,
          "product": 0,
          "release": 0
        }
      ],
      "scope": "string",
      "project": "string"
    }
  ]
}

A complex license expression is an expression with more than one License expressed in it.

A choice has to be made, either by keeping the whole expression either by picking the chosen licenses in the expression.

To make a choice, click on “choose expression” in hermine UI. Pick the desired scope, type the actual expression you’ll want to use for this component, and enter an explanation for your choice.

This will create a UsageChoice object that is linked to the Usage object.

Step 5 – Check licenses against policy

GET api/releases/<int:release_id>/validation_5 /

Gets lists of components in each category of the OS policy (never allowed, context_allowed, unknown), the list of involved licenses.

Parameters:

release (int) – the id of the release whose scopes’ default exploitation mode is checked;

Returns:

  • valid is set to true if all scopes in the release have a default exploitation mode defined;

  • details prompts the relative url to Hermine’s UI for validation of the targeted release;

  • usages_lic_never_allowed is an array of usages containing a license that has been marked as never allowed by the legal team;

  • usages_lic_context_allowed is an array of usages containing a license that has been marked as allowed depending on context by the legal team;

  • usages_lic_unknown is an array of usages containing a license that still has to be reviewed by the legal team;

  • involved_lic is an array containing all the licenses that are red, orange or grey and that need a derogation for this release;

  • derogations is an array of the derogations that has been made.

An example would be:

endpoint = "api/releases/<int:release_id>/validation_5/"
url = HERMINE_HOST + endpoint


get_response = requests.get(url, headers=headers)
data = get_response.json()

if data["valid"]:
    print("No component require a derogation")
else:
    nb_components_never = len(data["usages_lic_never_allowed"])
    nb_components_context = len(data["usages_lic_context_allowed"])
    nb_components_unknown = len(data["usages_lic_unknown"])
    url = HERMINE_HOST + data["details"]
    print("One or more components require a derogation to the company OS policy")
    print(f"  * {nb_components_never} components require have a license which is never allowed")
    print(f"  * {nb_components_context} components require have a license which is allowed depending on the context")
    print(f"  * {nb_components_unknown} components require have a license which is not considered by the OS policy yet")
    print(f"To solve these issues you can use Hermine's UI : {url}")

The response type is application/json with the following schema :

{
  "valid": true,
  "details": "/relative/url/to/release/validation/page",
  "usages_lic_never_allowed": [
    {
      "id": 0,
      "release": 0,
      "version": 0,
      "status": "Auto",
      "addition_method": "Scan",
      "addition_date": "2019-08-24T14:15:22Z",
      "linking": "Aggregation",
      "component_modified": "Altered",
      "exploitation": "DistributionSourceDistributionNonSource",
      "description": "string",
      "licenses_chosen": [
        0
      ],
      "license_choices": [
        {
          "id": 0,
          "version_constraint": "string",
          "created": "2019-08-24T14:15:22Z",
          "updated": "2019-08-24T14:15:22Z",
          "scope": "string",
          "exploitation": "DistributionSourceDistributionNonSource",
          "expression_in": "string",
          "expression_out": "string",
          "explanation": "string",
          "component": 0,
          "version": 0,
          "author": 0,
          "category": 0,
          "product": 0,
          "release": 0
        }
      ],
      "scope": "string",
      "project": "string"
    }
  ],
  "usages_lic_context_allowed": [
    {
      "id": 0,
      "release": 0,
      "version": 0,
      "status": "Auto",
      "addition_method": "Scan",
      "addition_date": "2019-08-24T14:15:22Z",
      "linking": "Aggregation",
      "component_modified": "Altered",
      "exploitation": "DistributionSourceDistributionNonSource",
      "description": "string",
      "licenses_chosen": [
        0
      ],
      "license_choices": [
        {
          "id": 0,
          "version_constraint": "string",
          "created": "2019-08-24T14:15:22Z",
          "updated": "2019-08-24T14:15:22Z",
          "scope": "string",
          "exploitation": "DistributionSourceDistributionNonSource",
          "expression_in": "string",
          "expression_out": "string",
          "explanation": "string",
          "component": 0,
          "version": 0,
          "author": 0,
          "category": 0,
          "product": 0,
          "release": 0
        }
      ],
      "scope": "string",
      "project": "string"
    }
  ],
  "usages_lic_unknown": [
    {
      "id": 0,
      "release": 0,
      "version": 0,
      "status": "Auto",
      "addition_method": "Scan",
      "addition_date": "2019-08-24T14:15:22Z",
      "linking": "Aggregation",
      "component_modified": "Altered",
      "exploitation": "DistributionSourceDistributionNonSource",
      "description": "string",
      "licenses_chosen": [
        0
      ],
      "license_choices": [
        {
          "id": 0,
          "version_constraint": "string",
          "created": "2019-08-24T14:15:22Z",
          "updated": "2019-08-24T14:15:22Z",
          "scope": "string",
          "exploitation": "DistributionSourceDistributionNonSource",
          "expression_in": "string",
          "expression_out": "string",
          "explanation": "string",
          "component": 0,
          "version": 0,
          "author": 0,
          "category": 0,
          "product": 0,
          "release": 0
        }
      ],
      "scope": "string",
      "project": "string"
    }
  ],
  "involved_lic": [
    {
      "id": 0,
      "spdx_id": "string",
      "long_name": "string",
      "license_version": "string",
      "radical": "string",
      "autoupgrade": true,
      "steward": "string",
      "inspiration_spdx": "string",
      "copyleft": "None",
      "allowed": "always",
      "allowed_explanation": "string",
      "url": "http://example.com",
      "osi_approved": true,
      "fsf_approved": true,
      "foss": "Yes",
      "patent_grant": true,
      "ethical_clause": true,
      "non_commercial": true,
      "non_tivoisation": true,
      "law_choice": "string",
      "venue_choice": "string",
      "liability": "Full",
      "warranty": "Full",
      "comment": "string",
      "verbatim": "string",
      "obligation_set": [
        {
          "id": 0,
          "generic_name": "string",
          "name": "string",
          "verbatim": "string",
          "passivity": "Active",
          "trigger_expl": "DistributionSourceDistributionNonSource",
          "trigger_mdf": "Altered",
          "generic": 0
        }
      ]
    }
  ],
  "derogations": [
    {
      "id": 0,
      "version_constraint": "string",
      "created": "2019-08-24T14:15:22Z",
      "updated": "2019-08-24T14:15:22Z",
      "scope": "string",
      "exploitation": "DistributionSourceDistributionNonSource",
      "linking": "Aggregation",
      "modification": "Altered",
      "justification": "string",
      "component": 0,
      "version": 0,
      "author": 0,
      "category": 0,
      "product": 0,
      "release": 0,
      "license": 0
    }
  ]
}

API endpoint that allows to know if there are Usages of unnacepted licenses in this release. In this case, you must set relevant derogations in Hermine UI.

JUnit

A JUnit endpoint can be used at api/releases/int:release_id/junit/.

It returns a testsuite where each validation step is a testcase.

Example :

<?xml version="1.0" ?>
<testsuites disabled="0" errors="0" failures="1" tests="4">
    <testsuite disabled="0" errors="0" failures="1" name="Foobar" skipped="0" tests="4" time="0">
        <testcase name="Licenses curation"/>
        <testcase name="ANDs confirmation"/>
        <testcase name="Scope exploitations"/>
        <testcase name="License choices"/>
        <testcase name="Policy compatibility">
            <failure type="failure" message="3 invalid component usages"/>
        </testcase>
    </testsuite>
</testsuites>

Endpoints for generic obligations

GET api/generics/

List generic obligations, optionnaly filtered by license or exploitation.

Accept the following filtering parameters to list only obligations triggered by some licenses and usage contexts :

Parameters:
  • spdx (str) – a comma-separated list of SPDX license id

  • exploitation (str) – an exploitation among Usage.EXPLOITATION_CHOICES

  • modification (str) – a modification among Usage.MODIFICATION_CHOICES

  • exploitation – an exploitation among Usage.EXPLOITATION_CHOICES

  • modification – a modification among Usage.MODIFICATION_CHOICES

POST api/generics/sbom/

List generic obligations for a list of components with their licenses and context.

Parameters:
  • packages (list) – a list of package objects with

  • packages.package_id (str) – a arbitrary identifier name (used in the response)

  • packages.spdx (str) – the SPDX identifier for the package license

  • packages.exploitation (str) – an exploitation among Usage.EXPLOITATION_CHOICES

  • packages.modification (str) – a modification among Usage.MODIFICATION_CHOICES

Returns:

a list of generic obligations with a triggered_by attributes containing the package identifier which triggered the obligation

Example

POST /api/generics/sbom/
{
    "packages": [
        {
            "package_id": "foobar",
            "spdx": "MIT",
            "exploitation": "DistributionSource",
            "modificaiton": "Altered"
        },
        {
            "package_id": "barfoo",
            "spdx": "Apache-2.0",
            "exploitation": "DistributionSourceDistributionNonSource",
            "modificaiton": "Unmodified"
        }
    ]
}

200 OK
[
        {
            "id": 1,
            "name": "Patent Grant",
            "description": "",
            "in_core": false,
            "metacategory": "",
            "team": null,
            "passivity": "",
            "triggered_by": ["foobar", "barfoo"]
        },
        {
            "id": 12,
            "name": "Indemnification of contributors",
            "description": "",
            "in_core": false,
            "metacategory": "",
            "team": null,
            "passivity": "",
            "triggered_by": ["barfoo"]
        }
    ]

Generic API endpoints for Models

For the main models of the application, you can get a list of their instances at: /api/<str:class_name>. For example, the list of your products will be at api/products/.

A detailed view for an instance of a class can be found at /api/<str:class_name>/<int:instance_id>. For example: /api/products/1/.

You can check the list of the endpoints at /api/.