Example #1
0
def send_new_task_emails(request, task):
    message = get_media_file_contents('new_task_email.html')
    if message:
        dictionary = {
            'template_color':
            bootstrap_primary_color('danger')
            if task.force_shutdown else bootstrap_primary_color('warning'),
            'user':
            request.user,
            'task':
            task,
            'tool':
            task.tool,
            'tool_control_absolute_url':
            request.build_absolute_uri(task.tool.get_absolute_url())
        }
        # Send an email to the appropriate NanoFab staff that a new task has been created:
        subject = ('SAFETY HAZARD: ' if task.safety_hazard else
                   '') + task.tool.name + (' shutdown' if task.force_shutdown
                                           else ' problem')
        message = Template(message).render(Context(dictionary))
        recipients = tuple([
            r for r in [
                task.tool.primary_owner.email, task.tool.secondary_owner.email,
                task.tool.notification_email_address
            ] if r
        ])
        send_mail(subject,
                  '',
                  request.user.email,
                  recipients,
                  html_message=message)

    # Send an email to any user (excluding staff) with a future reservation on the tool:
    user_office_email = get_customization('user_office_email_address')
    message = get_media_file_contents('new_task_email.html')
    if user_office_email and message:
        upcoming_reservations = Reservation.objects.filter(
            start__gt=timezone.now(),
            cancelled=False,
            tool=task.tool,
            user__is_staff=False)
        for reservation in upcoming_reservations:
            if not task.tool.operational:
                subject = reservation.tool.name + " reservation problem"
                rendered_message = Template(message).render(
                    Context({
                        'reservation': reservation,
                        'template_color': bootstrap_primary_color('danger'),
                        'fatal_error': True
                    }))
            else:
                subject = reservation.tool.name + " reservation warning"
                rendered_message = Template(message).render(
                    Context({
                        'reservation':
                        reservation,
                        'template_color':
                        bootstrap_primary_color('warning'),
                        'fatal_error':
                        False
                    }))
            reservation.user.email_user(subject, rendered_message,
                                        user_office_email)
Example #2
0
def check_policy_to_enable_tool(tool, operator, user, project, staff_charge):
	"""
	Check that the user is allowed to enable the tool. Enable the tool if the policy checks pass.
	"""

	# 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,
		}
		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.lower()))

	# 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("You are blocked from using all tools in the NanoFab. Please complete the NanoFab 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()
Example #3
0
    def render(self, name, value, attrs=None, renderer=None):
        """
		This widget takes a list of items (tools/areas) and creates nested unordered lists in a hierarchical manner.
		The parameters name and attrs are not used.
		'value' is a dictionary which must contain a 'tools' or 'areas' key with a value that is a QuerySet of all tools/areas to be put in the list.
		A collection of unordered HTML lists is returned with various callbacks and properties attached to each nested element.

		For a more concrete example, suppose the following tools are input to the item tree:
		Packaging/Dicing Saw
		Chemical Vapor Deposition/PECVD
		Gen Furnaces/Sinter

		The following unordered HTML list would be produced:
		<ul>
			<li>
				<a href="javascript:void(0);" onclick="on_item_tree_click($(this.nextSibling))" class="node">Packaging</a>
				<ul class="collapsed">
					<li><a href="javascript:void(0);" onclick="on_item_tree_click($(this))" class="leaf node">Dicing saw</a></li>
				</ul>
			</li>
			<li>
				<a href="javascript:void(0);" onclick="on_item_tree_click($(this.nextSibling))" class="node">Chemical Vapor Deposition</a>
				<ul class="collapsed">
					<li><a href="javascript:void(0);" onclick="on_item_tree_click($(this))" class="leaf node">PECVD</a></li>
				</ul>
			</li>
			<li>
				<a href="javascript:void(0);" onclick="on_item_tree_click($(this.nextSibling))" class="node">Gen Furnaces</a>
				<ul class="collapsed">
					<li><a href="javascript:void(0);" onclick="on_item_tree_click($(this))" class="leaf node">Sinter</a></li>
				</ul>
			</li>
		</ul>
		"""
        area_tree = ItemTreeHelper(None, ReservationItemType.AREA)
        tool_tree = ItemTreeHelper(None, ReservationItemType.TOOL)
        user: User = value['user'] if 'user' in value else None
        model_tree = get_area_model_tree()
        area_tree_items: List[TreeItem] = model_tree.get_areas(
            [area.id for area in value.get('areas', [])])
        tools: List[Tool] = value.get('tools', [])
        tool_parent_ids = Tool.objects.filter(
            parent_tool__isnull=False).values_list('parent_tool_id', flat=True)
        user_accessible_areas = [] if not user or not area_tree_items else user.accessible_areas(
        )
        user_qualified_tool_ids = [] if not user or not tools else user.qualifications.all(
        ).values_list('id', flat=True)
        parent_areas_dict = {}
        if area_tree_items:
            # Create a lookup of area name to area with all the parents (in order to display info about category-parents)
            parent_areas_dict = {
                area_tree_item.name: area_tree_item
                for area_tree_item in model_tree.get_ancestor_areas(
                    area_tree_items)
            }
            # Sort areas by complete category
            area_tree_items = list(area_tree_items)
            area_tree_items.sort(key=lambda area: area.tree_category)

        display_all_areas = get_customization(
            'calendar_display_not_qualified_areas') == 'enabled'
        for area in area_tree_items:
            category = area.tree_category + '/' if area.tree_category else ''
            is_qualified = True if not display_all_areas else (
                user and user.is_staff) or (
                    user and area.item in user_accessible_areas)
            area_tree.add(ReservationItemType.AREA, category + area.name,
                          area.id, is_qualified)
        for tool in tools:
            is_qualified = (user and user.is_staff) or (
                user and tool.id in user_qualified_tool_ids)
            tool_tree.add(
                ReservationItemType.TOOL, tool.category + '/' +
                tool.name_or_child_in_use_name(parent_ids=tool_parent_ids),
                tool.id, is_qualified)

        legend = True if area_tree_items and tools else False
        result = ""
        if area_tree_items:
            result += area_tree.render(legend=legend,
                                       category_items_lookup=parent_areas_dict)
        if tools:
            result += tool_tree.render(legend=legend)
        return mark_safe(result)
