def __init__(self): self.instance_table = InstanceTable() self.model_table = ModelTable() self.rack_table = RackTable() self.user_table = UserTable() self.dc_table = DatacenterTable() self.rack_height = 42
def __init__(self): self.AUTH_MANAGER = AuthManager() self.VALIDATOR = Validator() self.USER_TABLE = UserTable() self.BLACKLIST = [] with open(dirname + blacklist_file, "r") as infile: contents = json.load(infile) self.BLACKLIST = contents.get("blacklist")
def __init__(self): self.instance_table = InstanceTable() self.model_table = ModelTable() self.rack_table = RackTable() self.user_table = UserTable() self.dc_table = DatacenterTable() self.cp_table = ChangePlanTable() self.cp_action_table = ChangePlanActionTable() self.instance_manager = InstanceManager() self.rack_height = 42 self.cp_asset_set = set() self.decom_asset_set = set() self.no_pow_conflict = False
def new_user(): """ Create a new user """ data: JSON = request.get_json() user_table: UserTable = UserTable() try: username: str = data["username"] password: str = data["password"] display_name: str = data["display_name"] email: str = data["email"] privilege: str = data["privilege"] user: User = User( username=username, display_name=display_name, email=email, password=password, privilege=privilege, ) user_table.add_user(user=user) except KeyError: return HTTPStatus.BAD_REQUEST except DBWriteException: return HTTPStatus.INTERNAL_SERVER_ERROR return HTTPStatus.OK
def user(username: str): """ Get a user """ user_table: UserTable = UserTable() user: Optional[User] = user_table.get_user(username=username) if user is None: return HTTPStatus.NOT_FOUND return user.make_json()
class ChangePlanValidator: def __init__(self): self.instance_table = InstanceTable() self.model_table = ModelTable() self.rack_table = RackTable() self.user_table = UserTable() self.dc_table = DatacenterTable() self.cp_table = ChangePlanTable() self.cp_action_table = ChangePlanActionTable() self.instance_manager = InstanceManager() self.rack_height = 42 self.cp_asset_set = set() self.decom_asset_set = set() self.no_pow_conflict = False def validate_action(self, cp_action, all_cp_actions): self.cp_asset_set = set() self.decom_asset_set = set() self.no_pow_conflict = False cp_validation_result = self._validate_change_plan( cp_action.change_plan_id) if cp_validation_result != Constants.API_SUCCESS: return cp_validation_result if cp_action.action == Constants.CREATE_KEY: return self._create_action_validate(cp_action, all_cp_actions) elif cp_action.action == Constants.UPDATE_KEY: return self._edit_action_validate(cp_action, all_cp_actions) elif cp_action.action == Constants.DECOMMISSION_KEY: return self._decom_action_validate(cp_action, all_cp_actions) else: return Constants.API_SUCCESS def _validate_change_plan(self, cp_id: int): change_plan = self.cp_table.get_change_plan(cp_id) if change_plan is None: return "Change plan actions must correspond to an existing change plan." if change_plan.executed: return "Cannot modify a change plan that has already been executed" return Constants.API_SUCCESS def _create_action_validate(self, cp_action, all_cp_actions): instance = self.instance_manager.make_instance(cp_action.new_record) datacenter = self.dc_table.get_datacenter(instance.datacenter_id) if datacenter is None: return "The datacenter does not exist. Please select a valid datacenter." input_validation_result = self._validate_inputs(instance, datacenter) if input_validation_result != Constants.API_SUCCESS: return input_validation_result model_template = self.model_table.get_model(instance.model_id) if model_template is None: return "The model does not exist." if (instance.mount_type != Constants.BLADE_KEY) and (not datacenter.is_offline_storage): instance_bottom = int(instance.rack_position) instance_top = instance_bottom + int(model_template.height) - 1 if instance_top > self.rack_height: return "The placement of the instance exceeds the height of the rack." else: instance_bottom = 0 instance_top = 0 new_asset_number = instance.asset_number new_hostname = instance.hostname pow_connections = set(instance.power_connections) prev_action_val_result = self._validate_vs_prev_actions( instance, all_cp_actions, cp_action, new_asset_number, new_hostname, pow_connections, instance_bottom, instance_top, datacenter, ) if prev_action_val_result != Constants.API_SUCCESS: return prev_action_val_result common_val_result = self._common_validations(instance, cp_action, instance_bottom, instance_top, datacenter) if common_val_result != Constants.API_SUCCESS: return common_val_result if (not datacenter.is_offline_storage) and (instance.mount_type != Constants.BLADE_KEY): if not self.no_pow_conflict: rack = self.rack_table.get_rack(instance.rack_label, instance.datacenter_id) for p_connection in instance.power_connections: char1 = p_connection[0].upper() num = int(p_connection[1:]) if char1 == "L": pdu_arr = rack.pdu_left elif char1 == "R": pdu_arr = rack.pdu_right else: return "Invalid power connection. Please specify left or right PDU." if pdu_arr[num - 1] == 1: return f"There is already an asset connected at PDU {char1}{num}. Please pick an empty PDU port." connection_validation_result = self._validate_connections( instance.network_connections, instance.hostname, cp_action, all_cp_actions, ) if connection_validation_result != Constants.API_SUCCESS: return connection_validation_result return Constants.API_SUCCESS def _edit_action_validate(self, cp_action, all_cp_actions): instance = self.instance_manager.make_instance(cp_action.new_record) datacenter = self.dc_table.get_datacenter(instance.datacenter_id) if datacenter is None: return "The datacenter does not exist. Please select a valid datacenter." input_validation_result = self._validate_inputs(instance, datacenter) if input_validation_result != Constants.API_SUCCESS: return input_validation_result model_template = self.model_table.get_model(instance.model_id) if model_template is None: return "The model does not exist." if (instance.mount_type != Constants.BLADE_KEY) and (not datacenter.is_offline_storage): instance_bottom = int(instance.rack_position) instance_top = instance_bottom + int(model_template.height) - 1 if instance_top > self.rack_height: return "The placement of the instance exceeds the height of the rack." else: instance_bottom = 0 instance_top = 0 prev_update_in_plan = self.cp_action_table.get_newest_asset_record_in_plan( cp_action.change_plan_id, cp_action.original_asset_number, 999999999, ) if (prev_update_in_plan is not None and prev_update_in_plan.action != Constants.COLLATERAL_KEY and prev_update_in_plan.step != cp_action.step): return f"This asset is already being modified in step {prev_update_in_plan.step}. Please update your desired information there." new_asset_number = instance.asset_number new_hostname = instance.hostname pow_connections = set(instance.power_connections) prev_action_val_result = self._validate_vs_prev_actions( instance, all_cp_actions, cp_action, new_asset_number, new_hostname, pow_connections, instance_bottom, instance_top, datacenter, ) if prev_action_val_result != Constants.API_SUCCESS: return prev_action_val_result common_val_result = self._common_validations(instance, cp_action, instance_bottom, instance_top, datacenter) if common_val_result != Constants.API_SUCCESS: return common_val_result if (not datacenter.is_offline_storage) and (instance.mount_type != Constants.BLADE_KEY): if prev_update_in_plan is not None: prev_connections = prev_update_in_plan.new_record.get( Constants.NETWORK_CONNECTIONS_KEY) else: prev_instance = self.instance_table.get_instance_by_asset_number( cp_action.original_asset_number) prev_connections = prev_instance.network_connections connection_validation_result = self._validate_connections_edit( instance.network_connections, instance.hostname, cp_action, all_cp_actions, prev_connections, ) if connection_validation_result != Constants.API_SUCCESS: return connection_validation_result return Constants.API_SUCCESS def _decom_action_validate(self, cp_action, all_cp_actions): asset = self.instance_table.get_instance_by_asset_number( cp_action.original_asset_number) if asset is None: return f"Asset with asset number {cp_action.original_asset_number} does not exist." prev_update_in_plan = self.cp_action_table.get_newest_asset_record_in_plan( cp_action.change_plan_id, cp_action.original_asset_number, 999999999, ) if (prev_update_in_plan is not None and prev_update_in_plan.action != Constants.COLLATERAL_KEY and prev_update_in_plan.step != cp_action.step): return f"This asset is already being modified in step {prev_update_in_plan.step}. Please update your desired information there." if asset.mount_type == Constants.CHASIS_KEY: no_conflict = set() for a in all_cp_actions: if a.new_record.get( Constants.CHASSIS_HOSTNAME_KEY) == asset.hostname: return f"A blade chassis must be empty before it can be decommissioned. A blade is placed in the chassis in step {a.step} of the change plan." else: no_conflict.add(a.original_asset_number) blade_list = self.instance_table.get_blades_by_chassis_hostname( asset.hostname) if blade_list is not None: for blade in blade_list: if not blade.asset_number in no_conflict: return f"A blade chassis must be empty before it can be decommissioned. Blade with asset number {blade.asset_number} is in the chassis." return Constants.API_SUCCESS def _validate_inputs(self, instance, datacenter): if (not datacenter.is_offline_storage) and (instance.mount_type != Constants.BLADE_KEY): rack = self.rack_table.get_rack(instance.rack_label, instance.datacenter_id) if rack is None: return f"Rack {instance.rack_label} does not exist in datacenter {datacenter.name}. Assets must be created on preexisting racks" rack_u_pattern = re.compile("[0-9]+") if rack_u_pattern.fullmatch(str(instance.rack_position)) is None: return "The value for Rack U must be a positive integer." asset_pattern = re.compile("[0-9]{6}") if asset_pattern.fullmatch(str(instance.asset_number)) is None: return "Asset numbers must be 6 digits long and only contain numbers." if instance.hostname is not None and instance.hostname != "": if len(instance.hostname) > 64: return "Hostnames must be 64 characters or less" host_pattern = re.compile("[a-zA-Z]*[A-Za-z0-9-]*[A-Za-z0-9]") if host_pattern.fullmatch(instance.hostname) is None: return "Hostnames must start with a letter, only contain letters, numbers, periods, and hyphens, and end with a letter or number." if instance.owner != "" and self.user_table.get_user( instance.owner) is None: return f"The owner {instance.owner} is not an existing user. Please enter the username of an existing user." return Constants.API_SUCCESS def _validate_vs_prev_actions( self, instance, all_cp_actions, cp_action, new_asset_number, new_hostname, pow_connections, instance_bottom: int, instance_top: int, datacenter, ): for prev_action in all_cp_actions: if prev_action.step >= cp_action.step: continue if prev_action.action != Constants.CREATE_KEY: self.cp_asset_set.add(prev_action.original_asset_number) if prev_action.action == Constants.DECOMMISSION_KEY: self.decom_asset_set.add(prev_action.original_asset_number) # Asset Number Validation if (prev_action.original_asset_number == new_asset_number and prev_action.action != Constants.DECOMMISSION_KEY): return f"Asset numbers must be unique. An asset with asset number {instance.asset_number} already exists." if (prev_action.new_record.get( Constants.ASSET_NUMBER_KEY) == new_asset_number and prev_action.action == Constants.CREATE_KEY): return f"Asset with asset number {new_asset_number} is already being created in step {prev_action.step} of the change plan." # Hostname Validation if new_hostname is not None and new_hostname != "": if (prev_action.new_record.get( Constants.HOSTNAME_KEY) == new_hostname and prev_action.action != Constants.DECOMMISSION_KEY): return f"Asset with hostname {new_hostname} already exists in step {prev_action.step} of the change plan." # Location Validation if not datacenter.is_offline_storage: if instance.mount_type != Constants.BLADE_KEY: if (prev_action.action == Constants.CREATE_KEY or prev_action.action == Constants.UPDATE_KEY): model_id = self.instance_manager.get_model_id_from_name( prev_action.new_record.get(Constants.MODEL_KEY)) model = self.model_table.get_model(model_id) other_bottom = int( prev_action.new_record.get( Constants.RACK_POSITION_KEY)) other_top = (int( prev_action.new_record.get( Constants.RACK_POSITION_KEY)) + model.height - 1) if (other_bottom >= instance_bottom and other_bottom <= instance_top): result = f"The asset placement conflicts with asset with asset number {prev_action.new_record.get(Constants.ASSET_NUMBER_KEY)} " result += f"edited in step {prev_action.step}." return result elif other_top >= instance_bottom and other_top <= instance_top: result = f"The asset placement conflicts with asset with asset number {prev_action.new_record.get(Constants.ASSET_NUMBER_KEY)} " result += f"edited in step {prev_action.step}." return result elif (instance.mount_type == Constants.BLADE_KEY and prev_action.new_record.get( Constants.MOUNT_TYPE_KEY) == Constants.BLADE_KEY): if instance.chassis_hostname == prev_action.new_record.get( Constants.CHASSIS_HOSTNAME_KEY ) and instance.chassis_slot == prev_action.new_record.get( Constants.CHASSIS_SLOT_KEY): return f"A blade is already being placed in chassis with hostname {instance.chassis_hostname} at position {instance.chassis_slot} in step {prev_action.step} of the change plan." # Power Connection Validation if (not datacenter.is_offline_storage) and (instance.mount_type != Constants.BLADE_KEY): if (prev_action.action != Constants.DECOMMISSION_KEY and prev_action.action != Constants.COLLATERAL_KEY): prev_pow_connections = prev_action.new_record.get( Constants.POWER_CONNECTIONS_KEY) print(prev_action.new_record) prev_pow_connections = set(prev_pow_connections) pow_intersection = pow_connections.intersection( prev_pow_connections) if len(pow_intersection) > 0: return f"There is already an asset connected at PDU port {pow_intersection.pop()}. Please pick an empty PDU port." if (prev_action.action == Constants.UPDATE_KEY or prev_action.action == Constants.DECOMMISSION_KEY): old_pow_connections = prev_action.old_record.get( Constants.POWER_CONNECTIONS_KEY) if old_pow_connections is None: self.no_pow_conflict = True else: old_pow_connections = set(old_pow_connections) old_pow_intersection = pow_connections.intersection( old_pow_connections) if len(old_pow_intersection) > 0: self.no_pow_conflict = True return Constants.API_SUCCESS def _common_validations(self, instance, cp_action, instance_bottom, instance_top, datacenter): if (self.instance_table.get_instance_by_asset_number( instance.asset_number) is not None and not instance.asset_number in self.decom_asset_set and instance.asset_number != cp_action.original_asset_number): return f"Asset numbers must be unique. An asset with asset number {instance.asset_number} already exists." if instance.hostname != "" and instance.hostname is not None: duplicate_hostname = self.instance_table.get_instance_by_hostname( instance.hostname) if (duplicate_hostname is not None and not duplicate_hostname.asset_number in self.cp_asset_set and duplicate_hostname.asset_number != instance.asset_number): return f"An asset with hostname {duplicate_hostname.hostname} exists at location {duplicate_hostname.rack_label} U{duplicate_hostname.rack_position}" if not datacenter.is_offline_storage: if instance.mount_type != Constants.BLADE_KEY: instance_list = self.instance_table.get_instances_by_rack( instance.rack_label, instance.datacenter_id) if instance_list is not None: for current_instance in instance_list: if (current_instance.asset_number in self.cp_asset_set or current_instance.asset_number == cp_action.original_asset_number): continue model = self.model_table.get_model(instance.model_id) current_instance_top = ( current_instance.rack_position + model.height - 1) if (current_instance.rack_position >= instance_bottom and current_instance.rack_position <= instance_top): return self.return_conflict(current_instance) elif (current_instance_top >= instance_bottom and current_instance_top <= instance_top): return self.return_conflict(current_instance) elif instance.mount_type == Constants.BLADE_KEY: blade_conflict = self.instance_table.get_blade_by_chassis_and_slot( instance.chassis_hostname, instance.chassis_slot) if (blade_conflict is not None and not blade_conflict.asset_number in self.cp_asset_set): return f"A blade with hostname {blade_conflict.hostname} is already located in position {instance.chassis_slot} of chassis with hostname {instance.chassis_hostname}." return Constants.API_SUCCESS def _validate_connections(self, network_connections, hostname, cp_action, all_cp_actions): result = "" new_connections = {} for my_port in network_connections: mac_adress = network_connections[my_port][ Constants.MAC_ADDRESS_KEY] connection_hostname = network_connections[my_port][ "connection_hostname"] connection_port = network_connections[my_port]["connection_port"] if connection_hostname in new_connections.keys(): if new_connections[connection_hostname] == connection_port: result += "Cannot make two network connections to the same port." elif connection_hostname != "" and connection_port != "": new_connections[connection_hostname] = connection_port mac_pattern = re.compile( "[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}" ) if mac_adress != "" and mac_pattern.fullmatch( mac_adress.lower()) is None: result += f"Invalid MAC address for port {my_port}. MAC addresses must be 6 byte, colon separated hexidecimal strings (i.e. a1:b2:c3:d4:e5:f6). \n" if (connection_hostname == "" or connection_hostname is None) and ( connection_port == "" or connection_port is None): continue if not (connection_hostname != "" and connection_port != ""): result += "Connections require both a hostname and connection port." other_instance = None for prev_action in all_cp_actions: if prev_action.step >= cp_action.step: continue if (prev_action.new_record[Constants.HOSTNAME_KEY] == connection_hostname): other_instance = self.instance_manager.make_instance( prev_action.new_record) if other_instance is None: other_instance = self.instance_table.get_instance_by_hostname( connection_hostname) if other_instance is None: result += f"The asset with hostname {connection_hostname} does not exist. Connections must be between assets with existing hostnames. \n" continue if connection_port in other_instance.network_connections: if (other_instance.network_connections[connection_port] ["connection_port"] != my_port) and ( other_instance.network_connections[connection_port] ["connection_port"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is connected to another asset. \n" continue if (other_instance.network_connections[connection_port] ["connection_hostname"] != hostname) and ( other_instance.network_connections[connection_port] ["connection_hostname"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is already connected to another asset." if result == "": return Constants.API_SUCCESS else: return result def _validate_connections_edit(self, network_connections, hostname, cp_action, all_cp_actions, prev_connections): result = "" new_connections = {} for my_port in network_connections: mac_adress = network_connections[my_port][ Constants.MAC_ADDRESS_KEY] connection_hostname = network_connections[my_port][ "connection_hostname"] connection_port = network_connections[my_port]["connection_port"] if connection_hostname in new_connections.keys(): if new_connections[connection_hostname] == connection_port: result += "Cannot make two network connections to the same port." elif connection_hostname != "" and connection_port != "": new_connections[connection_hostname] = connection_port mac_pattern = re.compile( "[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}" ) if mac_adress != "" and mac_pattern.fullmatch( mac_adress.lower()) is None: result += f"Invalid MAC address for port {my_port}. MAC addresses must be 6 byte, colon separated hexidecimal strings (i.e. a1:b2:c3:d4:e5:f6). \n" if (connection_hostname == "" or connection_hostname is None) and ( connection_port == "" or connection_port is None): continue if (connection_hostname == prev_connections[my_port]["connection_hostname"] and connection_port == prev_connections[my_port]["connection_port"]): continue if not (connection_hostname != "" and connection_port != ""): result += "Connections require both a hostname and connection port." other_instance = None for prev_action in all_cp_actions: if prev_action.step >= cp_action.step: continue if (prev_action.new_record[Constants.HOSTNAME_KEY] == connection_hostname): other_instance = self.instance_manager.make_instance( prev_action.new_record) if other_instance is None: other_instance = self.instance_table.get_instance_by_hostname( connection_hostname) if other_instance is None: result += f"The asset with hostname {connection_hostname} does not exist. Connections must be between assets with existing hostnames. \n" continue if connection_port in other_instance.network_connections: if (other_instance.network_connections[connection_port] ["connection_port"] != my_port) and ( other_instance.network_connections[connection_port] ["connection_port"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is connected to another asset. \n" continue if (other_instance.network_connections[connection_port] ["connection_hostname"] != hostname) and ( other_instance.network_connections[connection_port] ["connection_hostname"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is already connected to another asset." if result == "": return Constants.API_SUCCESS else: return result def return_conflict(self, current_instance): result = f"The asset placement conflicts with asset with asset number {current_instance.asset_number} " result += f"on rack {current_instance.rack_label} at height U{current_instance.rack_position}." return result
from app.dal.user_table import UserTable from app.data_models.user import User from app.exceptions.UserExceptions import ( InvalidDatacenterError, InvalidEmailError, InvalidPasswordError, InvalidPrivilegeError, InvalidUsernameError, NoEditsError, UserException, UsernameTakenError, ) from app.main.types import JSON from app.permissions.permissions_constants import PermissionConstants USER_TABLE = UserTable() DC_TABLE = DatacenterTable() class Validator: def __init__(self) -> None: self.EMAIL_USED_MSG = "Email already associated with another account" self.EMAIL_INVALID_MSG = "Email invalid" self.USERNAME_INVALID_MSG = "Username invalid" self.USERNAME_TAKEN_MSG = "Username already taken" self.PASSWORD_INVALID_MSG = "Password too weak" def validate_password(self, password: str) -> bool: """Ensures password adheres to security guidelines: - Should have at least one number. - Should have at least one uppercase and one lowercase character.
db.drop_all() db.create_all() db.session.commit() encrypted_password = AuthManager().encrypt_pw(password="******") datacenters = ["*"] priv: Permission = Permission( model=True, asset=True, datacenters=datacenters, power=True, audit=True, admin=True, ) user: User = User( username="******", display_name="Admin", email="*****@*****.**", password=encrypted_password, privilege=priv.make_json(), datacenters=datacenters, ) UserTable().add_user(user=user) model: Model = Model(vendor="dell", model_number="1234", mount_type="rackmount", height=3) ModelTable().add_model(model=model)
class UserManager: def __init__(self): self.AUTH_MANAGER = AuthManager() self.VALIDATOR = Validator() self.USER_TABLE = UserTable() self.BLACKLIST = [] with open(dirname + blacklist_file, "r") as infile: contents = json.load(infile) self.BLACKLIST = contents.get("blacklist") @staticmethod def __add_message_to_JSON(json, message) -> dict: json[Constants.MESSAGE_KEY] = message return json @staticmethod def __make_user_from_json(json) -> User: privilege = json.get(Constants.PRIVILEGE_KEY) # if json.get(Constants.USERNAME_KEY) is None: # raise TypeError("Username cannot be None. Requires type str.") # if json.get(Constants.DISPLAY_NAME_KEY) is None: # raise TypeError("Username cannot be None. Requires type str.") # if json.get(Constants.EMAIL_KEY) is None: # raise TypeError("Username cannot be None. Requires type str.") # if json.get(Constants.PASSWORD_KEY) is None: # raise TypeError("Username cannot be None. Requires type str.") # if json.get(Constants.PRIVILEGE_KEY) is None: # raise TypeError("Username cannot be None. Requires type dict.") return User( username=json.get(Constants.USERNAME_KEY), display_name=json.get(Constants.DISPLAY_NAME_KEY), email=json.get(Constants.EMAIL_KEY), password=json.get(Constants.PASSWORD_KEY), privilege=privilege, datacenters=privilege.get(PermissionConstants.DATACENTERS), ) @staticmethod def __match_oauth(request, response): usernames_match = request.get( Constants.USERNAME_KEY) == response.get("netid") display_names_match = request.get( Constants.DISPLAY_NAME_KEY) == response.get("displayName") emails_match = request.get(Constants.EMAIL_KEY) == response.get("mail") return usernames_match and display_names_match and emails_match def search(self, request): request_data = request.get_json() # print("request data: ", request_data) filters = request_data.get(Constants.FILTER_KEY) limit = filters.get(Constants.LIMIT_KEY) if limit is None: limit = 1000 print("FILTER") print(filters) # print(filters.get(Constants.PRIVILEGE_KEY).get(PermissionConstants.DATACENTERS)) users = self.USER_TABLE.search_users( username=filters.get(Constants.USERNAME_KEY), display_name=filters.get(Constants.DISPLAY_NAME_KEY), email=filters.get(Constants.EMAIL_KEY), privilege=filters.get(Constants.PRIVILEGE_KEY), datacenters=filters.get(Constants.PRIVILEGE_KEY).get( PermissionConstants.DATACENTERS), limit=limit, ) json_list = [user.make_json() for user in users] # json.dumps(json_list) return json_list def create_user(self, request): """Route for creating users Username Criteria: - Between 4 and 20 characters - Contains only alphanumeric characters and ".", "_" - No "." or "_" at the beginning - No doubles of special characters (".." or "__") - Not already taken by another user Email Criteria: - Valid email address compliant with RCF 5322 standard - Not already associated with another account Password Criteria: - Contains at least one number. - Contains at least one uppercase and one lowercase character. - Contains at least one special symbol. - Between 8 to 20 characters long. Returns: string: Success or failure, if failure provide message """ response = {} request_data = request.get_json() print(request_data) try: username = request_data.get(Constants.USERNAME_KEY) password = request_data.get(Constants.PASSWORD_KEY) email = request_data.get(Constants.EMAIL_KEY) display_name = request_data.get(Constants.DISPLAY_NAME_KEY) privilege = request_data.get(Constants.PRIVILEGE_KEY) datacenters = privilege.get(PermissionConstants.DATACENTERS) except: raise InvalidInputsError( "Incorrectly formatted message. Application error on the frontend" ) try: user = self.__make_user_from_json(request_data) self.VALIDATOR.validate_create_user(user) except UserException as e: raise UserException(e.message) except: raise UserException("Could not create user") # dcs = [] # for datacenter in datacenters: # dcs.append(f"{}") try: encrypted_password = self.AUTH_MANAGER.encrypt_pw(password) user = User( username=username, display_name=display_name, email=email, password=encrypted_password, privilege=privilege, datacenters=datacenters, ) print(user) self.USER_TABLE.add_user(user) except: raise UserException("Could not create user") return self.__add_message_to_JSON(response, "success") def delete(self, request): """Route for deleting users Returns: string: Success or failure, if failure provide message """ response = {} request_data = request.get_json() username = request_data.get(Constants.USERNAME_KEY) user = self.USER_TABLE.get_user(username) try: self.VALIDATOR.validate_delete_user(user) except UserException as e: return self.__add_message_to_JSON(response, e.message) except Exception as e: print(e) return self.__add_message_to_JSON( response, f"User '{username}' does not exist") self.USER_TABLE.delete_user(user) return self.__add_message_to_JSON(response, "success") def edit(self, request): response = {} request_data = request.get_json() username_original = request_data.get(Constants.ORIGINAL_USERNAME_KEY) request_data.get(Constants.USERNAME_KEY) request_data.get(Constants.EMAIL_KEY) request_data.get(Constants.DISPLAY_NAME_KEY) request_data.get(Constants.PRIVILEGE_KEY) old_user = None try: print("trying") user = self.__make_user_from_json(request_data) print("made new user") updated_user, old_user = self.VALIDATOR.validate_edit_user( user, username_original) print("validated new user") except UserException as e: raise UserException(e.message) # except Exception as e: # print("Could not edit user") # print(e) # raise UserException("Could not edit user") # # Update map of netid to username # if old_user.password.decode("utf-8") == Constants.NETID_PASSWORD: # netid = "" # with open(os.path.dirname(__file__) + "/netid_map.json", "r") as f: # map = json.load(f) # for key in map.keys(): # if map[key] == old_user.username: # netid = key # break # # with open(os.path.dirname(__file__) + "/netid_map.json", "w") as f: # map[netid] = updated_user.username # json.dump(map, f) self.USER_TABLE.delete_user(old_user) print("deleted old user") self.USER_TABLE.add_user(updated_user) print("added new user") if (old_user.privilege[PermissionConstants.ADMIN] == True and updated_user.privilege[PermissionConstants.ADMIN] != True): return self.__add_message_to_JSON( response, f"Success, Demotion to user privilege will take effect within the next {self.AUTH_MANAGER.TOKEN_EXP_DAYS} Days, {self.AUTH_MANAGER.TOKEN_EXP_HOURS} Hours, {self.AUTH_MANAGER.TOKEN_EXP_MINUTES} Minutes, and {self.AUTH_MANAGER.TOKEN_EXP_SECONDS} Seconds.", ) return self.__add_message_to_JSON(response, "success") def authenticate(self, request): # TESTED AND FUNCTIONAL """ Route for authenticating users """ answer = {} request_data = request.get_json() username = request_data.get(Constants.USERNAME_KEY) attempted_password = request_data.get(Constants.PASSWORD_KEY) user = self.USER_TABLE.get_user(username) if user is None: raise NonexistantUserError(f"User '{username}' does not exist") if user.password.decode("utf-8") == Constants.NETID_PASSWORD: raise UserException( "Please click 'SIGN IN WITH NETID' to log in as a NetID user") auth_success = self.AUTH_MANAGER.compare_pw(attempted_password, user.password) if not auth_success: raise IncorrectPasswordError("Incorrect password") answer[Constants.TOKEN_KEY] = self.AUTH_MANAGER.encode_auth_token( username) answer[Constants.PRIVILEGE_KEY] = user.privilege answer[Constants.PRIVILEGE_KEY][ PermissionConstants.DATACENTERS] = user.datacenters return self.__add_message_to_JSON(answer, Constants.API_SUCCESS) def logout(self, request): global dirname global blacklist_file response = {} token = request.headers.get(Constants.TOKEN_KEY) self.BLACKLIST.append(token) # print(self.BLACKLIST) with open(dirname + blacklist_file, "w") as outfile: json.dump({"blacklist": self.BLACKLIST}, outfile, indent=4) return self.__add_message_to_JSON(response, Constants.API_SUCCESS) def detail_view(self, request): response = {} request_data = request.get_json() username = request_data.get(Constants.USERNAME_KEY) if username is None: raise InvalidUsernameError("Please provide a username") user = self.USER_TABLE.get_user(username) if user is None: raise NonexistantUserError(f"User '{username}' does not exist") response["user"] = user.make_json() return response def oauth(self, request): response = {} request_data = request.json username = request_data.get(Constants.USERNAME_KEY) email = request_data.get(Constants.EMAIL_KEY) display_name = request_data.get(Constants.DISPLAY_NAME_KEY) privilege = { "model": False, "asset": False, "datacenters": [], "power": False, "audit": False, "admin": False, } datacenters = privilege["datacenters"] password = Constants.NETID_PASSWORD.encode("utf-8") client_id = request_data.get("client_id") token = request_data.get(Constants.TOKEN_KEY) headers = {"x-api-key": client_id, "Authorization": f"Bearer {token}"} duke_response = requests.get("https://api.colab.duke.edu/identity/v1/", headers=headers) data_matches = self.__match_oauth(request_data, duke_response.json()) if not data_matches: raise UserException(f"Cannot confirm NetID user {username}") user = User(username, display_name, email, password, privilege, datacenters) try: self.VALIDATOR.validate_shibboleth_login(user) except UserException as e: raise UserException(e.message) except Exception as e: print(str(e)) raise UserException("Could not authorize shibboleth login") existing_user = self.USER_TABLE.get_user(user.username) if existing_user is None: self.USER_TABLE.add_user(user) else: privilege = existing_user.privilege # TODO: FIgure out what to do when adding netID user overwrites existing user response[Constants.TOKEN_KEY] = self.AUTH_MANAGER.encode_auth_token( username) response[Constants.PRIVILEGE_KEY] = privilege response[Constants.MESSAGE_KEY] = Constants.API_SUCCESS response[Constants.USERNAME_KEY] = username # print("RESPONSE") # print(response) return response
from application import application, init # isort:skip from typing import List, Optional from app.dal.user_table import UserTable from app.data_models.user import User from app.users.authentication import AuthManager class InvalidInputException(Exception): """ Raised when the input given is invalid """ user_table = UserTable() def main(): print("User Management") print() while True: print("1 - Add user") print("2 - Get user") print("3 - Get all users") print("q - exit") print() user_input: str = input("Select operation: ") if user_input == "q":
class InstanceValidator: def __init__(self): self.instance_table = InstanceTable() self.model_table = ModelTable() self.rack_table = RackTable() self.user_table = UserTable() self.dc_table = DatacenterTable() self.rack_height = 42 def create_instance_validation(self, instance, queue=None): basic_val_result = self.basic_validations(instance, -1) if basic_val_result != Constants.API_SUCCESS: return basic_val_result model_template = self.model_table.get_model(instance.model_id) if model_template is None: return "The model does not exist." dc_template = self.dc_table.get_datacenter(instance.datacenter_id) if dc_template is None: return "The datacenter does not exist." # Check that connections are only within a single datacenter net_cons = instance.network_connections for port in net_cons.keys(): dest_hostname = net_cons[port][Constants.CONNECTION_HOSTNAME] if dest_hostname != "" and dest_hostname is not None: dc_id = (InstanceTable().get_instance_by_hostname( dest_hostname).datacenter_id) if dc_id != instance.datacenter_id: raise InvalidInputsError( "Network connections cannot span multiple datacenters") if dc_template.is_offline_storage: return Constants.API_SUCCESS if instance.mount_type == Constants.BLADE_KEY: return self.blade_validation(instance, -1, queue) else: return self.rackmount_validation(instance, -1, model_template, dc_template) def edit_instance_validation(self, instance, original_asset_number): basic_val_result = self.basic_validations(instance, original_asset_number) if basic_val_result != Constants.API_SUCCESS: return basic_val_result model_template = self.model_table.get_model(instance.model_id) if model_template is None: return "The model does not exist." dc_template = self.dc_table.get_datacenter(instance.datacenter_id) if dc_template is None: return "The datacenter does not exist." # Check that connections are only within a single datacenter net_cons = instance.network_connections for port in net_cons.keys(): dest_hostname = net_cons[port][Constants.CONNECTION_HOSTNAME] if dest_hostname != "" and dest_hostname is not None: dc_id = (InstanceTable().get_instance_by_hostname( dest_hostname).datacenter_id) if dc_id != instance.datacenter_id: raise InvalidInputsError( "Network connections cannot span multiple datacenters") if dc_template.is_offline_storage: return Constants.API_SUCCESS if instance.mount_type == Constants.BLADE_KEY: return self.blade_validation(instance, original_asset_number) else: return self.rackmount_validation(instance, original_asset_number, model_template, dc_template) return Constants.API_SUCCESS def basic_validations(self, instance, original_asset_number): if (instance.asset_number != original_asset_number and original_asset_number != -1): asset_pattern = re.compile("[0-9]{6}") if asset_pattern.fullmatch(str(instance.asset_number)) is None: return "Asset numbers must be 6 digits long and only contain numbers." if (self.instance_table.get_instance_by_asset_number( instance.asset_number) is not None): return f"Asset numbers must be unique. An asset with asset number {instance.asset_number} already exists." if instance.hostname != "" and instance.hostname is not None: duplicate_hostname = self.instance_table.get_instance_by_hostname( instance.hostname) if duplicate_hostname is not None: if duplicate_hostname.asset_number != original_asset_number: print("DUPLICATE NUMBER", duplicate_hostname.asset_number) print("ORIGINAL NUMBER", original_asset_number) return f"An asset with hostname {duplicate_hostname.hostname} already exists. Please provide a unique hostname." if len(instance.hostname) > 64: return "Hostnames must be 64 characters or less" host_pattern = re.compile("[a-zA-Z]*[A-Za-z0-9-]*[A-Za-z0-9]") if host_pattern.fullmatch(instance.hostname) is None: return "Hostnames must start with a letter, only contain letters, numbers, periods, and hyphens, and end with a letter or number." if instance.owner != "" and self.user_table.get_user( instance.owner) is None: return f"The owner {instance.owner} is not an existing user. Please enter the username of an existing user." return Constants.API_SUCCESS def rackmount_validation(self, instance, original_asset_number, model_template, dc_template): if instance.mount_type == Constants.CHASIS_KEY and instance.hostname == "": if (self.instance_table.get_blades_by_chassis_hostname( instance.hostname) is not None): return "Blade chassis with blades require a hostname." rack = self.rack_table.get_rack(instance.rack_label, instance.datacenter_id) if rack is None and not dc_template.is_offline_storage: return f"Rack {instance.rack_label} does not exist in datacenter {dc_template}. Assets must be created on preexisting racks" p_connection_set = set() for p_connection in instance.power_connections: if p_connection in p_connection_set: return "Cannot connect two asset power ports to the same PDU port." else: p_connection_set.add(p_connection) char1 = p_connection[0].upper() print("PRINGINDISNFISNF") print(p_connection) num = int(p_connection[1:]) if char1 == "L": pdu_arr = rack.pdu_left elif char1 == "R": pdu_arr = rack.pdu_right else: return "Invalid power connection. Please specify left or right PDU." if pdu_arr[num - 1] == 1: return f"There is already an asset connected at PDU {char1}{num}. Please pick an empty PDU port." pattern = re.compile("[0-9]+") if pattern.fullmatch(str(instance.rack_position)) is None: return "The value for Rack U must be a positive integer." instance_bottom = int(instance.rack_position) instance_top = instance_bottom + int(model_template.height) - 1 if instance_top > self.rack_height: return "The placement of the instance exceeds the height of the rack." instance_list = self.instance_table.get_instances_by_rack( instance.rack_label, instance.datacenter_id) if instance_list is not None: for current_instance in instance_list: if current_instance.asset_number == original_asset_number: continue model = self.model_table.get_model(current_instance.model_id) current_instance_top = current_instance.rack_position + model.height - 1 if (current_instance.rack_position >= instance_bottom and current_instance.rack_position <= instance_top): return self.return_conflict(current_instance) elif (current_instance_top >= instance_bottom and current_instance_top <= instance_top): return self.return_conflict(current_instance) connection_validation_result = self.validate_connections( instance.network_connections, instance.hostname) if connection_validation_result != Constants.API_SUCCESS: return connection_validation_result return Constants.API_SUCCESS def blade_validation(self, instance, original_asset_number, queue=None): blade_chassis = self.instance_table.get_instance_by_hostname( instance.chassis_hostname) print("\n\n") print("CHECKING THE QUEUE") # In the case of import instance, need to check queue of instances as well as db if queue is not None and blade_chassis is None: for item in queue: print(item) if item.hostname == instance.chassis_hostname: blade_chassis = item print("BLADE CHASSIS") print(blade_chassis) if blade_chassis is None: return f"No blade chassis exists with hostname {instance.chassis_hostname}." elif blade_chassis.mount_type != Constants.CHASIS_KEY: return f"Asset with hostname {instance.chassis_hostname} is not a blade chassis. Blades can only be installed in a blade chassis." if blade_chassis.datacenter_id != instance.datacenter_id: return f"The blade chassis selected is in datacenter {blade_chassis.datacenter_id}. A blade and its chassis must be in the same datacenter." if instance.chassis_slot > 14 or instance.chassis_slot < 1: return f"A blade chassis contains 14 slots. Please provide a position between 1 and 14." blade_conflict = self.instance_table.get_blade_by_chassis_and_slot( instance.chassis_hostname, instance.chassis_slot) if (blade_conflict is not None and blade_conflict.asset_number != original_asset_number): return f"Blade with asset number {blade_conflict.asset_number} already exists at slot {instance.chassis_slot} of chassis with hostname {instance.chassis_hostname}." return Constants.API_SUCCESS def validate_connections(self, network_connections, hostname): # print("validating connections") result = "" new_connections = {} for my_port in network_connections: mac_adress = network_connections[my_port][ Constants.MAC_ADDRESS_KEY] connection_hostname = network_connections[my_port][ "connection_hostname"] connection_port = network_connections[my_port]["connection_port"] if connection_hostname in new_connections.keys(): if new_connections[connection_hostname] == connection_port: result += "Cannot make two network connections to the same port." elif connection_hostname != "" and connection_port != "": new_connections[connection_hostname] = connection_port mac_pattern = re.compile( "[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}:[a-f0-9]{2}" ) if mac_adress != "" and mac_pattern.fullmatch( mac_adress.lower()) is None: result += f"Invalid MAC address for port {my_port}. MAC addresses must be 6 byte, colon separated hexidecimal strings (i.e. a1:b2:c3:d4:e5:f6). \n" if (connection_hostname == "" or connection_hostname is None) and ( connection_port == "" or connection_port is None): continue if not (connection_hostname != "" and connection_port != ""): result += "Connections require both a hostname and connection port." other_instance = self.instance_table.get_instance_by_hostname( connection_hostname) if other_instance is None: result += f"The asset with hostname {connection_hostname} does not exist. Connections must be between assets with existing hostnames. \n" continue if connection_port in other_instance.network_connections: if (other_instance.network_connections[connection_port] ["connection_port"] != my_port) and ( other_instance.network_connections[connection_port] ["connection_port"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is connected to another asset. \n" continue if (other_instance.network_connections[connection_port] ["connection_hostname"] != hostname) and ( other_instance.network_connections[connection_port] ["connection_hostname"] != ""): result += f"The port {connection_port} on asset with hostname {connection_hostname} is already connected to another asset. \n" # print("finished connection validation") if result == "": return Constants.API_SUCCESS else: return result def return_conflict(self, current_instance): result = f"The asset placement conflicts with asset with asset number {current_instance.asset_number} " result += f"on rack {current_instance.rack_label} at height U{current_instance.rack_position}." return result
model=True, asset=True, datacenters=datacenters, power=True, audit=True, admin=True, ) user: User = User( username="******", display_name="Admin", email="*****@*****.**", password=encrypted_password, privilege=priv.make_json(), datacenters=datacenters, ) UserTable().add_user(user=user) # Basic user encrypted_password = AuthManager().encrypt_pw(password="******") datacenters = [] basic_priv: Permission = Permission( model=False, asset=False, datacenters=datacenters, power=False, audit=False, admin=False, ) basic_user: User = User( username="******", display_name="Basic",