Example #1
0
    def restore(self, request, pk):
        # Get the ConfigRevision being restored
        candidate_config = get_object_or_404(ConfigRevision, pk=pk)

        if request.method == 'POST':
            candidate_config.activate()
            self.message_user(request,
                              f"Restored configuration revision #{pk}")

            return redirect(reverse('admin:extras_configrevision_changelist'))

        # Get the current ConfigRevision
        config_version = get_config().version
        current_config = ConfigRevision.objects.filter(
            pk=config_version).first()

        params = []
        for param in PARAMS:
            params.append(
                (param.name, current_config.data.get(param.name, None),
                 candidate_config.data.get(param.name, None)))

        context = self.admin_site.each_context(request)
        context.update({
            'object': candidate_config,
            'params': params,
        })

        return TemplateResponse(request,
                                'admin/extras/configrevision/restore.html',
                                context)
Example #2
0
    def post(self, request):
        logger = logging.getLogger('netbox.auth.login')
        form = LoginForm(request, data=request.POST)

        if form.is_valid():
            logger.debug("Login form validation was successful")

            # If maintenance mode is enabled, assume the database is read-only, and disable updating the user's
            # last_login time upon authentication.
            if get_config().MAINTENANCE_MODE:
                logger.warning("Maintenance mode enabled: disabling update of most recent login time")
                user_logged_in.disconnect(update_last_login, dispatch_uid='update_last_login')

            # Authenticate user
            auth_login(request, form.get_user())
            logger.info(f"User {request.user} successfully authenticated")
            messages.info(request, "Logged in as {}.".format(request.user))

            return self.redirect_to_next(request, logger)

        else:
            logger.debug("Login form validation failed")

        return render(request, self.template_name, {
            'form': form,
            'auth_backends': load_backends(settings.AUTHENTICATION_BACKENDS),
        })
Example #3
0
    def get(self, request, pk):
        parent = self.get_parent(request, pk)
        config = get_config()
        PAGINATE_COUNT = config.PAGINATE_COUNT
        MAX_PAGE_SIZE = config.MAX_PAGE_SIZE

        try:
            limit = int(request.query_params.get('limit', PAGINATE_COUNT))
        except ValueError:
            limit = PAGINATE_COUNT
        if MAX_PAGE_SIZE:
            limit = min(limit, MAX_PAGE_SIZE)

        # Calculate available IPs within the parent
        ip_list = []
        for index, ip in enumerate(parent.get_available_ips(), start=1):
            ip_list.append(ip)
            if index == limit:
                break
        serializer = serializers.AvailableIPSerializer(ip_list,
                                                       many=True,
                                                       context={
                                                           'request': request,
                                                           'parent': parent,
                                                           'vrf': parent.vrf,
                                                       })

        return Response(serializer.data)
Example #4
0
    def dispatch(self, request, *args, **kwargs):
        config = get_config()

        # Enforce GRAPHQL_ENABLED
        if not config.GRAPHQL_ENABLED:
            return HttpResponseNotFound("The GraphQL API is not enabled.")

        # Attempt to authenticate the user using a DRF token, if provided
        if not request.user.is_authenticated:
            authenticator = TokenAuthentication()
            try:
                auth_info = authenticator.authenticate(request)
                if auth_info is not None:
                    request.user = auth_info[0]  # User object
            except AuthenticationFailed as exc:
                return HttpResponseForbidden(exc.detail)

        # Enforce LOGIN_REQUIRED
        if settings.LOGIN_REQUIRED and not request.user.is_authenticated:

            # If this is a human user, send a redirect to the login page
            if self.request_wants_html(request):
                return redirect_to_login(reverse('graphql'))

            return HttpResponseForbidden("No credentials provided.")

        return super().dispatch(request, *args, **kwargs)