Example #4
0
def self_log_in(request, load_areas=True):
    user: User = request.user
    if not able_to_self_log_in_to_area(user):
        return redirect(reverse('landing'))

    dictionary = {
        'projects': user.active_projects(),
    }
    if request.GET.get('area_id'):
        dictionary['area_id'] = quiet_int(request.GET['area_id'])

    facility_name = get_customization('facility_name')
    try:
        check_policy_to_enter_any_area(user)
    except InactiveUserError:
        dictionary[
            'error_message'] = f'Your account has been deactivated. Please visit the {facility_name} staff to resolve the problem.'
        return render(request, 'area_access/self_login.html', dictionary)
    except NoActiveProjectsForUserError:
        dictionary[
            'error_message'] = f"You are not a member of any active projects. You won't be able to use any interlocked {facility_name} tools. Please visit the {facility_name} user office for more information."
        return render(request, 'area_access/self_login.html', dictionary)
    except PhysicalAccessExpiredUserError:
        dictionary[
            'error_message'] = f"Your physical access to the {facility_name} has expired. Have you completed your safety training within the last year? Please visit the User Office to renew your access."
        return render(request, 'area_access/self_login.html', dictionary)
    except NoPhysicalAccessUserError:
        dictionary[
            'error_message'] = f"You have not been granted physical access to any {facility_name} area. Please visit the User Office if you believe this is an error."
        return render(request, 'area_access/self_login.html', dictionary)

    if load_areas:
        dictionary['user_accessible_areas'], dictionary[
            'areas'] = load_areas_for_use_in_template(user)
    else:
        dictionary['user_accessible_areas'] = []
        dictionary['areas'] = []

    if request.method == 'GET':
        return render(request, 'area_access/self_login.html', dictionary)
    if request.method == 'POST':
        try:
            a = Area.objects.get(id=request.POST['area'])
            p = Project.objects.get(id=request.POST['project'])
            check_policy_to_enter_this_area(a, request.user)
            if p in dictionary['projects']:
                AreaAccessRecord.objects.create(area=a,
                                                customer=request.user,
                                                project=p)
        except NoAccessiblePhysicalAccessUserError as error:
            if error.access_exception:
                dictionary[
                    'area_error_message'] = f"You do not have access to the {error.area.name} at this time due to the following exception: {error.access_exception.name}. The exception ends on {localize(error.access_exception.end_time.astimezone(timezone.get_current_timezone()))}"
            else:
                dictionary[
                    'area_error_message'] = f"You do not have access to the {error.area.name} at this time. Please visit the User Office if you believe this is an error."
            return render(request, 'area_access/self_login.html', dictionary)
        except UnavailableResourcesUserError as error:
            dictionary[
                'area_error_message'] = f'The {error.area.name} is inaccessible because a required resource is unavailable ({error.resources[0]}).'
            return render(request, 'area_access/self_login.html', dictionary)
        except ScheduledOutageInProgressError as error:
            dictionary[
                'area_error_message'] = f'The {error.area.name} is inaccessible because a scheduled outage is in progress.'
            return render(request, 'area_access/self_login.html', dictionary)
        except MaximumCapacityReachedError as error:
            dictionary[
                'area_error_message'] = f'The {error.area.name} is inaccessible because it has reached its maximum capacity. Wait for somebody to exit and try again.'
            return render(request, 'area_access/self_login.html', dictionary)
        except ReservationRequiredUserError as error:
            dictionary[
                'area_error_message'] = f'You do not have a current reservation for the {error.area.name}. Please make a reservation before trying to access this area.'
            return render(request, 'area_access/self_login.html', dictionary)
        except Exception as error:
            area_access_logger.exception(error)
            dictionary['area_error_message'] = "unexpected error"
            return render(request, 'area_access/self_login.html', dictionary)
        return redirect(reverse('landing'))
Example #5
0
def check_policy_to_enable_tool(tool, operator, user, project, staff_charge):
    """
	Check that the user is allowed to enable the tool. Enable the tool if the policy checks pass.
	"""

    # The tool must be visible to users.
    if not tool.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.
    if tool 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,
        }
        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",
                      '',
                      abuse_email_address, [abuse_email_address],
                      html_message=rendered_message)
        return HttpResponseBadRequest(
            "You must be logged in to the {} to operate this tool.".format(
                tool.requires_area_access.name.lower()))

    # 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 begin 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(
            "You are blocked from using all tools in the facility. Please complete the 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."
        )

    #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.")

    #Refuses login on tools that require reservations if there is no reservation
    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.")
        # try:
        # 	current_reservation = Reservation.objects.filter(start__lt=timezone.now()+td, start__gt=timezone.now(), end__gt=timezone.now(), cancelled=False, missed=False, shortened=False, user=operator, tool=tool)
        # 	# Resize the user's reservation to the current time. This is necessary because otherwise, if they log in and log out before the official start of their reservation, it will not be shortened
        # 	new_reservation = deepcopy(current_reservation)
        # 	new_reservation.id = None
        # 	new_reservation.pk = None
        # 	now=timezone.now()
        # 	new_reservation.start=now
        # 	new_reservation.save()
        # 	current_reservation.cancelled = True
        # 	current_reservation.cancellation_time = now
        # 	current_reservation.cancelled_by = user
        # 	current_reservation.descendant = new_reservation
        # 	current_reservation.save()
        # except:
        # 	pass
    return HttpResponse()
Example #6
0
def send_new_task_emails(request, task: Task, task_images: List[TaskImages]):
    message = get_media_file_contents('new_task_email.html')
    attachments = None
    if task_images:
        attachments = [
            create_email_attachment(task_image.image, task_image.image.name)
            for task_image in task_images
        ]
    # Send an email to the appropriate staff that a new task has been created:
    if message:
        dictionary = {
            'template_color':
            bootstrap_primary_color('danger')
            if task.force_shutdown else bootstrap_primary_color('warning'),
            'user':
            request.user,
            'task':
            task,
            'tool':
            task.tool,
            'tool_control_absolute_url':
            request.build_absolute_uri(task.tool.get_absolute_url())
        }
        subject = ('SAFETY HAZARD: ' if task.safety_hazard else
                   '') + task.tool.name + (' shutdown' if task.force_shutdown
                                           else ' problem')
        message = Template(message).render(Context(dictionary))
        managers = []
        if hasattr(settings, 'LAB_MANAGERS'):
            managers = settings.LAB_MANAGERS
        recipients = tuple([
            r for r in [
                task.tool.primary_owner.email,
                *task.tool.backup_owners.all().values_list('email', flat=True),
                task.tool.notification_email_address, *managers
            ] if r
        ])
        send_mail(subject=subject,
                  content=message,
                  from_email=request.user.email,
                  to=recipients,
                  attachments=attachments,
                  email_category=EmailCategory.TASKS)

    # Send an email to all users (excluding staff) qualified on the tool:
    user_office_email = get_customization('user_office_email_address')
    message = get_media_file_contents('new_task_email.html')
    if user_office_email and message:
        users = User.objects.filter(qualifications__id=task.tool.id,
                                    is_staff=False,
                                    is_active=True)
        dictionary = {
            'template_color':
            bootstrap_primary_color('danger')
            if task.force_shutdown else bootstrap_primary_color('warning'),
            'user':
            request.user,
            'task':
            task,
            'tool':
            task.tool,
            'tool_control_absolute_url':
            request.build_absolute_uri(task.tool.get_absolute_url()),
            'labmember':
            True
        }
        subject = task.tool.name + (' shutdown'
                                    if task.force_shutdown else ' problem')
        users = [x.email for x in users]
        rendered_message = Template(message).render(Context(dictionary))
        #try:
        email = EmailMultiAlternatives(subject,
                                       from_email=user_office_email,
                                       bcc=set(users))
        email.attach_alternative(rendered_message, 'text/html')
        email.send()
Example #7
0
def interlock_bypass_allowed(user: User):
    return user.is_staff or get_customization(
        'allow_bypass_interlock_on_failure') == 'enabled'
