# Copyright (C) 2025 James Brotosky, Brandon Wickline # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from datetime import datetime import logging import os import pandas as pd from services.agenthandler import selectAgents from services.API import AirlockAPIWrapper from utils.configmanager import load_env from utils.selector import Selector from utils.utils import colorText, get_sanitized_input logger = logging.getLogger(__name__) def otp_activities_by_agent(api: AirlockAPIWrapper): activeagents = api.otp_find_active() awaitingagents = api.otp_find_awaiting() enforcedagents = api.otp_find_enforced() revokedagents = api.otp_find_revoked() # Add a 'status' column to each DataFrame activeagents["status"] = "active" awaitingagents["status"] = "awaiting" enforcedagents["status"] = "enforced" revokedagents["status"] = "revoked" # Combine all into one DataFrame combined_agents = pd.concat( [activeagents, awaitingagents, enforcedagents, revokedagents], ignore_index=True ) combined_agents = combined_agents.sort_values(by="otpid", ascending=False) # Optionally, select specific hosts user_input = ( get_sanitized_input("\nWould you like to search for a specific device? (y/n): ") .strip() .lower() ) if user_input == "y": agentnames = [] agents = selectAgents(api) for agent in agents: agentnames.append(agent.hostname) combined_agents = combined_agents[combined_agents["hostname"].isin(agentnames)] # Present and select rows selected_rows = Selector.select_dataframe_with_mode( combined_agents, columns=["otpid", "hostname", "status", "purpose", "granted"], header="OTP Sessions", ) combined_df = pd.DataFrame() for row in selected_rows: otpid = row["otpid"] hostname = row["hostname"] result = api.otp_get_activities(otpid) result["hostname"] = hostname if not result.empty: logger.info(f"Activities for {hostname} (otpid: {otpid}):\n{result}") combined_df = pd.concat([combined_df, result], ignore_index=True) else: logger.info(f"No activities found for {hostname} (otpid: {otpid})") user_input = ( get_sanitized_input( "\nWould you like to export the results to a CSV file? (y/n): " ) .strip() .lower() ) if user_input == "y": working_dir = load_env("WORKING_DIR") timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") filename = f"otp_activities_{timestamp}.csv" file_path = os.path.join(str(working_dir), filename) combined_df.to_csv(file_path, index=False) logging.info(f"Exported Data to {file_path}") print( colorText( f"\n✅ OTP Activity exported to: {working_dir}\\{filename}", "green", ) ) else: logging.debug("User declined to export the DataFrame.") def otp_revoke(api: AirlockAPIWrapper): activeagents = api.otp_find_active() awaitingagents = api.otp_find_awaiting() activeagents["status"] = "active" awaitingagents["status"] = "awaiting" combined_agents = pd.concat([activeagents, awaitingagents], ignore_index=True) combined_agents = combined_agents.sort_values(by="otpid", ascending=False) # Combine all into one DataFrame combined_agents = pd.concat([activeagents, awaitingagents], ignore_index=True) combined_agents = combined_agents.sort_values(by="otpid", ascending=False) # Optionally, select specific hosts user_input = ( get_sanitized_input("\nWould you like to search for a specific device? (y/n): ") .strip() .lower() ) if user_input == "y": agentnames = [] agents = selectAgents(api) for agent in agents: agentnames.append(agent.hostname) combined_agents = combined_agents[combined_agents["hostname"].isin(agentnames)] # Present and select rows selected_rows = Selector.select_dataframe_with_mode( combined_agents, columns=["otpid", "hostname", "status", "purpose", "granted"], header="OTP Sessions", ) for row in selected_rows: otpid = row["otpid"] hostname = row["hostname"] result = api.otp_revoke(otpid) logger.info(f"{hostname} (otpid: {otpid}):\n{result}")