Example #5
0
    def get_elevation_svg(
            self,
            face=DeviceFaceChoices.FACE_FRONT,
            user=None,
            unit_width=None,
            unit_height=None,
            legend_width=RACK_ELEVATION_LEGEND_WIDTH_DEFAULT,
            include_images=True,
            base_url=None
    ):
        """
        Return an SVG of the rack elevation

        :param face: Enum of [front, rear] representing the desired side of the rack elevation to render
        :param user: User instance to be used for evaluating device view permissions. If None, all devices
            will be included.
        :param unit_width: Width in pixels for the rendered drawing
        :param unit_height: Height of each rack unit for the rendered drawing. Note this is not the total
            height of the elevation
        :param legend_width: Width of the unit legend, in pixels
        :param include_images: Embed front/rear device images where available
        :param base_url: Base URL for links and images. If none, URLs will be relative.
        """
        elevation = RackElevationSVG(self, user=user, include_images=include_images, base_url=base_url)
        if unit_width is None or unit_height is None:
            config = get_config()
            unit_width = unit_width or config.RACK_ELEVATION_DEFAULT_UNIT_WIDTH
            unit_height = unit_height or config.RACK_ELEVATION_DEFAULT_UNIT_HEIGHT

        return elevation.render(face, unit_width, unit_height, legend_width)
Example #6
0
def get_paginate_count(request):
    """
    Determine the desired length of a page, using the following in order:

        1. per_page URL query parameter
        2. Saved user preference
        3. PAGINATE_COUNT global setting.

    Return the lesser of the calculated value and MAX_PAGE_SIZE.
    """
    config = get_config()

    def _max_allowed(page_size):
        if config.MAX_PAGE_SIZE:
            return min(page_size, config.MAX_PAGE_SIZE)
        return page_size

    if 'per_page' in request.GET:
        try:
            per_page = int(request.GET.get('per_page'))
            if request.user.is_authenticated:
                request.user.config.set('pagination.per_page',
                                        per_page,
                                        commit=True)
            return _max_allowed(per_page)
        except ValueError:
            pass

    if request.user.is_authenticated:
        per_page = request.user.config.get('pagination.per_page',
                                           config.PAGINATE_COUNT)
        return _max_allowed(per_page)

    return _max_allowed(config.PAGINATE_COUNT)
Example #7
0
def render_markdown(value):
    """
    Render text as Markdown
    """
    schemes = '|'.join(get_config().ALLOWED_URL_SCHEMES)

    # Strip HTML tags
    value = strip_tags(value)

    # Sanitize Markdown links
    pattern = fr'\[([^\]]+)\]\((?!({schemes})).*:(.+)\)'
    value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE)

    # Sanitize Markdown reference links
    pattern = fr'\[(.+)\]:\s*(?!({schemes}))\w*:(.+)'
    value = re.sub(pattern, '[\\1]: \\3', value, flags=re.IGNORECASE)

    # Render Markdown
    html = markdown(
        value, extensions=['fenced_code', 'tables',
                           StrikethroughExtension()])

    # If the string is not empty wrap it in rendered-markdown to style tables
    if html:
        html = f'<div class="rendered-markdown">{html}</div>'

    return mark_safe(html)
Example #8
0
    def __init__(self, object_list, per_page, orphans=None, **kwargs):

        # Determine the page size
        try:
            per_page = int(per_page)
            if per_page < 1:
                per_page = get_config().PAGINATE_COUNT
        except ValueError:
            per_page = get_config().PAGINATE_COUNT

        # Set orphans count based on page size
        if orphans is None and per_page <= 50:
            orphans = 5
        elif orphans is None:
            orphans = 10

        super().__init__(object_list, per_page, orphans=orphans, **kwargs)
Example #9
0
    def test_config_init_empty(self):
        cache.clear()

        config = get_config()
        self.assertEqual(config.config, {})
        self.assertEqual(config.version, None)

        clear_config()
Example #10
0
 def primary_ip(self):
     if get_config().PREFER_IPV4 and self.primary_ip4:
         return self.primary_ip4
     elif self.primary_ip6:
         return self.primary_ip6
     elif self.primary_ip4:
         return self.primary_ip4
     else:
         return None
Example #11
0
    def test_default_page_size(self):
        response = self.client.get(self.url, format='json', **self.header)
        page_size = get_config().PAGINATE_COUNT
        self.assertLess(page_size, 100, "Default page size not sufficient for data set")

        self.assertHttpStatus(response, status.HTTP_200_OK)
        self.assertEqual(response.data['count'], 100)
        self.assertTrue(response.data['next'].endswith(f'?limit={page_size}&offset={page_size}'))
        self.assertIsNone(response.data['previous'])
        self.assertEqual(len(response.data['results']), page_size)