Example #8
0
def create_or_modify_user(request, user_id):
    identity_service = get_identity_service()
    # Get access levels and sort by area category
    access_levels = list(PhysicalAccessLevel.objects.all().only(
        'name', 'area'))
    access_level_for_sort = list(
        set([
            ancestor for access in access_levels
            for ancestor in access.area.get_ancestors(include_self=True)
        ]))
    access_level_for_sort.sort(key=lambda x: x.tree_category())
    area_access_levels = Area.objects.filter(
        id__in=[area.id for area in access_level_for_sort])
    dict_area = {}
    for access in access_levels:
        dict_area.setdefault(access.area.id, []).append(access)

    dictionary = {
        'projects': Project.objects.filter(active=True, account__active=True),
        'tools': Tool.objects.filter(visible=True),
        'area_access_dict': dict_area,
        'area_access_levels': area_access_levels,
        'one_year_from_now': timezone.now() + timedelta(days=365),
        'identity_service_available': identity_service.get('available', False),
        'identity_service_domains': identity_service.get('domains', []),
    }
    try:
        user = User.objects.get(id=user_id)
    except:
        user = None

    timeout = identity_service.get('timeout', 3)
    site_title = get_customization('site_title')
    if dictionary['identity_service_available']:
        try:
            result = requests.get(urljoin(identity_service['url'], '/areas/'),
                                  timeout=timeout)
            if result.status_code == HTTPStatus.OK:
                dictionary[
                    'externally_managed_physical_access_levels'] = result.json(
                    )
            else:
                dictionary['identity_service_available'] = False
                warning_message = f"The identity service encountered a problem while attempting to return a list of externally managed areas. The administrator has been notified to resolve the problem."
                dictionary['warning'] = warning_message
                warning_message += ' The HTTP error was {}: {}'.format(
                    result.status_code, result.text)
                users_logger.error(warning_message)
        except Exception as e:
            dictionary['identity_service_available'] = False
            warning_message = f"There was a problem communicating with the identity service. {site_title} is unable to retrieve the list of externally managed areas. The administrator has been notified to resolve the problem."
            dictionary['warning'] = warning_message
            warning_message += ' An exception was encountered: ' + type(
                e).__name__ + ' - ' + str(e)
            users_logger.error(warning_message)
    elif identity_service:
        # display warning if identity service is defined but disabled
        dictionary[
            'warning'] = 'The identity service is disabled. You will not be able to modify externally managed physical access levels, reset account passwords, or unlock accounts.'

    if request.method == 'GET':
        dictionary['form'] = UserForm(instance=user)
        try:
            if dictionary[
                    'identity_service_available'] and user and user.is_active and user.domain:
                parameters = {
                    'username': user.username,
                    'domain': user.domain,
                }
                result = requests.get(identity_service['url'],
                                      parameters,
                                      timeout=timeout)
                if result.status_code == HTTPStatus.OK:
                    dictionary['user_identity_information'] = result.json()
                elif result.status_code == HTTPStatus.NOT_FOUND:
                    dictionary[
                        'warning'] = "The identity service could not find username {} on the {} domain. Does the user's account reside on a different domain? If so, select that domain now and save the user information.".format(
                            user.username, user.domain)
                else:
                    dictionary['identity_service_available'] = False
                    warning_message = 'The identity service encountered a problem while attempting to search for a user. The administrator has been notified to resolve the problem.'
                    dictionary['warning'] = warning_message
                    warning_message += ' The HTTP error was {}: {}'.format(
                        result.status_code, result.text)
                    users_logger.error(warning_message)
        except Exception as e:
            dictionary['identity_service_available'] = False
            warning_message = f"There was a problem communicating with the identity service. {site_title} is unable to search for a user. The administrator has been notified to resolve the problem."
            dictionary['warning'] = warning_message
            warning_message += ' An exception was encountered: ' + type(
                e).__name__ + ' - ' + str(e)
            users_logger.error(warning_message)
        return render(request, 'users/create_or_modify_user.html', dictionary)
    elif request.method == 'POST':
        form = UserForm(request.POST, instance=user)
        dictionary['form'] = form
        if not form.is_valid():
            return render(request, 'users/create_or_modify_user.html',
                          dictionary)

        # Remove the user account from the domain if it's deactivated, changed domain, or changed username...
        if dictionary['identity_service_available'] and user:
            no_longer_active = form.initial[
                'is_active'] is True and form.cleaned_data['is_active'] is False
            domain_switched = form.initial['domain'] != '' and form.initial[
                'domain'] != form.cleaned_data['domain']
            username_changed = form.initial['username'] != form.cleaned_data[
                'username']
            if no_longer_active or domain_switched or username_changed:
                parameters = {
                    'username': form.initial['username'],
                    'domain': form.initial['domain'],
                }
                try:
                    result = requests.delete(identity_service['url'],
                                             data=parameters,
                                             timeout=timeout)
                    # If the delete succeeds, or the user is not found, then everything is ok.
                    if result.status_code not in (HTTPStatus.OK,
                                                  HTTPStatus.NOT_FOUND):
                        dictionary['identity_service_available'] = False
                        users_logger.error(
                            'The identity service encountered a problem while attempting to delete a user. The HTTP error is {}: {}'
                            .format(result.status_code, result.text))
                        dictionary[
                            'warning'] = 'The user information was not modified because the identity service could not delete the corresponding domain account. The administrator has been notified to resolve the problem.'
                        return render(request,
                                      'users/create_or_modify_user.html',
                                      dictionary)
                except Exception as e:
                    dictionary['identity_service_available'] = False
                    users_logger.error(
                        'There was a problem communicating with the identity service while attempting to delete a user. An exception was encountered: '
                        + type(e).__name__ + ' - ' + str(e))
                    dictionary[
                        'warning'] = 'The user information was not modified because the identity service could not delete the corresponding domain account. The administrator has been notified to resolve the problem.'
                    return render(request, 'users/create_or_modify_user.html',
                                  dictionary)

        # Ensure the user account is added and configured correctly on the current domain if the user is active...
        if dictionary['identity_service_available'] and form.cleaned_data[
                'is_active']:
            parameters = {
                'username':
                form.cleaned_data['username'],
                'domain':
                form.cleaned_data['domain'],
                'badge_number':
                form.cleaned_data.get('badge_number', ''),
                'email':
                form.cleaned_data.get('email'),
                'access_expiration':
                form.cleaned_data.get('access_expiration'),
                'requested_areas':
                request.POST.getlist('externally_managed_access_levels'),
            }
            try:
                if len(parameters['requested_areas']
                       ) > 0 and not parameters['badge_number']:
                    dictionary[
                        'warning'] = 'A user must have a badge number in order to have area access. Please enter the badge number first, then grant access to areas.'
                    return render(request, 'users/create_or_modify_user.html',
                                  dictionary)
                result = requests.put(identity_service['url'],
                                      data=parameters,
                                      timeout=timeout)
                if result.status_code == HTTPStatus.NOT_FOUND:
                    dictionary[
                        'warning'] = 'The username was not found on this domain. Did you spell the username correctly in this form and did you select the correct domain? Ensure the user exists on the domain in order to proceed.'
                    return render(request, 'users/create_or_modify_user.html',
                                  dictionary)
                if result.status_code != HTTPStatus.OK:
                    dictionary['identity_service_available'] = False
                    users_logger.error(
                        'The identity service encountered a problem while attempting to modify a user. The HTTP error is {}: {}'
                        .format(result.status_code, result.text))
                    dictionary[
                        'warning'] = 'The user information was not modified because the identity service encountered a problem while creating the corresponding domain account. The administrator has been notified to resolve the problem.'
                    return render(request, 'users/create_or_modify_user.html',
                                  dictionary)
            except Exception as e:
                dictionary['identity_service_available'] = False
                users_logger.error(
                    'There was a problem communicating with the identity service while attempting to modify a user. An exception was encountered: '
                    + type(e).__name__ + ' - ' + str(e))
                dictionary[
                    'warning'] = 'The user information was not modified because the identity service encountered a problem while creating the corresponding domain account. The administrator has been notified to resolve the problem.'
                return render(request, 'users/create_or_modify_user.html',
                              dictionary)

        # Only save the user model for now, and wait to process the many-to-many relationships.
        # This way, many-to-many changes can be recorded.
        # See this web page for more information:
        # https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method
        user = form.save(commit=False)
        user.save()
        record_active_state(request, user, form, 'is_active', user_id == 'new')
        record_local_many_to_many_changes(request, user, form,
                                          'qualifications')
        record_local_many_to_many_changes(request, user, form,
                                          'physical_access_levels')
        record_local_many_to_many_changes(request, user, form, 'projects')
        form.save_m2m()

        message = f"{user} has been added successfully to {site_title}" if user_id == 'new' else f"{user} has been updated successfully"
        messages.success(request, message)
        return redirect('users')
    else:
        return HttpResponseBadRequest('Invalid method')
