Skip to content

Commit 180b288

Browse files
committed
Merge branch 'master' of github.com:blackducksoftware/hub-rest-api-python
2 parents 45ddf39 + 6b9ffa7 commit 180b288

File tree

8 files changed

+638
-2
lines changed

8 files changed

+638
-2
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ In v2022.2 of Black Duck the REST API introduced a max page size to protect syst
88

99
**The old HubInstance interface and many of the examples using it do not perform paging and will break as a result of the changes in v2022.2**.
1010

11+
Any issues related to the HubInstance Interface will be closed as *Won't Fix*
12+
13+
Any PRs with new or modified example scripts/utilities **must** use the client interface.
14+
1115
# New in 1.0.0
1216

1317
Introducing the new Client class.
@@ -61,7 +65,18 @@ for project in bd.get_resource(name='projects'):
6165

6266
Example code showing how to work with the new Client can be found in the *examples/client* folder.
6367

68+
**Examples which use the old HubInstance interface -which is not maintained- are not guaranteed to work. Use at your own risk.**
69+
70+
# Version History
71+
72+
Including a version history on a go-forward basis.
73+
74+
## v1.1.0
75+
76+
Retries will be attempted for all HTTP verbs, not just GET.
77+
6478
# Test #
79+
6580
Using [pytest](https://pytest.readthedocs.io/en/latest/contents.html)
6681

6782
```bash

blackduck/Client.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def __init__(self, base_url, timeout, retries, verify):
4242
total=int(retries),
4343
backoff_factor=2, # exponential retry 1, 2, 4, 8, 16 sec ...
4444
status_forcelist=[429, 500, 502, 503, 504],
45-
allowed_methods=['GET']
4645
)
4746

4847
adapter = HTTPAdapter(max_retries=retry_strategy)

blackduck/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
VERSION = (1, 0, 7)
1+
VERSION = (1, 1, 0)
22

33
__version__ = '.'.join(map(str, VERSION))

examples/client/deactivate_users.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
'''
2+
Purpose: Deactivates Black Duck users who are not actively using the system, will warn for users that have never logged in.
3+
4+
Usage:
5+
deactivate_users.py [--dry-run] [--since-days DAYS] [--interactive] [--base-url https://your.blackduck.url] [--token-file token.txt]
6+
7+
required arguments:
8+
--base-url BASE_URL Hub server URL e.g. https://your.blackduck.url
9+
--token-file TOKEN_FILE containing access token
10+
11+
optional arguments:
12+
--dry-run Show actions that would be executed on a normal run
13+
--since-days Number of days since last login for a user to be inactive. Default: 90
14+
--interactive Run in interactive mode to choose which users to deactivate
15+
16+
Examples:
17+
18+
authentication required for all examples below
19+
--base-url https://your.blackduck.url --token-file token.txt
20+
21+
print help message
22+
python deactivate_users.py -h
23+
24+
deactivate all users who haven't logged in for more than 90 days
25+
python deactivate_users.py
26+
27+
log users that would be deactivated who haven't logged in for more than 90 days
28+
python deactivate_users.py --dry-run
29+
30+
deactivate all users who haven't logged in for more than 30 days
31+
python deactivate_users.py --since-days 30
32+
33+
interactively deactivate users who haven't logged in for more than 120 days
34+
python deactivate_users.py --since-days 120 --interactive
35+
'''
36+
37+
from blackduck import Client
38+
import logging
39+
import argparse
40+
41+
def query_yes_no(question, default="yes"):
42+
"""Ask a yes/no question via input() and return their answer.
43+
44+
"question" is a string that is presented to the user.
45+
"default" is the presumed answer if the user just hits <Enter>.
46+
It must be "yes" (the default), "no" or None (meaning
47+
an answer is required of the user).
48+
49+
The "answer" return value is True for "yes" or False for "no".
50+
"""
51+
valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
52+
if default is None:
53+
prompt = " [y/n] "
54+
elif default == "yes":
55+
prompt = " [Y/n] "
56+
elif default == "no":
57+
prompt = " [y/N] "
58+
else:
59+
raise ValueError("invalid default answer: '%s'" % default)
60+
61+
while True:
62+
print(question + prompt)
63+
choice = input().lower()
64+
if default is not None and choice == "":
65+
return valid[default]
66+
elif choice in valid:
67+
return valid[choice]
68+
else:
69+
print("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n")
70+
71+
logging.basicConfig(
72+
level=logging.INFO,
73+
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
74+
)
75+
76+
# Use -h to print help message
77+
parser = argparse.ArgumentParser("Deactivates users in the system")
78+
parser.add_argument("--base-url", required=True, help="Hub server URL e.g. https://your.blackduck.url")
79+
parser.add_argument("--token-file", dest='token_file', required=True, help="containing access token")
80+
parser.add_argument("--no-verify", dest='verify', action='store_false', help="disable TLS certificate verification")
81+
parser.add_argument("-d", "--dry-run", action='store_true', help=f"Run in dry-run mode to determine users it will update")
82+
parser.add_argument("-s", "--since-days", default=90, type=int, help=f"Number of days since last login for a user to be inactive. Default: 90")
83+
parser.add_argument("-i", "--interactive", action='store_true', help=f"Run in interactive mode to manually choose which users to deactivate, has no effect when run with dry-run mode")
84+
85+
args = parser.parse_args()
86+
87+
with open(args.token_file, 'r') as tf:
88+
access_token = tf.readline().strip()
89+
90+
bd = Client(
91+
base_url=args.base_url,
92+
token=access_token,
93+
verify=args.verify
94+
)
95+
96+
system_users = ['sysadmin', 'anonymous', 'blackduck_system', 'default-authenticated-user']
97+
98+
dormant_params = {
99+
"sinceDays": args.since_days
100+
}
101+
102+
headers = {
103+
'accept': "application/vnd.blackducksoftware.user-4+json"
104+
}
105+
106+
for user in bd.get_items("api/dormant-users", params=dormant_params, headers=headers):
107+
if user['username'] not in system_users:
108+
# If the user has logged in before
109+
if 'lastLogin' in user:
110+
if args.dry_run:
111+
logging.info(f"Will mark user '{user['username']}' as inactive, their last login date was {user['lastLogin']}")
112+
else:
113+
114+
user_url = user['_meta']['href'].replace("/last-login", "")
115+
116+
# Get the user data to keep all data the same except for the active parameter
117+
user_data = bd.get_json(user_url)
118+
# Skip already inactive users
119+
if user_data['active'] == False:
120+
continue
121+
122+
# If interactive mode is running, prompt the user before disabling
123+
if args.interactive:
124+
proceed = query_yes_no(f"User '{user['username']}' last login date was {user['lastLogin']}, do you want to mark them as inactive?")
125+
if not proceed:
126+
logging.info(f"Skipping user '{user['username']}' due to interactive input")
127+
continue
128+
logging.info(f"Marking user '{user['username']}' as inactive")
129+
deactivate_params = {"userName": user_data['userName'],
130+
"externalUserName": user_data['externalUserName'] if 'externalUserName' in user_data else None,
131+
"firstName": user_data['firstName'],
132+
"lastName": user_data['lastName'],
133+
"email": user_data['email'],
134+
"type": user_data['type'],
135+
"active": False}
136+
# Deactivate the user
137+
response = bd.session.put(user_url, json=deactivate_params)
138+
if response.status_code == 200:
139+
logging.info(f"User '{user['username']}' updated successfully")
140+
elif response.status_code == 404:
141+
logging.info(f"User '{user['username']}' 404 Not found")
142+
else:
143+
logging.error(f"Unexpected error updating user '{user['username']}'")
144+
bd.http_error_handler(response)
145+
#Else the user has never logged in
146+
else:
147+
logging.warning(f"User '{user['username']}' has never logged in")
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'''
2+
Created on January 23, 2023
3+
@author: christopher-mackle
4+
Delete the oldest project version (last created or last updated) when you've hit your max version limit
5+
'''
6+
7+
from datetime import datetime
8+
from blackduck.Client import Client
9+
import argparse
10+
11+
parser = argparse.ArgumentParser("Get a specific component and list its vulnerabilities")
12+
parser.add_argument("--url", dest='url', required=True, help="Hub server URL e.g. https://your.blackduck.url")
13+
parser.add_argument("--token", dest='token', required=True, help="Hub server access token")
14+
parser.add_argument("--project", dest='project_name', required=True, help="Name of project")
15+
parser.add_argument("--mode", dest='mode', required=False, default='createdAt', help="Use createdAt or updatedAt")
16+
parser.add_argument("--count", dest='count_total', required=False, default=9, help="Max versions - 1")
17+
args = parser.parse_args()
18+
19+
TOKEN = args.token
20+
BASE_URL = args.url
21+
PROJECT_NAME = args.project_name
22+
23+
try:
24+
bd = Client(
25+
token=TOKEN,
26+
base_url=BASE_URL,
27+
verify=False # TLS certificate verification
28+
)
29+
except:
30+
print("Could not authenticate to your Black Duck server with the given URL and token.")
31+
32+
def deleteVersion(mode):
33+
count = 0
34+
time_format = '%Y-%m-%dT%H:%M:%S.%fZ'
35+
for version in bd.get_resource('versions', project):
36+
count = count + 1
37+
if count == 1:
38+
oldest_version = version
39+
else:
40+
oldestVersionDate = datetime.strptime(oldest_version[mode], time_format)
41+
currentVersionDate = datetime.strptime(version[mode], time_format)
42+
if currentVersionDate < oldestVersionDate:
43+
oldest_version = version
44+
if count > int(args.count_total):
45+
print('Too many project versions. Deleting oldest project version before scanning.')
46+
projectVersionToDelete = oldest_version['_meta']['href']
47+
r = bd.session.delete(projectVersionToDelete)
48+
r.raise_for_status()
49+
print("Project version " + oldest_version['versionName'] + ' has been deleted.')
50+
51+
if __name__ == '__main__':
52+
found = False
53+
for project in bd.get_resource('projects'):
54+
if project['name'] == PROJECT_NAME:
55+
found = True
56+
if args.mode == 'createdAt':
57+
deleteVersion(args.mode)
58+
elif args.mode == 'updatedAt':
59+
deleteVersion('settingUpdatedAt')
60+
else:
61+
print("Invalid mode: " + args.mode)
62+
print("Valid modes: createdAt, updatedAt")
63+
if not found:
64+
print("Project " + PROJECT_NAME + " not found.")

0 commit comments

Comments
 (0)