Example #12
0
def settings_and_registry(request):
    """
    Expose Django settings and NetBox registry stores in the template context. Example: {{ settings.DEBUG }}
    """
    return {
        'settings': django_settings,
        'config': get_config(),
        'registry': registry,
        'preferences':
        request.user.config if request.user.is_authenticated else {},
    }
Example #13
0
    def test_config_init_from_db(self):
        CONFIG_DATA = {'BANNER_TOP': 'A'}
        cache.clear()

        # Create a config but don't load it into the cache
        configrevision = ConfigRevision.objects.create(data=CONFIG_DATA)

        config = get_config()
        self.assertEqual(config.config, CONFIG_DATA)
        self.assertEqual(config.version, configrevision.pk)

        clear_config()
Example #14
0
    def test_settings_override(self):
        CONFIG_DATA = {'BANNER_TOP': 'A'}
        cache.clear()

        # Create a config and load it into the cache
        configrevision = ConfigRevision.objects.create(data=CONFIG_DATA)
        configrevision.activate()

        config = get_config()
        self.assertEqual(config.BANNER_TOP, 'Z')
        self.assertEqual(config.version, configrevision.pk)

        clear_config()
Example #15
0
    def clean(self):
        super().clean()

        if self.address:

            # /0 masks are not acceptable
            if self.address.prefixlen == 0:
                raise ValidationError(
                    {'address': "Cannot create IP address with /0 mask."})

            # Enforce unique IP space (if applicable)
            if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (
                    self.vrf and self.vrf.enforce_unique):
                duplicate_ips = self.get_duplicates()
                if duplicate_ips and (
                        self.role not in IPADDRESS_ROLES_NONUNIQUE
                        or any(dip.role not in IPADDRESS_ROLES_NONUNIQUE
                               for dip in duplicate_ips)):
                    raise ValidationError({
                        'address':
                        "Duplicate IP address found in {}: {}".format(
                            "VRF {}".format(self.vrf)
                            if self.vrf else "global table",
                            duplicate_ips.first(),
                        )
                    })

        # Check for primary IP assignment that doesn't match the assigned device/VM
        if self.pk:
            for cls, attr in ((Device, 'device'), (VirtualMachine,
                                                   'virtual_machine')):
                parent = cls.objects.filter(
                    Q(primary_ip4=self) | Q(primary_ip6=self)).first()
                if parent and getattr(self.assigned_object, attr,
                                      None) != parent:
                    # Check for a NAT relationship
                    if not self.nat_inside or getattr(
                            self.nat_inside.assigned_object, attr,
                            None) != parent:
                        raise ValidationError({
                            'interface':
                            f"IP address is primary for {cls._meta.model_name} {parent} but "
                            f"not assigned to it!"
                        })

        # Validate IP status selection
        if self.status == IPAddressStatusChoices.STATUS_SLAAC and self.family != 6:
            raise ValidationError(
                {'status': "Only IPv6 addresses can be assigned SLAAC status"})
Example #16
0
    def get_limit(self, request):
        if self.limit_query_param:
            try:
                limit = int(request.query_params[self.limit_query_param])
                if limit < 0:
                    raise ValueError()
                # Enforce maximum page size, if defined
                MAX_PAGE_SIZE = get_config().MAX_PAGE_SIZE
                if MAX_PAGE_SIZE:
                    return MAX_PAGE_SIZE if limit == 0 else min(limit, MAX_PAGE_SIZE)
                return limit
            except (KeyError, ValueError):
                pass

        return self.default_limit
Example #17
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Append current parameter values to form field help texts and check for static configurations
        config = get_config()
        for param in PARAMS:
            value = getattr(config, param.name)
            is_static = hasattr(settings, param.name)
            if value:
                help_text = self.fields[param.name].help_text
                if help_text:
                    help_text += '<br />'  # Line break
                help_text += f'Current value: <strong>{value}</strong>'
                if is_static:
                    help_text += ' (defined statically)'
                elif value == param.default:
                    help_text += ' (default)'
                self.fields[param.name].help_text = help_text
            if is_static:
                self.fields[param.name].disabled = True
Example #18
0
    def clean(self):
        super().clean()

        if self.prefix:

            # /0 masks are not acceptable
            if self.prefix.prefixlen == 0:
                raise ValidationError(
                    {'prefix': "Cannot create prefix with /0 mask."})

            # Enforce unique IP space (if applicable)
            if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (
                    self.vrf and self.vrf.enforce_unique):
                duplicate_prefixes = self.get_duplicates()
                if duplicate_prefixes:
                    raise ValidationError({
                        'prefix':
                        "Duplicate prefix found in {}: {}".format(
                            "VRF {}".format(self.vrf)
                            if self.vrf else "global table",
                            duplicate_prefixes.first(),
                        )
                    })