Example #9
0
def get_billing_data(start, end):
    billing_result = []
    undergrad_rate = UserType.objects.get(name='Undergraduate').daily_rate
    #daily_rate = {'Internal - Full':135, 'Internal - Unlimited':0, 'Internal - SMP':67.5, 'Internal - Packaging':67.5, 'External Academic':220,'Industrial':0,'Undergraduate':45}

    #staff_charge_rate = {'Internal - Full':80, 'Internal - Unlimited':80, 'Internal - SMP':80, 'Internal - Packaging':80, 'External Academic':130,'Industrial':480,'Undergraduate':80}
    user_exclude = [1, 3, 7, 8]
    projects_to_exclude = []
    exclude = get_customization('exclude_from_billing')
    if exclude:
        projects_to_exclude = [int(s) for s in exclude.split() if s.isdigit()]
    users = User.objects.all().exclude(type__in=user_exclude).order_by(
        'type', 'last_name')
    for user in users:
        billable_days = 0
        try:
            user_access = AreaAccessRecord.objects.filter(
                customer=user, end__gte=start, end__lt=end,
                staff_charge=None).exclude(
                    project__id__in=projects_to_exclude).order_by('start')
            for index, access_event in enumerate(user_access):
                start_date = timezone.localtime(access_event.start).date()
                end_date = timezone.localtime(access_event.end).date()
                dt = end_date - start_date
                days = dt.days
                if index == 0:
                    billable_days += days + 1
                else:
                    last_access = user_access[index - 1]
                    if timezone.localtime(
                            last_access.end).date() == start_date:
                        billable_days += days
                    else:
                        billable_days += days + 1
        except:
            pass
        stockroom_bill = 0
        try:
            stockroom_withdraws = StockroomWithdraw.objects.filter(
                customer=user, date__range=(start, end))
            for purchase in stockroom_withdraws:
                stockroom_bill += purchase.stock.cost * purchase.quantity
        except:
            pass
        staff_charge_bill = 0
        try:
            staff_charge = StaffCharge.objects.filter(customer=user,
                                                      end__gte=start,
                                                      end__lt=end,
                                                      validated=True)
            for charge in staff_charge:
                chargetime = charge.end - charge.start
                staff_charge_bill += round(
                    Decimal(chargetime.total_seconds() / 3600) *
                    user.type.staff_rate, 2)
        except:
            pass
        name = user.last_name + ", " + user.first_name
        usage_bill = 0
        try:
            principal_inv = user.active_projects().exclude(
                id__in=projects_to_exclude).values_list('account__name',
                                                        flat=True)[0]
        except:
            principal_inv = "unknown"
        if user.type.name == 'Internal - Unlimited':
            usage_bill = user.type.daily_rate
        elif user.type.name == 'Internal - Full' or user.type.name == 'Internal - Packaging' or user.type.name == 'Internal - SMP':
            if billable_days > 10:
                usage_bill = 10 * user.type.daily_rate + (billable_days -
                                                          10) * undergrad_rate
            else:
                usage_bill = billable_days * user.type.daily_rate
        else:
            usage_bill = billable_days * user.type.daily_rate
        user_billing = {
            'username': user.username,
            'name': name,
            'email': user.email,
            'PI': principal_inv,
            'user_type': user.type.name,
            'billable_days': billable_days,
            'rate': user.type.daily_rate,
            'usage_bill': usage_bill,
            'stockroom_bill': stockroom_bill,
            'staff_charge_bill': staff_charge_bill,
            'total_bill': usage_bill + staff_charge_bill + stockroom_bill
        }
        billing_result.append(user_billing)

    return billing_result
