Skip to content

Commit 72b023a

Browse files
committed
fixed more bugs
1 parent 4c78cef commit 72b023a

4 files changed

Lines changed: 95 additions & 10 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "bloodhound-cli" # The package name as installed via pip (pip install bloodhound-cli)
7-
version = "1.1.1" # Bump: add CE skeleton, --edition, --verbose, rich optional
7+
version = "1.1.2" # Bump: add CE skeleton, --edition, --verbose, rich optional
88
description = "CLI for querying BloodHound data (Legacy Neo4j + CE skeleton)."
99
readme = "README.md"
1010
authors = [

src/bloodhound_cli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# src/bloodhound_cli/__init__.py
2-
__version__ = "1.1.1"
2+
__version__ = "1.1.2"

src/bloodhound_cli/core/ce.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,88 @@ def upload_data_and_wait(self, file_path: str, poll_interval: int = 5, timeout_s
694694
print(f"Error in upload and wait: {e}")
695695
return False
696696

697+
def verify_token(self) -> bool:
698+
"""Verify if the current token is valid by making a test request"""
699+
try:
700+
# Try to make a simple API call to verify the token
701+
response = self.session.get(
702+
f"{self.base_url}/api/v2/file-upload",
703+
headers=self._get_headers()
704+
)
705+
return response.status_code == 200
706+
except Exception:
707+
return False
708+
709+
def auto_renew_token(self) -> bool:
710+
"""Automatically renew the token using stored credentials"""
711+
try:
712+
# Load config to get stored credentials
713+
config = configparser.ConfigParser()
714+
config.read(os.path.expanduser("~/.bloodhound_config"))
715+
716+
if 'CE' not in config:
717+
return False
718+
719+
username = config['CE'].get('username', 'admin')
720+
password = config['CE'].get('password')
721+
base_url = config['CE'].get('base_url', 'http://localhost:8080')
722+
723+
if not password:
724+
return False
725+
726+
# Create a new session for authentication (without the expired token)
727+
import requests
728+
temp_session = requests.Session()
729+
temp_session.verify = self.session.verify
730+
731+
# Authenticate with stored credentials using the temp session
732+
login_url = f"{base_url}/api/v2/login"
733+
payload = {"login_method": "secret", "username": username, "secret": password}
734+
735+
response = temp_session.post(login_url, json=payload, timeout=60)
736+
if response.status_code >= 400:
737+
return False
738+
739+
data = response.json()
740+
token = None
741+
if isinstance(data, dict):
742+
data_field = data.get("data")
743+
if isinstance(data_field, dict):
744+
token = data_field.get("session_token")
745+
if not token:
746+
token = data.get("token") or data.get("access_token") or data.get("jwt")
747+
748+
if not token:
749+
return False
750+
751+
# Update the stored token and our session
752+
config['CE']['api_token'] = token
753+
with open(os.path.expanduser("~/.bloodhound_config"), 'w') as f:
754+
config.write(f)
755+
756+
# Update our session with the new token
757+
self.api_token = token
758+
self.session.headers.update({"Authorization": f"Bearer {token}"})
759+
760+
return True
761+
762+
except Exception as e:
763+
print(f"Error auto-renewing token: {e}")
764+
return False
765+
766+
def ensure_valid_token(self) -> bool:
767+
"""Ensure we have a valid token, auto-renew if necessary"""
768+
if not self.api_token:
769+
return self.auto_renew_token()
770+
771+
# Check if current token is valid
772+
if self.verify_token():
773+
return True
774+
775+
# Token is invalid, try to renew
776+
print("Token expired, attempting to renew...")
777+
return self.auto_renew_token()
778+
697779
def close(self):
698780
"""Close the HTTP session"""
699781
try:

src/bloodhound_cli/main.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ def output_results(results, output_file=None, verbose=False, result_type="result
4848
# Fallback to console output
4949
for result in results:
5050
print(result)
51-
else:
52-
# Console output
53-
for result in results:
54-
print(result)
51+
else:
52+
# Console output
53+
for result in results:
54+
print(result)
5555

5656

5757
def get_client(edition: str, **kwargs):
@@ -84,8 +84,9 @@ def get_client(edition: str, **kwargs):
8484
verify=kwargs.get('verify', True)
8585
)
8686

87-
# Only authenticate if no token is available (either from config or parameters)
88-
if not client.api_token:
87+
# Ensure we have a valid token
88+
if not client.ensure_valid_token():
89+
# If auto-renewal failed, try manual authentication
8990
username = kwargs.get('username', 'admin')
9091
password = kwargs.get('ce_password', kwargs.get('password', 'Bloodhound123!'))
9192
client.authenticate(username, password)
@@ -165,7 +166,7 @@ def cmd_users(args):
165166
print(f"Found {len(users)} {user_type} in domain {args.domain}")
166167

167168
# Output results to console or file
168-
output_results(users, args.output, args.verbose, user_type)
169+
output_results(users, args.output, False, user_type)
169170

170171
finally:
171172
client.close()
@@ -197,7 +198,7 @@ def cmd_computers(args):
197198
print(f"Found {len(computers)} computers in domain {args.domain}")
198199

199200
# Output results to console or file
200-
output_results(computers, args.output, args.verbose, "computers")
201+
output_results(computers, args.output, False, "computers")
201202

202203
finally:
203204
client.close()
@@ -418,6 +419,8 @@ def cmd_auth(args):
418419

419420
config['CE']['base_url'] = args.url
420421
config['CE']['api_token'] = token
422+
config['CE']['username'] = args.username
423+
config['CE']['password'] = password # Store password for auto-renewal
421424

422425
# Update GENERAL section
423426
if 'GENERAL' not in config:

0 commit comments

Comments
 (0)