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 eachscope
project
couple the associatedexploitation
;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 compliance actions
- GET api/generics/
List compliance actions, 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 compliance actions 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 compliance actions 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/
.