Example #10
0
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()
Example #11
0
def check_policy_to_save_reservation(
        cancelled_reservation: Optional[Reservation],
        new_reservation: Reservation, user_creating_reservation: User,
        explicit_policy_override: bool):
    """
		Check the reservation creation policy and return a list of policy problems if any.
	"""
    user = new_reservation.user

    facility_name = get_customization('facility_name')

    # The function will check all policies. Policy problems are placed in the policy_problems list. overridable is True if the policy problems can be overridden by a staff member.
    policy_problems = []
    overridable = False

    item_type = new_reservation.reservation_item_type

    # Reservations may not have a start time that is earlier than the end time.
    if new_reservation.start >= new_reservation.end:
        policy_problems.append("Reservation start time (" +
                               format_datetime(new_reservation.start) +
                               ") must be before the end time (" +
                               format_datetime(new_reservation.end) + ").")

    check_coincident_item_reservation_policy(cancelled_reservation,
                                             new_reservation,
                                             user_creating_reservation,
                                             policy_problems)

    # Reservations that have been cancelled may not be changed.
    if new_reservation.cancelled:
        policy_problems.append(
            "This reservation has already been cancelled by " +
            str(new_reservation.cancelled_by) + " at " +
            format_datetime(new_reservation.cancellation_time) + ".")

    # The user must belong to at least one active project to make a reservation.
    if user.active_project_count() < 1:
        if user == user_creating_reservation:
            policy_problems.append(
                "You do not belong to any active projects. Thus, you may not create any reservations."
            )
        else:
            policy_problems.append(
                str(user) +
                " does not belong to any active projects and cannot have reservations."
            )

    # The user must associate their reservation with a project they belong to.
    if new_reservation.project and new_reservation.project not in user.active_projects(
    ):
        if user == user_creating_reservation:
            policy_problems.append(
                "You do not belong to the project associated with this reservation."
            )
        else:
            policy_problems.append(
                str(user) + " does not belong to the project named " +
                str(new_reservation.project) + ".")

    # If the user is a staff member or there's an explicit policy override then the policy check is finished.
    if user.is_staff or explicit_policy_override:
        return policy_problems, overridable

    # If there are no blocking policy conflicts at this point, the rest of the policies can be overridden.
    if not policy_problems:
        overridable = True

    # Some tool reservations require a prior area reservation
    # Staff may break this rule.
    # An explicit policy override allows this rule to be broken.
    if item_type == ReservationItemType.TOOL:
        if new_reservation.tool.requires_area_reservation():
            area: Area = new_reservation.tool.requires_area_access
            # Check that a reservation for the area has been made and contains the start time
            if not Reservation.objects.filter(
                    missed=False,
                    cancelled=False,
                    shortened=False,
                    user=user,
                    area=area,
                    start__lte=new_reservation.start,
                    end__gt=new_reservation.start).exists():
                if user == user_creating_reservation:
                    policy_problems.append(
                        f"This tool requires a {area} reservation. Please make a reservation in the {area} prior to reserving this tool."
                    )
                else:
                    policy_problems.append(
                        f"This tool requires a {area} reservation. Please make sure to also create a reservation in the {area} or {str(user)} will not be able to enter the area."
                    )

    # The user must complete training to create reservations.
    # Staff may break this rule.
    # An explicit policy override allows this rule to be broken.
    if user.training_required:
        if user == user_creating_reservation:
            policy_problems.append(
                f"You are blocked from making reservations in the {facility_name}. Please complete the {facility_name} rules tutorial in order to create new reservations."
            )
        else:
            policy_problems.append(
                f"{str(user)} is blocked from making reservations in the {facility_name}. The user needs to complete the {facility_name} rules tutorial in order to create new reservations."
            )

    # Users may only change their own reservations.
    # Staff may break this rule.
    # An explicit policy override allows this rule to be broken.
    if cancelled_reservation and user != user_creating_reservation:
        policy_problems.append(
            "You may not change reservations that you do not own.")

    # The user may not create or move a reservation to have a start time that is earlier than the current time.
    # Staff may break this rule.
    # An explicit policy override allows this rule to be broken.
    if new_reservation.start < timezone.now():
        policy_problems.append("Reservation start time (" +
                               format_datetime(new_reservation.start) +
                               ") is earlier than the current time (" +
                               format_datetime(timezone.now()) + ").")

    # The user may not move or resize a reservation to have an end time that is earlier than the current time.
    # Staff may break this rule.
    # An explicit policy override allows this rule to be broken.
    if new_reservation.end < timezone.now():
        policy_problems.append("Reservation end time (" +
                               format_datetime(new_reservation.end) +
                               ") is earlier than the current time (" +
                               format_datetime(timezone.now()) + ").")

    # The user must be qualified on the tool in question in order to create, move, or resize a reservation.
    # Staff may break this rule.
    # An explicit policy override allows this rule to be broken.
    if new_reservation.tool and new_reservation.tool not in user.qualifications.all(
    ):
        if user == user_creating_reservation:
            policy_problems.append(
                "You are not qualified to use this tool. Creating, moving, and resizing reservations is forbidden."
            )
        else:
            policy_problems.append(
                f"{str(user)} is not qualified to use this tool. Creating, moving, and resizing reservations is forbidden."
            )

    # The user must be authorized on the area in question at the start and end times of the reservation in order to create, move, or resize a reservation.
    # Staff may break this rule.
    # An explicit policy override allows this rule to be broken.
    if item_type == ReservationItemType.AREA:
        user_access_levels = user.accessible_access_levels_for_area(
            new_reservation.area)
        if not any([
                access_level.accessible_at(new_reservation.start)
                for access_level in user_access_levels
        ]) or not any([
                access_level.accessible_at(new_reservation.end)
                for access_level in user_access_levels
        ]):
            details = f" (times allowed in this area are: {','.join([access.get_schedule_display_with_times() for access in user_access_levels])})" if user_access_levels else ''
            if user == user_creating_reservation:
                policy_problems.append(
                    f"You are not authorized to access this area at this time{details}. Creating, moving, and resizing reservations is forbidden."
                )
            else:
                policy_problems.append(
                    f"{str(user)} is not authorized to access this area at this time{details}. Creating, moving, and resizing reservations is forbidden."
                )

    check_tool_reservation_requiring_area(policy_problems,
                                          user_creating_reservation,
                                          cancelled_reservation,
                                          new_reservation)

    # The reservation start time may not exceed the item's reservation horizon.
    # Staff may break this rule.
    # An explicit policy override allows this rule to be broken.
    item = new_reservation.reservation_item
    if item.reservation_horizon is not None:
        reservation_horizon = timedelta(days=item.reservation_horizon)
        if new_reservation.start > timezone.now() + reservation_horizon:
            policy_problems.append(
                "You may not create reservations further than " +
                str(reservation_horizon.days) +
                f" days from now for this {item_type.value}.")

    # Check item policy rules
    item_policy_problems = []
    if should_enforce_policy(new_reservation):
        item_policy_problems = check_policy_rules_for_item(
            cancelled_reservation, new_reservation, user_creating_reservation)

    # Return the list of all policies that are not met.
    return policy_problems + item_policy_problems, overridable