Example #19
0
    def napalm(self, request, pk):
        """
        Execute a NAPALM method on a Device
        """
        device = get_object_or_404(self.queryset, pk=pk)
        if not device.primary_ip:
            raise ServiceUnavailable(
                "This device does not have a primary IP address configured.")
        if device.platform is None:
            raise ServiceUnavailable(
                "No platform is configured for this device.")
        if not device.platform.napalm_driver:
            raise ServiceUnavailable(
                f"No NAPALM driver is configured for this device's platform: {device.platform}."
            )

        # Check for primary IP address from NetBox object
        if device.primary_ip:
            host = str(device.primary_ip.address.ip)
        else:
            # Raise exception for no IP address and no Name if device.name does not exist
            if not device.name:
                raise ServiceUnavailable(
                    "This device does not have a primary IP address or device name to lookup configured."
                )
            try:
                # Attempt to complete a DNS name resolution if no primary_ip is set
                host = socket.gethostbyname(device.name)
            except socket.gaierror:
                # Name lookup failure
                raise ServiceUnavailable(
                    f"Name lookup failure, unable to resolve IP address for {device.name}. Please set Primary IP or "
                    f"setup name resolution.")

        # Check that NAPALM is installed
        try:
            import napalm
            from napalm.base.exceptions import ModuleImportError
        except ModuleNotFoundError as e:
            if getattr(e, 'name') == 'napalm':
                raise ServiceUnavailable(
                    "NAPALM is not installed. Please see the documentation for instructions."
                )
            raise e

        # Validate the configured driver
        try:
            driver = napalm.get_network_driver(device.platform.napalm_driver)
        except ModuleImportError:
            raise ServiceUnavailable(
                "NAPALM driver for platform {} not found: {}.".format(
                    device.platform, device.platform.napalm_driver))

        # Verify user permission
        if not request.user.has_perm('dcim.napalm_read_device'):
            return HttpResponseForbidden()

        napalm_methods = request.GET.getlist('method')
        response = OrderedDict([(m, None) for m in napalm_methods])

        config = get_config()
        username = config.NAPALM_USERNAME
        password = config.NAPALM_PASSWORD
        timeout = config.NAPALM_TIMEOUT
        optional_args = config.NAPALM_ARGS.copy()
        if device.platform.napalm_args is not None:
            optional_args.update(device.platform.napalm_args)

        # Update NAPALM parameters according to the request headers
        for header in request.headers:
            if header[:9].lower() != 'x-napalm-':
                continue

            key = header[9:]
            if key.lower() == 'username':
                username = request.headers[header]
            elif key.lower() == 'password':
                password = request.headers[header]
            elif key:
                optional_args[key.lower()] = request.headers[header]

        # Connect to the device
        d = driver(hostname=host,
                   username=username,
                   password=password,
                   timeout=timeout,
                   optional_args=optional_args)
        try:
            d.open()
        except Exception as e:
            raise ServiceUnavailable(
                "Error connecting to the device at {}: {}".format(host, e))

        # Validate and execute each specified NAPALM method
        for method in napalm_methods:
            if not hasattr(driver, method):
                response[method] = {'error': 'Unknown NAPALM method'}
                continue
            if not method.startswith('get_'):
                response[method] = {
                    'error': 'Only get_* NAPALM methods are supported'
                }
                continue
            try:
                response[method] = getattr(d, method)()
            except NotImplementedError:
                response[method] = {
                    'error':
                    'Method {} not implemented for NAPALM driver {}'.format(
                        method, driver)
                }
            except Exception as e:
                response[method] = {
                    'error': 'Method {} failed: {}'.format(method, e)
                }
        d.close()

        return Response(response)
Example #20
0
 def __call__(self, value):
     if self.schemes is None:
         # We can't load the allowed schemes until the configuration has been initialized
         self.schemes = get_config().ALLOWED_URL_SCHEMES
     return super().__call__(value)
Example #21
0
 def __init__(self):
     self.default_limit = get_config().PAGINATE_COUNT