Skip to content
This repository was archived by the owner on Feb 27, 2023. It is now read-only.

Commit 3934eb0

Browse files
authored
Feature/content signing (#30)
Feature/content signing
2 parents ac98c4b + f86ab01 commit 3934eb0

File tree

3 files changed

+135
-1
lines changed

3 files changed

+135
-1
lines changed

bintray/bintray.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,3 +514,99 @@ def get_oss_licenses(self):
514514
"""
515515
url = "{}/licenses/oss_licenses".format(Bintray.BINTRAY_URL)
516516
return self._requester.get(url)
517+
518+
# Content Signing
519+
520+
def get_org_gpg_public_key(self, org):
521+
""" Get the organization GPG public key.
522+
523+
The response Content-Type format is 'application/pgp-keys'.
524+
525+
:param org: Organization name
526+
:return: response Content-Type format as 'application/pgp-keys'.
527+
"""
528+
url = "{}/orgs/{}/keys/gpg/public.key".format(Bintray.BINTRAY_URL, org)
529+
return self._requester.get(url)
530+
531+
def get_user_gpg_public_key(self, user):
532+
""" Get the subject GPG public key.
533+
534+
The response Content-Type format is 'application/pgp-keys'.
535+
536+
:param org: Organization name
537+
:return: response Content-Type format as 'application/pgp-keys'.
538+
"""
539+
url = "{}/users/{}/keys/gpg/public.key".format(Bintray.BINTRAY_URL, user)
540+
return self._requester.get(url)
541+
542+
def gpg_sign_version(self, subject, repo, package, version, key_subject=None, passphrase=None,
543+
key_path=None):
544+
""" GPG sign all files associated with the specified version.
545+
546+
GPG signing information may be needed
547+
548+
Security: Authenticated user with 'publish' permission.
549+
550+
:param subject: username or organization
551+
:param repo: repository name
552+
:param package: package name
553+
:param version: package version
554+
:param key_subject: Alternative Bintray subject for the GPG public key
555+
:param passphrase: Optional private key passphrase, if required
556+
:param key_path: Optional private key, if not stored in Bintray
557+
:return: request response
558+
"""
559+
url = "{}/gpg/{}/{}/{}/versions/{}".format(Bintray.BINTRAY_URL, subject, repo, package,
560+
version)
561+
body = {}
562+
if subject:
563+
body['subject'] = key_subject
564+
if passphrase:
565+
body['passphrase'] = passphrase
566+
if key_path:
567+
with open(key_path, 'r') as fd:
568+
body['private_key'] = fd.read()
569+
body = None if body == {} else body
570+
headers = None
571+
if "passphrase" in body and len(body.keys()) == 1:
572+
headers = {"X-GPG-PASSPHRASE": passphrase}
573+
body = None
574+
575+
response = self._requester.post(url, json=body, headers=headers)
576+
577+
self._logger.info("Sign successfully: {}".format(url))
578+
return response
579+
580+
def gpg_sign_file(self, subject, repo, file_path, key_subject=None, passphrase=None,
581+
key_path=None):
582+
""" GPG sign the specified repository file.
583+
584+
GPG signing information may be needed
585+
586+
:param subject: username or organization
587+
:param repo: repository name
588+
:param file_path: file path to be signed
589+
:param key_subject: Alternative Bintray subject for the GPG public key
590+
:param passphrase: Optional private key passphrase, if required
591+
:param key_path: Optional private key, if not stored in Bintray
592+
:return: request response
593+
"""
594+
url = "{}/gpg/{}/{}/{}".format(Bintray.BINTRAY_URL, subject, repo, file_path)
595+
body = {}
596+
if subject:
597+
body['subject'] = key_subject
598+
if passphrase:
599+
body['passphrase'] = passphrase
600+
if key_path:
601+
with open(key_path, 'r') as fd:
602+
body['private_key'] = fd.read()
603+
body = None if body == {} else body
604+
headers = None
605+
if "passphrase" in body and len(body.keys()) == 1:
606+
headers = {"X-GPG-PASSPHRASE": passphrase}
607+
body = None
608+
609+
response = self._requester.post(url, json=body, headers=headers)
610+
611+
self._logger.info("Sign successfully: {}".format(url))
612+
return response

bintray/requester.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
import requests
4+
import json
45

56
from requests.auth import HTTPBasicAuth
67

@@ -31,7 +32,10 @@ def _add_status_code(self, response):
3132
:param response: Requests response
3233
:return: Response JSON
3334
"""
34-
json_data = response.json()
35+
try:
36+
json_data = response.json()
37+
except json.decoder.JSONDecodeError:
38+
json_data = {'message': response.content.decode()}
3539
if isinstance(json_data, list):
3640
json_data.append({"statusCode": response.status_code, "error": not response.ok})
3741
else:

tests/test_content_signing.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import os
4+
from bintray.bintray import Bintray
5+
6+
7+
def test_get_org_gpg_public_key():
8+
bintray = Bintray()
9+
response = bintray.get_org_gpg_public_key("jfrog")
10+
assert response.get("error") == False
11+
assert response.get("statusCode") == 200
12+
assert "BEGIN PGP PUBLIC KEY BLOCK" in response.get("message")
13+
14+
15+
def test_get_user_gpg_public_key():
16+
bintray = Bintray()
17+
response = bintray.get_user_gpg_public_key("bintray")
18+
assert response.get("error") == False
19+
assert response.get("statusCode") == 200
20+
assert "BEGIN PGP PUBLIC KEY BLOCK" in response.get("message")
21+
22+
23+
def test_gpg_sign_version():
24+
bintray = Bintray()
25+
response = bintray.gpg_sign_version("uilianries", "generic", "statistics", "test")
26+
assert {'error': False, 'message': 'success', 'statusCode': 200} == response or \
27+
{'error': False, 'message': 'success', 'statusCode': 201} == response
28+
29+
30+
def test_gpg_sign_file():
31+
bintray = Bintray()
32+
response = bintray.gpg_sign_file("uilianries", "generic", "packages.json")
33+
assert {'error': False, 'message': 'success', 'statusCode': 200} == response or \
34+
{'error': False, 'message': 'success', 'statusCode': 201} == response

0 commit comments

Comments
 (0)