Example #12
0
def login_to_area(request, door_id):
	door = get_object_or_404(Door, id=door_id)

	badge_number = request.POST.get('badge_number', '')
	if badge_number == '':
		return render(request, 'area_access/badge_not_found.html')
	try:
		badge_number = int(badge_number)
		user = User.objects.get(badge_number=badge_number)
	except (User.DoesNotExist, ValueError):
		return render(request, 'area_access/badge_not_found.html')

	log = PhysicalAccessLog()
	log.user = user
	log.door = door
	log.time = timezone.now()
	log.result = PhysicalAccessType.DENY  # Assume the user does not have access

	facility_name = get_customization('facility_name')

	# Check policy for entering an area
	try:
		check_policy_to_enter_any_area(user=user)
	except InactiveUserError:
		log.details = "This user is not active, preventing them from entering any access controlled areas."
		log.save()
		return render(request, 'area_access/inactive.html')

	except NoActiveProjectsForUserError:
		log.details = "The user has no active projects, preventing them from entering an access controlled area."
		log.save()
		return render(request, 'area_access/no_active_projects.html')

	except PhysicalAccessExpiredUserError:
		log.details = "This user was blocked from this physical access level because their physical access has expired."
		log.save()
		message = f"Your physical access to the {facility_name} has expired. Have you completed your safety training within the last year? Please visit the User Office to renew your access."
		return render(request, 'area_access/physical_access_denied.html', {'message': message})

	except NoPhysicalAccessUserError:
		log.details = "This user does not belong to ANY physical access levels."
		log.save()
		message = f"You have not been granted physical access to any {facility_name} area. Please visit the User Office if you believe this is an error."
		return render(request, 'area_access/physical_access_denied.html', {'message': message})

	max_capacity_reached = False
	reservation_requirement_failed = False
	scheduled_outage_in_progress = False
	# Check policy to enter this area
	try:
		check_policy_to_enter_this_area(area=door.area, user=user)
	except NoAccessiblePhysicalAccessUserError:
		log.details = "This user is not assigned to a physical access level that allows access to this door at this time."
		log.save()
		message = f"You do not have access to this area of the {facility_name} at this time. Please visit the User Office if you believe this is an error."
		return render(request, 'area_access/physical_access_denied.html', {'message': message})

	except UnavailableResourcesUserError as error:
		log.details = "The user was blocked from entering this area because a required resource was unavailable."
		log.save()
		return render(request, 'area_access/resource_unavailable.html', {'unavailable_resources': error.resources})

	except MaximumCapacityReachedError as error:
		# deal with this error after checking if the user is already logged in
		max_capacity_reached = error

	except ScheduledOutageInProgressError as error:
		# deal with this error after checking if the user is already logged in
		scheduled_outage_in_progress = error

	except ReservationRequiredUserError:
		# deal with this error after checking if the user is already logged in
		reservation_requirement_failed = True

	current_area_access_record = user.area_access_record()
	if current_area_access_record and current_area_access_record.area == door.area:
		# No log entry necessary here because all validation checks passed.
		# The log entry is captured when the subsequent choice is made by the user.
		return render(request, 'area_access/already_logged_in.html', {
			'area': door.area,
			'project': current_area_access_record.project,
			'badge_number': user.badge_number,
			'reservation_requirement_failed': reservation_requirement_failed,
			'max_capacity_reached': max_capacity_reached,
			'scheduled_outage_in_progress': scheduled_outage_in_progress,
		})

	if scheduled_outage_in_progress:
		log.details = f"The user was blocked from entering this area because the {scheduled_outage_in_progress.area.name} has a scheduled outage in progress."
		log.save()
		message = f"The {scheduled_outage_in_progress.area.name} is inaccessible because a scheduled outage is in progress."
		return render(request, 'area_access/physical_access_denied.html', {'message': message})

	if max_capacity_reached:
		log.details = f"The user was blocked from entering this area because the {max_capacity_reached.area.name} has reached its maximum capacity of {max_capacity_reached.area.maximum_capacity} people at a time."
		log.save()
		message = f"The {max_capacity_reached.area.name} has reached its maximum capacity. Please wait for somebody to leave and try again."
		return render(request, 'area_access/physical_access_denied.html', {'message': message})

	if reservation_requirement_failed:
		log.details = f"The user was blocked from entering this area because the user does not have a current reservation for the {door.area}."
		log.save()
		message = "You do not have a current reservation for this area. Please make a reservation before trying to access this area."
		return render(request, 'area_access/physical_access_denied.html', {'message': message})

	previous_area = None
	if user.active_project_count() >= 1:
		if user.active_project_count() == 1:
			project = user.active_projects()[0]
		else:
			project_id = request.POST.get('project_id')
			if not project_id:
				# No log entry necessary here because all validation checks passed, and the user must indicate which project
				# the wish to login under. The log entry is captured when the subsequent choice is made by the user.
				return render(request, 'area_access/choose_project.html', {'area': door.area, 'user': user})
			else:
				project = get_object_or_404(Project, id=project_id)
				if project not in user.active_projects():
					log.details = "The user attempted to bill the project named {}, but they are not a member of that project.".format(
						project.name)
					log.save()
					message = "You are not authorized to bill this project."
					return render(request, 'area_access/physical_access_denied.html', {'message': message})

		log.result = PhysicalAccessType.ALLOW
		log.save()

		# Automatically log the user out of any previous area before logging them in to the new area.
		if user.in_area():
			previous_area_access_record = user.area_access_record()
			previous_area_access_record.end = timezone.now()
			previous_area_access_record.save()
			previous_area = previous_area_access_record.area

		record = AreaAccessRecord()
		record.area = door.area
		record.customer = user
		record.project = project
		record.save()
		unlock_door(door.id)
		return render(request, 'area_access/login_success.html', {'area': door.area, 'name': user.first_name, 'project': record.project, 'previous_area': previous_area})
Example #13
0
def send_new_task_emails(request, task):
    message = get_media_file_contents('new_task_email.html')
    if message:
        dictionary = {
            'template_color':
            bootstrap_primary_color('danger')
            if task.force_shutdown else bootstrap_primary_color('warning'),
            'user':
            request.user,
            'task':
            task,
            'tool':
            task.tool,
            'tool_control_absolute_url':
            request.build_absolute_uri(task.tool.get_absolute_url())
        }
        # Send an email to the appropriate NanoFab staff that a new task has been created:
        subject = ('SAFETY HAZARD: ' if task.safety_hazard else
                   '') + task.tool.name + (' shutdown' if task.force_shutdown
                                           else ' problem')
        message = Template(message).render(Context(dictionary))
        recipients = tuple([
            r for r in [
                task.tool.primary_owner.email,
                *task.tool.backup_owners.all().values_list('email', flat=True),
                task.tool.notification_email_address
            ] if r
        ])
        send_mail(subject,
                  '',
                  request.user.email,
                  recipients,
                  html_message=message)

    # Send an email to all users (excluding staff) qualified on the tool:
    user_office_email = get_customization('user_office_email_address')
    message = get_media_file_contents('new_task_email.html')
    if user_office_email and message:
        users = User.objects.filter(qualifications__id=task.tool.id,
                                    is_staff=False)
        dictionary = {
            'template_color':
            bootstrap_primary_color('danger')
            if task.force_shutdown else bootstrap_primary_color('warning'),
            'user':
            request.user,
            'task':
            task,
            'tool':
            task.tool,
            'tool_control_absolute_url':
            request.build_absolute_uri(task.tool.get_absolute_url()),
            'labmember':
            True
        }
        subject = task.tool.name + (' shutdown'
                                    if task.force_shutdown else ' problem')
        users = [x.email for x in users]
        rendered_message = Template(message).render(Context(dictionary))
        try:
            email = EmailMultiAlternatives(subject,
                                           from_email=user_office_email,
                                           bcc=set(users))
            email.attach_alternative(rendered_message, 'text/html')
            email.send()
        except SMTPException as e:
            dictionary = {
                'title':
                'Email not sent',
                'heading':
                'There was a problem sending your email',
                'content':
                'NEMO was unable to send the email through the email server. The error message that NEMO received is: '
                + str(e),
            }
Example #14
0
def send_new_task_emails(request, task: Task, task_images: List[TaskImages]):
    message = get_media_file_contents('new_task_email.html')
    attachments = None
    if task_images:
        attachments = [
            create_email_attachment(task_image.image, task_image.image.name)
            for task_image in task_images
        ]
    # Send an email to the appropriate staff that a new task has been created:
    if message:
        dictionary = {
            'template_color':
            bootstrap_primary_color('danger')
            if task.force_shutdown else bootstrap_primary_color('warning'),
            'user':
            request.user,
            'task':
            task,
            'tool':
            task.tool,
            'tool_control_absolute_url':
            request.build_absolute_uri(task.tool.get_absolute_url())
        }
        subject = ('SAFETY HAZARD: ' if task.safety_hazard else
                   '') + task.tool.name + (' shutdown' if task.force_shutdown
                                           else ' problem')
        message = Template(message).render(Context(dictionary))
        managers = []
        if hasattr(settings, 'LAB_MANAGERS'):
            managers = settings.LAB_MANAGERS
        recipients = tuple([
            r for r in [
                task.tool.primary_owner.email,
                *task.tool.backup_owners.all().values_list('email', flat=True),
                task.tool.notification_email_address, *managers
            ] if r
        ])
        send_mail(subject, message, request.user.email, recipients,
                  attachments)

    # Send an email to any user (excluding staff) with a future reservation on the tool:
    user_office_email = get_customization('user_office_email_address')
    message = get_media_file_contents('new_task_email.html')
    if user_office_email and message:
        upcoming_reservations = Reservation.objects.filter(
            start__gt=timezone.now(),
            cancelled=False,
            tool=task.tool,
            user__is_staff=False)
        for reservation in upcoming_reservations:
            if not task.tool.operational:
                subject = reservation.tool.name + " reservation problem"
                rendered_message = Template(message).render(
                    Context({
                        'reservation': reservation,
                        'template_color': bootstrap_primary_color('danger'),
                        'fatal_error': True
                    }))
            else:
                subject = reservation.tool.name + " reservation warning"
                rendered_message = Template(message).render(
                    Context({
                        'reservation':
                        reservation,
                        'template_color':
                        bootstrap_primary_color('warning'),
                        'fatal_error':
                        False
                    }))
            reservation.user.email_user(subject, rendered_message,
                                        user_office_email)
