Skip to content

Commit b9a2128

Browse files
authored
Merge pull request #158 from skiyooka/master
Last minute fixes, examples, and updated documentation.
2 parents eadc458 + eec9e9e commit b9a2128

File tree

6 files changed

+196
-75
lines changed

6 files changed

+196
-75
lines changed

README.md

Lines changed: 53 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,54 @@
1-
## Overview ##
1+
# Overview
22

33
The hub-rest-api-python provides Python bindings for Hub REST API.
44

5-
:warning:Recently CVE-2020- 27589, a medium severity security defect, was discovered in the [blackduck PyPi](https://pypi.org/project/blackduck/) library which affects versions 0.0.25 – 0.0.52 that could suppress certificate validation if the calling code used either the upload_scan or download_project_scans methods. These methods did not enforce certificate validation. Other methods in the library are not affected. The defect was fixed in version 0.0.53.
5+
:warning:Recently [CVE-2020-27589](https://nvd.nist.gov/vuln/detail/CVE-2020-27589), a medium severity security defect,
6+
was discovered in the [blackduck PyPi](https://pypi.org/project/blackduck) library which affects versions 0.0.25 – 0.0.52
7+
that could suppress certificate validation if the calling code used either the upload_scan or download_project_scans
8+
methods. These methods did not enforce certificate validation. Other methods in the library are not affected.
9+
The defect was fixed in version 0.0.53.
610

7-
Customers using the [blackduck library](https://pypi.org/project/blackduck/) should upgrade to version 0.0.53, or later, to implement the fix.
11+
Customers using the [blackduck library](https://pypi.org/project/blackduck) should upgrade to version 0.0.53, or later, to implement the fix.
812

9-
## To use ##
13+
# New in 1.0.0
14+
15+
Introducing the new Client class.
16+
17+
In order to provide a more robust long-term connection, faster performance, and an overall better experience a new
18+
Client class has been designed.
19+
20+
It is backed by a [Requests session](https://docs.python-requests.org/en/master/user/advanced/#session-objects)
21+
object. The user specifies a base URL, timeout, retries, proxies, and TLS verification upon initialization and these
22+
attributes are persisted across all requests.
23+
24+
At the REST API level, the Client class provides a consistent way to discover and traverse public resources, uses a
25+
[generator](https://wiki.python.org/moin/Generators) to fetch all items using pagination, and automatically renews
26+
the bearer token.
27+
28+
To read more about the new Client class see the [Client User Guide](https://github.com/blackducksoftware/hub-rest-api-python/wiki/Client-User-Guide)
29+
on the [Hub REST API Python Wiki](https://github.com/blackducksoftware/hub-rest-api-python/wiki).
30+
31+
### Important Note
32+
The old HubInstance (in HubRestApi.py) keeps its existing functionality for backwards compatibility and therefore does
33+
**not** currently leverage any of the new features in the Client class.
34+
35+
We believe that the new features are compelling enough to strongly encourage users to consider moving from HubInstance
36+
to Client. Please give it a try and let us know what you think!
37+
38+
# To use
1039

1140
```
12-
pip install blackduck
41+
pip3 install blackduck
1342
```
1443

1544
```python
1645
from blackduck import Client
17-
import json
1846
import logging
1947
import os
2048

2149
logging.basicConfig(
2250
level=logging.INFO,
23-
format='[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s'
51+
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
2452
)
2553

2654
bd = Client(
@@ -35,57 +63,24 @@ for project in bd.get_resource(name='projects'):
3563

3664
### Examples
3765

38-
Example code showing how to do various things can be found in the *examples* folder.
39-
40-
## Build ##
41-
42-
You should be using [virtualenv](https://pypi.org/project/virtualenv/), [virtrualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/) to make things easy on yourself.
66+
Example code showing how to work with the new Client can be found in the *examples/client* folder.
4367

44-
Ref: [Packaging Python Projects Tutorial](https://packaging.python.org/tutorials/packaging-projects/)
68+
# Test #
69+
Using [pytest](https://pytest.readthedocs.io/en/latest/contents.html)
4570

46-
### Build the blackduck packages
47-
To build both the source distribution package and the wheel package,
71+
```bash
72+
git clone https://github.com/blackducksoftware/hub-rest-api-python.git
73+
cd hub-rest-api-python
74+
# optional but advisable: create/use virtualenv
75+
# you should have 3.x+, e.g. Python 3.8.0+
4876

49-
```
5077
pip3 install -r requirements.txt
51-
python3 setup.py sdist bdist_wheel
52-
```
53-
54-
### Distribute the package
55-
56-
Requires you have an account on either/both [PyPi](https://pypi.org) and [Test PyPi](https://test.pypi.org) *AND* you must be a package maintainer.
57-
58-
Send a request to [email protected] or [email protected] if you want to be listed as a package maintainer.
59-
60-
#### To PyPi
61-
62-
Upload to PyPi,
63-
64-
```
65-
twine upload dist/*
66-
```
67-
68-
Then try installing it from PyPy
69-
70-
```
71-
pip install blackduck
72-
```
73-
74-
#### To Test PyPi
75-
76-
Upload to Test PyPi,
77-
78-
```
79-
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
80-
```
81-
82-
Then try installing it from Test PyPy
83-
84-
```
85-
pip install --index-url https://test.pypi.org/simple/ blackduck
78+
pip3 install .
79+
cd test
80+
pytest
8681
```
8782

88-
### Install package locally
83+
## Install package locally
8984

9085
Do this when testing a new version.
9186

@@ -96,27 +91,17 @@ pip3 install -r requirements.txt
9691
pip3 install .
9792
```
9893

99-
## Test ##
100-
Using (pytest)[https://pytest.readthedocs.io/en/latest/contents.html]
94+
To uninstall:
10195

102-
```bash
103-
git clone https://github.com/blackducksoftware/hub-rest-api-python.git
104-
cd hub-rest-api-python
105-
# optional but advisable: create/use virtualenv
106-
# you should have 3.x+, e.g. Python 3.7.0
107-
108-
pip3 install -r requirements.txt
109-
pip3 install .
110-
cd test
111-
pytest
96+
```
97+
pip3 uninstall blackduck
11298
```
11399

114100
## Where can I get the latest release? ##
115-
This package is available on PyPi,
101+
This package is available on PyPi:
116102

117103
`pip3 install blackduck`
118104

119105
## Documentation ##
120-
Documentation for hub-rest-api-python can be found on the base project: [Hub REST API Python Wiki](https://github.com/blackducksoftware/hub-rest-api-python/wiki)
121-
122-
106+
Documentation for hub-rest-api-python can be found on the base project:
107+
[Hub REST API Python Wiki](https://github.com/blackducksoftware/hub-rest-api-python/wiki)

blackduck/Authentication.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def __init__(self, session, username, password):
114114
self.username = username
115115
self.password = password
116116
self.bearer_token = None
117+
self.csrf_token = None
117118
self.valid_until = datetime.utcnow()
118119

119120
def __call__(self, request):
@@ -123,6 +124,7 @@ def __call__(self, request):
123124

124125
request.headers.update({
125126
"authorization": f"bearer {self.bearer_token}",
127+
"X-CSRF-TOKEN": self.csrf_token
126128
})
127129

128130
return request
@@ -146,6 +148,7 @@ def authenticate(self):
146148
try:
147149
cookie = response.headers['Set-Cookie']
148150
self.bearer_token = cookie[cookie.index('=') + 1:cookie.index(';')]
151+
self.csrf_token = response.headers['X-CSRF-TOKEN']
149152
# As of 2021.2 the bearer token is good for 2 hours but there
150153
# is no explicit reference to expiry time in the response.
151154
#

blackduck/Client.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ def __init__(self, base_url, timeout, retries, verify):
5858
def request(self, method, url, **kwargs):
5959
kwargs['timeout'] = self._timeout
6060

61-
# set default media type if not provided
62-
headers = {
63-
'accept': "application/json",
64-
'content-type': "application/json"
65-
}
66-
headers.update(kwargs.pop('headers', dict()))
67-
kwargs['headers'] = headers
61+
if method.lower() == 'get':
62+
headers = kwargs.pop('headers', dict())
63+
lc_keys = {key.lower(): value for (key, value) in headers.items()}
64+
if 'accept' not in lc_keys and 'content-type' not in lc_keys:
65+
# set default media type only if neither 'accept' nor 'content-type'
66+
# exist as some endpoints may only accept one or the other but not both
67+
lc_keys['accept'] = "application/json"
68+
lc_keys['content-type'] = "application/json"
69+
kwargs['headers'] = lc_keys
6870

6971
url = urljoin(self.base_url, url)
7072
return super().request(method, url, **kwargs)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from blackduck import Client
2+
3+
import argparse
4+
import logging
5+
import requests
6+
7+
logging.basicConfig(
8+
level=logging.DEBUG,
9+
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
10+
)
11+
12+
parser = argparse.ArgumentParser()
13+
parser.add_argument("--base-url", required=True, help="Hub server URL e.g. https://your.blackduck.url")
14+
parser.add_argument("--token-file", dest='token_file', required=True, help="containing access token")
15+
parser.add_argument("--no-verify", dest='verify', action='store_false', help="disable TLS certificate verification")
16+
parser.add_argument("--project", required=True, help="Project name")
17+
args = parser.parse_args()
18+
19+
with open(args.token_file, 'r') as tf:
20+
access_token = tf.readline().strip()
21+
22+
bd = Client(
23+
base_url=args.base_url,
24+
token=access_token,
25+
verify=args.verify
26+
)
27+
28+
project_name = args.project
29+
30+
# POST
31+
project_data = {
32+
'name': project_name,
33+
'description': "some description",
34+
'projectLevelAdjustments': True,
35+
}
36+
37+
try:
38+
r = bd.session.post("/api/projects", json=project_data)
39+
r.raise_for_status()
40+
print(f"created project {r.links['project']['url']}")
41+
except requests.HTTPError as err:
42+
# more fine grained error handling here; otherwise:
43+
bd.http_error_handler(err)
44+
45+
# GET
46+
params = {
47+
'q': [f"name:{project_name}"]
48+
}
49+
project_url = None
50+
for project in bd.get_items("/api/projects", params=params):
51+
if project['name'] == project_name:
52+
project_url = bd.list_resources(project)['href']
53+
print(f"project url: {project_url}")
54+
55+
# DELETE
56+
try:
57+
r = bd.session.delete(project_url)
58+
r.raise_for_status()
59+
print("deleted project")
60+
except requests.HTTPError as err:
61+
if err.response.status_code == 404:
62+
print("not found")
63+
else:
64+
bd.http_error_handler(err)

examples/client/password.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from blackduck import Client
2+
from blackduck.Client import HubSession
3+
from blackduck.Authentication import CookieAuth
4+
5+
import argparse
6+
import logging
7+
from pprint import pprint
8+
9+
logging.basicConfig(
10+
level=logging.DEBUG,
11+
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
12+
)
13+
14+
parser = argparse.ArgumentParser()
15+
parser.add_argument("--base-url", required=True, help="Hub server URL e.g. https://your.blackduck.url")
16+
parser.add_argument("--username", required=True, help="Hub user")
17+
parser.add_argument("--password", required=True, help="Hub user")
18+
parser.add_argument("--no-verify", dest='verify', action='store_false', help="disable TLS certificate verification")
19+
args = parser.parse_args()
20+
21+
verify = args.verify
22+
base_url = args.base_url
23+
session = HubSession(base_url, timeout=15.0, retries=3, verify=verify)
24+
auth = CookieAuth(session, username=args.username, password=args.password)
25+
26+
bd = Client(
27+
base_url=base_url,
28+
session=session,
29+
auth=auth,
30+
verify=verify
31+
)
32+
33+
pprint(bd.list_resources())

examples/client/quickstart.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from blackduck import Client
2+
3+
import argparse
4+
import logging
5+
from pprint import pprint
6+
7+
logging.basicConfig(
8+
level=logging.DEBUG,
9+
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
10+
)
11+
12+
parser = argparse.ArgumentParser()
13+
parser.add_argument("--base-url", required=True, help="Hub server URL e.g. https://your.blackduck.url")
14+
parser.add_argument("--token-file", dest='token_file', required=True, help="containing access token")
15+
parser.add_argument("--no-verify", dest='verify', action='store_false', help="disable TLS certificate verification")
16+
args = parser.parse_args()
17+
18+
with open(args.token_file, 'r') as tf:
19+
access_token = tf.readline().strip()
20+
21+
bd = Client(
22+
base_url=args.base_url,
23+
token=access_token,
24+
verify=args.verify
25+
)
26+
27+
for project in bd.get_resource('projects'):
28+
print(f"Project: {project['name']}")
29+
print("Project list_resources():")
30+
pprint(bd.list_resources(project))
31+
for version in bd.get_resource('versions', project):
32+
print(f"Version: {version['versionName']}")
33+
print("Exiting after printing first project and version")
34+
quit(0)

0 commit comments

Comments
 (0)