def process_area_access_record_with_parents(user: User): show_not_qualified_areas = get_customization( 'dashboard_display_not_qualified_areas') records = AreaAccessRecord.objects.filter(end=None, staff_charge=None) if not user.is_staff and show_not_qualified_areas != 'enabled': records = records.filter(area__in=user.accessible_areas()) records = records.prefetch_related('customer', 'project', 'area') no_occupants = not records.exists() area_items = None area_model_tree = get_area_model_tree() if not no_occupants: areas_and_parents = area_model_tree.get_ancestor_areas( area_model_tree.get_areas([record.area.id for record in records]), include_self=True) # Sort to have area without children before others areas_and_parents.sort(key=lambda x: f'{x.tree_category}zz' if x.is_leaf else f'{x.tree_category}/aa') area_summary = create_area_summary(area_model_tree=area_model_tree, add_resources=False, add_outages=False, add_occupants=True) area_summary_dict = {area['id']: area for area in area_summary} for area_item in areas_and_parents: area_item.item = area_summary_dict[area_item.id] area_items = area_tree_helper(areas_and_parents, records) return area_items, no_occupants
def check_policy_to_enter_this_area(area: Area, user: User): # If explicitly set on the Physical Access Level, staff may be exempt from being granted explicit access if user.is_staff and any([access_level.accessible() for access_level in PhysicalAccessLevel.objects.filter(allow_staff_access=True, area=area)]): pass else: # Check if the user normally has access to this area door at the current time (or access to any parent) if not any([access_level.accessible() for access_level in user.accessible_access_levels_for_area(area)]): first_access_exception = next(iter([access_level.ongoing_exception() for access_level in user.accessible_access_levels_for_area(area)]), None) raise NoAccessiblePhysicalAccessUserError(user=user, area=area, access_exception=first_access_exception) if not user.is_staff and not user.is_service_personnel: for a in area.get_ancestors(ascending=True, include_self=True): unavailable_resources = a.required_resources.filter(available=False) if unavailable_resources: raise UnavailableResourcesUserError(user=user, area=a, resources=unavailable_resources) # Non staff users may not enter an area during a scheduled outage. if area.scheduled_outage_in_progress(): raise ScheduledOutageInProgressError(user=user, area=area) # If we reached maximum capacity, fail (only for non staff users) for a in area.get_ancestors(ascending=True, include_self=True): if a.maximum_capacity and 0 < a.maximum_capacity <= a.occupancy_count(): raise MaximumCapacityReachedError(user=user, area=a) if area.requires_reservation and not area.get_current_reservation_for_user(user): raise ReservationRequiredUserError(user=user, area=area)
def check_policy_to_enter_any_area(user: User): """ Checks the area access policy for a user. """ if not user.is_active: raise InactiveUserError(user=user) if user.active_project_count() < 1: raise NoActiveProjectsForUserError(user=user) if user.access_expiration is not None and user.access_expiration < date.today(): raise PhysicalAccessExpiredUserError(user=user) user_has_access_to_at_least_one_area = user.accessible_access_levels().exists() if not user_has_access_to_at_least_one_area: raise NoPhysicalAccessUserError(user=user)
def log_out_user(user: User): record = user.area_access_record() # Allow the user to log out of any area, even if this is a logout tablet for a different area. if record: record.end = timezone.now() record.save() # Shorten the user's area reservation since the user is now leaving shorten_reservation(user, record.area) # Stop charging area access if staff is leaving the area staff_charge = user.get_staff_charge() if staff_charge: try: staff_area_access = AreaAccessRecord.objects.get(staff_charge=staff_charge, end=None) staff_area_access.end = timezone.now() staff_area_access.save() except AreaAccessRecord.DoesNotExist: pass
def load_areas_for_use_in_template(user: User): """ This method returns accessible areas for the user and a queryset ready to be used in template view. The template view needs to use the {% recursetree %} tag from mptt """ accessible_areas = user.accessible_areas() areas = list(set([ancestor for area in accessible_areas for ancestor in area.get_ancestors(include_self=True)])) areas.sort(key=lambda x: x.tree_category()) areas = Area.objects.filter(id__in=[area.id for area in areas]) return accessible_areas, areas
def check_policy_to_enter_any_area(user: User): """ Checks the area access policy for a user. """ if not user.is_active: raise InactiveUserError(user=user) if user.active_project_count() < 1: raise NoActiveProjectsForUserError(user=user) if user.access_expiration is not None and user.access_expiration < date.today(): raise PhysicalAccessExpiredUserError(user=user) user_has_access_to_at_least_one_area = user.physical_access_levels.all().exists() staff_has_access_to_at_least_one_area = user.is_staff and PhysicalAccessLevel.objects.filter(allow_staff_access=True).exists() if not (user_has_access_to_at_least_one_area or staff_has_access_to_at_least_one_area): raise NoPhysicalAccessUserError(user=user)
def check_user_reply_error(buddy_request: BuddyRequest, user: User) -> Optional[str]: error_message = None try: check_policy_to_enter_any_area(user) except InactiveUserError: error_message = "You cannot reply to this request because your account has been deactivated" except NoActiveProjectsForUserError: error_message = "You cannot reply to this request because you don't have any active projects" except PhysicalAccessExpiredUserError: error_message = "You cannot reply to this request because your facility access has expired" except NoPhysicalAccessUserError: error_message = "You cannot reply to this request because you do not have access to any areas" else: if buddy_request.area not in user.accessible_areas(): error_message = ( f"You cannot reply to this request because you do not have access to the {buddy_request.area.name}" ) return error_message
def check_billing_to_project(project: Project, user: User, item: Union[Tool, Area, Consumable, StaffCharge] = None): if project: if project not in user.active_projects(): raise NotAllowedToChargeProjectException(project=project, user=user) if item: # Check if project only allows billing for certain tools allowed_tools = project.only_allow_tools.all() if allowed_tools.exists(): if isinstance(item, Tool) and item not in allowed_tools: msg = f"{item.name} is not allowed for project {project.name}" raise ItemNotAllowedForProjectException(project, user, item.name, msg) elif isinstance(item, Area) and item.id not in distinct_qs_value_list( allowed_tools, '_requires_area_access_id'): msg = f"{item.name} is not allowed for project {project.name}" raise ItemNotAllowedForProjectException(project, user, item.name, msg) # Check if consumable withdrawals are allowed if isinstance(item, Consumable): if not project.allow_consumable_withdrawals: msg = f"Consumable withdrawals are not allowed for project {project.name}" raise ItemNotAllowedForProjectException(project, user, "Staff Charges", msg)
def check_policy_to_enable_tool(tool: Tool, operator: User, user: User, project: Project, staff_charge: bool): """ Check that the user is allowed to enable the tool. Enable the tool if the policy checks pass. """ facility_name = get_customization('facility_name') # The tool must be visible (or the parent if it's a child tool) to users. visible = tool.parent_tool.visible if tool.is_child_tool( ) else tool.visible if not visible: return HttpResponseBadRequest( "This tool is currently hidden from users.") # The tool must be operational. # If the tool is non-operational then it may only be accessed by staff members. if not tool.operational and not operator.is_staff: return HttpResponseBadRequest( "This tool is currently non-operational.") # The tool must not be in use. current_usage_event = tool.get_current_usage_event() if current_usage_event: return HttpResponseBadRequest("The tool is currently being used by " + str(current_usage_event.user) + ".") # The user must be qualified to use the tool itself, or the parent tool in case of alternate tool. tool_to_check_qualifications = tool.parent_tool if tool.is_child_tool( ) else tool if tool_to_check_qualifications not in operator.qualifications.all( ) and not operator.is_staff: return HttpResponseBadRequest( "You are not qualified to use this tool.") # Only staff members can operate a tool on behalf of another user. if (user and operator.pk != user.pk) and not operator.is_staff: return HttpResponseBadRequest( "You must be a staff member to use a tool on another user's behalf." ) # All required resources must be available to operate a tool except for staff. if tool.required_resource_set.filter( available=False).exists() and not operator.is_staff: return HttpResponseBadRequest( "A resource that is required to operate this tool is unavailable.") # The tool operator may not activate tools in a particular area unless they are logged in to the area. # Staff are exempt from this rule. if tool.requires_area_access and AreaAccessRecord.objects.filter( area=tool.requires_area_access, customer=operator, staff_charge=None, end=None).count() == 0 and not operator.is_staff: dictionary = {'operator': operator, 'tool': tool, 'type': 'access'} abuse_email_address = get_customization('abuse_email_address') message = get_media_file_contents( 'unauthorized_tool_access_email.html') if abuse_email_address and message: rendered_message = Template(message).render(Context(dictionary)) send_mail("Area access requirement", rendered_message, abuse_email_address, [abuse_email_address]) return HttpResponseBadRequest( "You must be logged in to the {} to operate this tool.".format( tool.requires_area_access.name)) # The tool operator may not activate tools in a particular area unless they are still within that area reservation window if not operator.is_staff and tool.requires_area_reservation(): if not tool.requires_area_access.get_current_reservation_for_user( operator): dictionary = { 'operator': operator, 'tool': tool, 'type': 'reservation', } abuse_email_address = get_customization('abuse_email_address') message = get_media_file_contents( 'unauthorized_tool_access_email.html') if abuse_email_address and message: rendered_message = Template(message).render( Context(dictionary)) send_mail("Area reservation requirement", rendered_message, abuse_email_address, [abuse_email_address]) return HttpResponseBadRequest( "You must have a current reservation for the {} to operate this tool." .format(tool.requires_area_access.name)) # Staff may only charge staff time for one user at a time. if staff_charge and operator.charging_staff_time(): return HttpResponseBadRequest( 'You are already charging staff time. You must end the current staff charge before you being another.' ) # Staff may not bill staff time to the themselves. if staff_charge and operator == user: return HttpResponseBadRequest( 'You cannot charge staff time to yourself.') # Users may only charge to projects they are members of. if project not in user.active_projects(): return HttpResponseBadRequest( 'The designated user is not assigned to the selected project.') # The tool operator must not have a lock on usage if operator.training_required: return HttpResponseBadRequest( f"You are blocked from using all tools in the {facility_name}. Please complete the {facility_name} rules tutorial in order to use tools." ) # Users may only use a tool when delayed logoff is not in effect. Staff are exempt from this rule. if tool.delayed_logoff_in_progress() and not operator.is_staff: return HttpResponseBadRequest( "Delayed tool logoff is in effect. You must wait for the delayed logoff to expire before you can use the tool." ) # Users may not enable a tool during a scheduled outage. Staff are exempt from this rule. if tool.scheduled_outage_in_progress() and not operator.is_staff: return HttpResponseBadRequest( "A scheduled outage is in effect. You must wait for the outage to end before you can use the tool." ) return HttpResponse()
def check_policy_to_enable_tool(tool: Tool, operator: User, user: User, project: Project, staff_charge: bool): """ Check that the user is allowed to enable the tool. Enable the tool if the policy checks pass. """ facility_name = get_customization('facility_name') # The tool must be visible (or the parent if it's a child tool) to users. visible = tool.parent_tool.visible if tool.is_child_tool() else tool.visible if not visible: return HttpResponseBadRequest("This tool is currently hidden from users.") # The tool must be operational. # If the tool is non-operational then it may only be accessed by staff members or service personnel. if not tool.operational and not operator.is_staff and not operator.is_service_personnel: return HttpResponseBadRequest("This tool is currently non-operational.") # The tool must not be in use. current_usage_event = tool.get_current_usage_event() if current_usage_event: return HttpResponseBadRequest("The tool is currently being used by " + str(current_usage_event.user) + ".") # The user must be qualified to use the tool itself, or the parent tool in case of alternate tool. tool_to_check_qualifications = tool.parent_tool if tool.is_child_tool() else tool if tool_to_check_qualifications not in operator.qualifications.all() and not operator.is_staff: return HttpResponseBadRequest("You are not qualified to use this tool.") # Only staff members can operate a tool on behalf of another user. if (user and operator.pk != user.pk) and not operator.is_staff: return HttpResponseBadRequest("You must be a staff member to use a tool on another user's behalf.") # All required resources must be available to operate a tool except for staff or service personnel. if tool.required_resource_set.filter(available=False).exists() and not operator.is_staff and not operator.is_service_personnel: return HttpResponseBadRequest("A resource that is required to operate this tool is unavailable.") # The tool operator may not activate tools in a particular area unless they are logged in to the area. # Staff are exempt from this rule. if tool.requires_area_access and AreaAccessRecord.objects.filter(area=tool.requires_area_access, customer=operator, staff_charge=None, end=None).count() == 0 and not operator.is_staff: abuse_email_address = get_customization('abuse_email_address') message = get_media_file_contents('unauthorized_tool_access_email.html') if abuse_email_address and message: dictionary = { 'operator': operator, 'tool': tool, 'type': 'access' } rendered_message = Template(message).render(Context(dictionary)) send_mail(subject="Area access requirement", content=rendered_message, from_email=abuse_email_address, to=[abuse_email_address], email_category=EmailCategory.ABUSE) return HttpResponseBadRequest("You must be logged in to the {} to operate this tool.".format(tool.requires_area_access.name)) # The tool operator may not activate tools in a particular area unless they are still within that area reservation window # Staff and service personnel are exempt from this rule. if not operator.is_staff and not operator.is_service_personnel and tool.requires_area_reservation(): if not tool.requires_area_access.get_current_reservation_for_user(operator): abuse_email_address = get_customization('abuse_email_address') message = get_media_file_contents('unauthorized_tool_access_email.html') if abuse_email_address and message: dictionary = { 'operator': operator, 'tool': tool, 'type': 'reservation', } rendered_message = Template(message).render(Context(dictionary)) send_mail(subject="Area reservation requirement", content=rendered_message, from_email=abuse_email_address, to=[abuse_email_address], email_category=EmailCategory.ABUSE) return HttpResponseBadRequest("You must have a current reservation for the {} to operate this tool.".format(tool.requires_area_access.name)) # Staff may only charge staff time for one user at a time. if staff_charge and operator.charging_staff_time(): return HttpResponseBadRequest('You are already charging staff time. You must end the current staff charge before you being another.') # Staff may not bill staff time to themselves. if staff_charge and operator == user: return HttpResponseBadRequest('You cannot charge staff time to yourself.') # Users may only charge to projects they are members of. if project not in user.active_projects(): return HttpResponseBadRequest('The designated user is not assigned to the selected project.') # The tool operator must not have a lock on usage if operator.training_required: return HttpResponseBadRequest(f"You are blocked from using all tools in the {facility_name}. Please complete the {facility_name} rules tutorial in order to use tools.") # Users may only use a tool when delayed logoff is not in effect. Staff and service personnel are exempt from this rule. if tool.delayed_logoff_in_progress() and not operator.is_staff and not operator.is_service_personnel: return HttpResponseBadRequest("Delayed tool logoff is in effect. You must wait for the delayed logoff to expire before you can use the tool.") # Users may not enable a tool during a scheduled outage. Staff and service personnel are exempt from this rule. if tool.scheduled_outage_in_progress() and not operator.is_staff and not operator.is_service_personnel: return HttpResponseBadRequest("A scheduled outage is in effect. You must wait for the outage to end before you can use the tool.") #Refuses all tool logins if user is logged in using an excluded project (i.e. one reserved for buddy system or observation) if not operator.is_staff: projects_to_exclude = [] exclude=get_customization('exclude_from_usage') if exclude: projects_to_exclude = [int(s) for s in exclude.split() if s.isdigit()] try: if tool.requires_area_access: current_access = AreaAccessRecord.objects.filter(area=tool.requires_area_access, customer=operator, staff_charge=None, end=None) if current_access[0].project.id in projects_to_exclude: return HttpResponseBadRequest("You may not use tools while logged in with this project.") except: return HttpResponseBadRequest("There was a problem enabling this tool. Please see staff.") if tool.reservation_required and not operator.is_staff: td=timedelta(minutes=15) if not Reservation.objects.filter(start__lt=timezone.now()+td, end__gt=timezone.now(), cancelled=False, missed=False, shortened=False, user=operator, tool=tool).exists(): return HttpResponseBadRequest("A reservation is required to enable this tool.") return HttpResponse()