Example #15
0
def login_to_area(request, door_id):
    door = get_object_or_404(Door, id=door_id)

    badge_number = request.POST.get("badge_number")
    bypass_interlock = request.POST.get("bypass", 'False') == 'True'
    if not badge_number:
        return render(request, "area_access/badge_not_found.html")
    try:
        user = User.objects.get(badge_number=badge_number)
    except User.DoesNotExist:
        return render(request, "area_access/badge_not_found.html")

    log = PhysicalAccessLog()
    log.user = user
    log.door = door
    log.time = timezone.now()
    log.result = PhysicalAccessType.DENY  # Assume the user does not have access

    facility_name = get_customization("facility_name")

    # Check policy for entering an area
    try:
        check_policy_to_enter_any_area(user=user)
    except InactiveUserError:
        log.details = "This user is not active, preventing them from entering any access controlled areas."
        log.save()
        return render(request, "area_access/inactive.html")

    except NoActiveProjectsForUserError:
        log.details = "The user has no active projects, preventing them from entering an access controlled area."
        log.save()
        return render(request, "area_access/no_active_projects.html")

    except PhysicalAccessExpiredUserError:
        log.details = "This user was blocked from this physical access level because their physical access has expired."
        log.save()
        message = f"Your physical access to the {facility_name} has expired. Have you completed your safety training within the last year? Please visit the User Office to renew your access."
        return render(request, "area_access/physical_access_denied.html",
                      {"message": message})

    except NoPhysicalAccessUserError:
        log.details = "This user does not belong to ANY physical access levels."
        log.save()
        message = f"You have not been granted physical access to any {facility_name} area. Please visit the User Office if you believe this is an error."
        return render(request, "area_access/physical_access_denied.html",
                      {"message": message})

    max_capacity_reached = False
    reservation_requirement_failed = False
    scheduled_outage_in_progress = False
    # Check policy to enter this area
    try:
        check_policy_to_enter_this_area(area=door.area, user=user)
    except NoAccessiblePhysicalAccessUserError as error:
        if error.access_exception:
            log.details = (
                f"The user was blocked from entering this area because of an exception: {error.access_exception.name}."
            )
            message = f"You do not have access to this area of the {facility_name} due to the following exception: {error.access_exception}. The exception ends on {localize(error.access_exception.end_time.astimezone(timezone.get_current_timezone()))}"
        else:
            log.details = (
                "This user is not assigned to a physical access level that allows access to this door at this time."
            )
            message = f"You do not have access to this area of the {facility_name} at this time. Please visit the User Office if you believe this is an error."
        log.save()
        return render(request, "area_access/physical_access_denied.html",
                      {"message": message})

    except UnavailableResourcesUserError as error:
        log.details = f"The user was blocked from entering this area because a required resource was unavailable [{', '.join(str(resource) for resource in error.resources)}]."
        log.save()
        return render(request, "area_access/resource_unavailable.html",
                      {"unavailable_resources": error.resources})

    except MaximumCapacityReachedError as error:
        # deal with this error after checking if the user is already logged in
        max_capacity_reached = error

    except ScheduledOutageInProgressError as error:
        # deal with this error after checking if the user is already logged in
        scheduled_outage_in_progress = error

    except ReservationRequiredUserError:
        # deal with this error after checking if the user is already logged in
        reservation_requirement_failed = True

    current_area_access_record = user.area_access_record()
    if current_area_access_record and current_area_access_record.area == door.area:
        # No log entry necessary here because all validation checks passed.
        # The log entry is captured when the subsequent choice is made by the user.
        return render(
            request,
            "area_access/already_logged_in.html",
            {
                "area": door.area,
                "project": current_area_access_record.project,
                "badge_number": user.badge_number,
                "reservation_requirement_failed":
                reservation_requirement_failed,
                "max_capacity_reached": max_capacity_reached,
                "scheduled_outage_in_progress": scheduled_outage_in_progress,
            },
        )

    if scheduled_outage_in_progress:
        log.details = f"The user was blocked from entering this area because the {scheduled_outage_in_progress.area.name} has a scheduled outage in progress."
        log.save()
        message = (
            f"The {scheduled_outage_in_progress.area.name} is inaccessible because a scheduled outage is in progress."
        )
        return render(request, "area_access/physical_access_denied.html",
                      {"message": message})

    if max_capacity_reached:
        log.details = f"The user was blocked from entering this area because the {max_capacity_reached.area.name} has reached its maximum capacity of {max_capacity_reached.area.maximum_capacity} people at a time."
        log.save()
        message = f"The {max_capacity_reached.area.name} has reached its maximum capacity. Please wait for somebody to leave and try again."
        return render(request, "area_access/physical_access_denied.html",
                      {"message": message})

    if reservation_requirement_failed:
        log.details = f"The user was blocked from entering this area because the user does not have a current reservation for the {door.area}."
        log.save()
        message = "You do not have a current reservation for this area. Please make a reservation before trying to access this area."
        return render(request, "area_access/physical_access_denied.html",
                      {"message": message})

    if user.active_project_count() >= 1:
        if user.active_project_count() == 1:
            project = user.active_projects()[0]
        else:
            project_id = request.POST.get("project_id")
            if not project_id:
                # No log entry necessary here because all validation checks passed, and the user must indicate which project
                # the wish to login under. The log entry is captured when the subsequent choice is made by the user.
                return render(request, "area_access/choose_project.html", {
                    "area": door.area,
                    "user": user
                })
            else:
                project = get_object_or_404(Project, id=project_id)
                try:
                    check_billing_to_project(project, user, door.area)
                except ProjectChargeException as e:
                    log.details = "The user attempted to bill the project named {} but got error: {}".format(
                        project.name, e.msg)
                    log.save()
                    return render(request,
                                  "area_access/physical_access_denied.html",
                                  {"message": e.msg})

        log.result = PhysicalAccessType.ALLOW
        log.save()

        # Automatically log the user out of any previous area before logging them in to the new area.
        previous_area = None
        if user.in_area():
            previous_area = user.area_access_record().area
            log_out_user(user)

        # All policy checks passed so open the door for the user.
        if not door.interlock.unlock():
            if bypass_interlock and interlock_bypass_allowed(user):
                pass
            else:
                return interlock_error("Login", user)

        delay_lock_door(door.id)

        log_in_user_to_area(door.area, user, project)

        return render(
            request,
            "area_access/login_success.html",
            {
                "area": door.area,
                "name": user.first_name,
                "project": project,
                "previous_area": previous_area
            },
        )
Example #16
0
def send_broadcast_email(request):
    content = get_media_file_contents('generic_email.html')
    if not content:
        return HttpResponseBadRequest(
            'Generic email template not defined. Visit the customization page to upload a template.'
        )
    form = EmailBroadcastForm(request.POST)
    if not form.is_valid():
        return render(request, 'email/compose_email.html', {'form': form})
    dictionary = {
        'title': form.cleaned_data['title'],
        'greeting': form.cleaned_data['greeting'],
        'contents': form.cleaned_data['contents'],
        'template_color': form.cleaned_data['color'],
    }
    content = Template(content).render(Context(dictionary))
    active_choice = form.cleaned_data['only_active_users']
    try:
        audience = form.cleaned_data['audience']
        selection = form.cleaned_data['selection']
        no_type = form.cleaned_data['no_type']
        users = get_users_for_email(audience, selection, no_type)
        if active_choice:
            users = users.filter(is_active=True)
    except Exception as error:
        warning_message = 'Your email was not sent. There was a problem finding the users to send the email to.'
        dictionary = {'error': warning_message}
        logger.warning(
            warning_message +
            ' audience: {}, only_active: {}. The error message that was received is: {}'
            .format(audience, active_choice, str(error)))
        return render(request, 'email/compose_email.html', dictionary)
    if not users:
        dictionary = {
            'error':
            'The audience you specified is empty. You must send the email to at least one person.'
        }
        return render(request, 'email/compose_email.html', dictionary)
    subject = form.cleaned_data['subject']
    users = [x.email for x in users]
    if form.cleaned_data['copy_me']:
        users += [request.user.email]
    try:
        send_mail(subject=subject,
                  content=content,
                  from_email=request.user.email,
                  bcc=set(users),
                  email_category=EmailCategory.BROADCAST_EMAIL)
    except SMTPException as error:
        site_title = get_customization('site_title')
        error_message = f"{site_title} was unable to send the email through the email server. The error message that was received is: " + str(
            error)
        logger.exception(error_message)
        dictionary = {
            'title': 'Email not sent',
            'heading': 'There was a problem sending your email',
            'content': error_message,
        }
        return render(request, 'acknowledgement.html', dictionary)
    dictionary = {
        'title': 'Email sent',
        'heading': 'Your email was sent',
    }
    return render(request, 'acknowledgement.html', dictionary)
Example #17
0
def create_reservation(request):
    """ Create a reservation for a user. """
    try:
        start, end = extract_times(request.POST)
    except Exception as e:
        return HttpResponseBadRequest(str(e))
    tool = get_object_or_404(Tool, name=request.POST.get('tool_name'))
    explicit_policy_override = False
    if request.user.is_staff:
        try:
            user = User.objects.get(id=request.POST['impersonate'])
        except:
            user = request.user
        try:
            explicit_policy_override = request.POST[
                'explicit_policy_override'] == 'true'
        except:
            pass
    else:
        user = request.user
    # Create the new reservation:
    new_reservation = Reservation()
    new_reservation.user = user
    new_reservation.creator = request.user
    new_reservation.tool = tool
    new_reservation.start = start
    new_reservation.end = end
    new_reservation.short_notice = determine_insufficient_notice(tool, start)
    policy_problems, overridable = check_policy_to_save_reservation(
        None, new_reservation, user, explicit_policy_override)

    # If there was a problem in saving the reservation then return the error...
    if policy_problems:
        return render(
            request, 'calendar/policy_dialog.html', {
                'policy_problems': policy_problems,
                'overridable': overridable and request.user.is_staff
            })

    # All policy checks have passed.

    # If the user only has one project then associate it with the reservation.
    # Otherwise, present a dialog box for the user to choose which project to associate.
    exclude = get_customization('exclude_from_usage')
    projects_to_exclude = []
    if exclude:
        projects_to_exclude = [int(s) for s in exclude.split() if s.isdigit()]
    active_projects = user.active_projects().exclude(
        id__in=projects_to_exclude)
    if len(active_projects) == 1:
        new_reservation.project = active_projects[0]
    else:
        try:
            new_reservation.project = Project.objects.get(
                id=request.POST['project_id'])
        except:
            return render(request, 'calendar/project_choice.html',
                          {'active_projects': active_projects})

    # Make sure the user is actually enrolled on the project. We wouldn't want someone
    # forging a request to reserve against a project they don't belong to.
    if new_reservation.project not in new_reservation.user.active_projects():
        return render(request, 'calendar/project_choice.html',
                      {'active_projects': active_projects()})

    configured = (request.POST.get('configured') == "true")
    # If a reservation is requested and the tool does not require configuration...
    if not tool.is_configurable():
        new_reservation.save()
        return HttpResponse()

    # If a reservation is requested and the tool requires configuration that has not been submitted...
    elif tool.is_configurable() and not configured:
        configuration_information = tool.get_configuration_information(
            user=user, start=start)
        return render(request, 'calendar/configuration.html',
                      configuration_information)

    # If a reservation is requested and configuration information is present also...
    elif tool.is_configurable() and configured:
        new_reservation.additional_information, new_reservation.self_configuration = extract_configuration(
            request)
        # Reservation can't be short notice if the user is configuring the tool themselves.
        if new_reservation.self_configuration:
            new_reservation.short_notice = False
        new_reservation.save()
        return HttpResponse()

    return HttpResponseBadRequest(
        "Reservation creation failed because invalid parameters were sent to the server."
    )
Example #18
0
def send_task_update_emails(request, task):
    message = get_media_file_contents('generic_email.html')
    user_office_email = get_customization('user_office_email_address')
    if message and user_office_email:
        task.refresh_from_db()
        if not task.tool.problematic():
            tool_status = 'up'
        elif task.tool.operational:
            tool_status = 'in problem status'
        else:
            tool_status = 'down'
        if task.resolved:
            subject = f'{task.tool} Issue Resolved'
            task_user = task.resolver
        elif task.cancelled:
            subject = f'{task.tool} Issue Cancelled'
            task_user = task.resolver
        else:
            subject = f'{task.tool} Update'
            task_user = task.last_updated_by
        messagebody = f"""
	The status of the {task.tool} was just modified by {task_user}.
	You can see the full status description here: {request.build_absolute_uri(task.tool.get_absolute_url())}.

	{task.tool} is currently {tool_status}.
	"""
    if task.resolved:
        messagebody += f"""
	Resolution description:
	{task.resolution_description}
	"""
    dictionary = {
        'template_color':
        bootstrap_primary_color('success')
        if tool_status == 'up' else bootstrap_primary_color('warning'),
        'title':
        subject,
        'greeting':
        f'{task.tool} Users,',
        'contents':
        messagebody
    }
    users = User.objects.filter(qualifications__id=task.tool.id,
                                is_active=True)
    users = [x.email for x in users]
    rendered_message = Template(message).render(Context(dictionary))
    try:
        email = EmailMultiAlternatives(subject,
                                       from_email=user_office_email,
                                       bcc=set(users))
        email.attach_alternative(rendered_message, 'text/html')
        email.send()
    except SMTPException as e:
        dictionary = {
            'title':
            'Email not sent',
            'heading':
            'There was a problem sending your email',
            'content':
            'NEMO was unable to send the email through the email server. The error message that NEMO received is: '
            + str(e),
        }