mirror of
https://github.com/certbot/certbot.git
synced 2026-08-02 00:22:28 +02:00
Fully type certbot-nginx module (#9124)
* Work in progress * Fix type * Work in progress * Work in progress * Work in progress * Work in progress * Work in progress * Oups. * Fix typing in UnspacedList * Fix logic * Finish typing * List certbot-nginx as fully typed in tox * Fix lint * Fix checks * Organize imports * Fix typing for Python 3.6 * Fix checks * Fix lint * Update certbot-nginx/certbot_nginx/_internal/configurator.py Co-authored-by: alexzorin <alex@zor.io> * Update certbot-nginx/certbot_nginx/_internal/configurator.py Co-authored-by: alexzorin <alex@zor.io> * Fix signature of deploy_cert regarding the installer interface * Update certbot-nginx/certbot_nginx/_internal/obj.py Co-authored-by: alexzorin <alex@zor.io> * Fix types * Update certbot-nginx/certbot_nginx/_internal/parser.py Co-authored-by: alexzorin <alex@zor.io> * Precise type * Precise _coerce possible inputs/outputs * Fix type * Update certbot-nginx/certbot_nginx/_internal/http_01.py Co-authored-by: ohemorange <ebportnoy@gmail.com> * Fix type * Remove an undesirable implementation. * Fix type Co-authored-by: alexzorin <alex@zor.io> Co-authored-by: ohemorange <ebportnoy@gmail.com>
This commit is contained in:
co-authored by
alexzorin
ohemorange
parent
30b066f082
commit
16aad35d31
@@ -964,7 +964,9 @@ class ApacheConfigurator(common.Configurator):
|
|||||||
logger.warning("Encountered a problem while parsing file: %s, skipping", path)
|
logger.warning("Encountered a problem while parsing file: %s, skipping", path)
|
||||||
return None
|
return None
|
||||||
for arg in args:
|
for arg in args:
|
||||||
addrs.add(obj.Addr.fromstring(self.parser.get_arg(arg)))
|
addr = obj.Addr.fromstring(self.parser.get_arg(arg))
|
||||||
|
if addr:
|
||||||
|
addrs.add(addr)
|
||||||
is_ssl = False
|
is_ssl = False
|
||||||
|
|
||||||
if self.parser.find_dir("SSLEngine", "on", start=path, exclude=False):
|
if self.parser.find_dir("SSLEngine", "on", start=path, exclude=False):
|
||||||
@@ -1094,7 +1096,9 @@ class ApacheConfigurator(common.Configurator):
|
|||||||
"""
|
"""
|
||||||
addrs = set()
|
addrs = set()
|
||||||
for param in node.parameters:
|
for param in node.parameters:
|
||||||
addrs.add(obj.Addr.fromstring(param))
|
addr = obj.Addr.fromstring(param)
|
||||||
|
if addr:
|
||||||
|
addrs.add(addr)
|
||||||
|
|
||||||
is_ssl = False
|
is_ssl = False
|
||||||
# Exclusion to match the behavior in get_virtual_hosts_v2
|
# Exclusion to match the behavior in get_virtual_hosts_v2
|
||||||
@@ -1647,9 +1651,10 @@ class ApacheConfigurator(common.Configurator):
|
|||||||
for addr in ssl_addr_p:
|
for addr in ssl_addr_p:
|
||||||
old_addr = obj.Addr.fromstring(
|
old_addr = obj.Addr.fromstring(
|
||||||
str(self.parser.get_arg(addr)))
|
str(self.parser.get_arg(addr)))
|
||||||
ssl_addr = old_addr.get_addr_obj("443")
|
if old_addr:
|
||||||
self.parser.aug.set(addr, str(ssl_addr))
|
ssl_addr = old_addr.get_addr_obj("443")
|
||||||
ssl_addrs.add(ssl_addr)
|
self.parser.aug.set(addr, str(ssl_addr))
|
||||||
|
ssl_addrs.add(ssl_addr)
|
||||||
|
|
||||||
return ssl_addrs
|
return ssl_addrs
|
||||||
|
|
||||||
|
|||||||
@@ -6,30 +6,38 @@ import socket
|
|||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
from typing import Any
|
||||||
|
from typing import Callable
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
from typing import Iterable
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Mapping
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from typing import Text
|
from typing import Text
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
from typing import Type
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import OpenSSL
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
from acme import challenges
|
|
||||||
from acme import crypto_util as acme_crypto_util
|
|
||||||
from certbot import crypto_util
|
|
||||||
from certbot import errors
|
|
||||||
from certbot import util
|
|
||||||
from certbot.display import util as display_util
|
|
||||||
from certbot.compat import os
|
|
||||||
from certbot.plugins import common
|
|
||||||
from certbot_nginx._internal import constants
|
from certbot_nginx._internal import constants
|
||||||
from certbot_nginx._internal import display_ops
|
from certbot_nginx._internal import display_ops
|
||||||
from certbot_nginx._internal import http_01
|
from certbot_nginx._internal import http_01
|
||||||
from certbot_nginx._internal import nginxparser
|
from certbot_nginx._internal import nginxparser
|
||||||
from certbot_nginx._internal import obj
|
from certbot_nginx._internal import obj
|
||||||
from certbot_nginx._internal import parser
|
from certbot_nginx._internal import parser
|
||||||
|
import OpenSSL
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
from acme import challenges
|
||||||
|
from acme import crypto_util as acme_crypto_util
|
||||||
|
from certbot import achallenges
|
||||||
|
from certbot import crypto_util
|
||||||
|
from certbot import errors
|
||||||
|
from certbot import util
|
||||||
|
from certbot.compat import os
|
||||||
|
from certbot.display import util as display_util
|
||||||
|
from certbot.plugins import common
|
||||||
|
|
||||||
NAME_RANK = 0
|
NAME_RANK = 0
|
||||||
START_WILDCARD_RANK = 1
|
START_WILDCARD_RANK = 1
|
||||||
@@ -70,7 +78,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
SSL_DIRECTIVES = ['ssl_certificate', 'ssl_certificate_key', 'ssl_dhparam']
|
SSL_DIRECTIVES = ['ssl_certificate', 'ssl_certificate_key', 'ssl_dhparam']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_parser_arguments(cls, add):
|
def add_parser_arguments(cls, add: Callable[..., None]) -> None:
|
||||||
default_server_root = _determine_default_server_root()
|
default_server_root = _determine_default_server_root()
|
||||||
add("server-root", default=constants.CLI_DEFAULTS["server_root"],
|
add("server-root", default=constants.CLI_DEFAULTS["server_root"],
|
||||||
help="Nginx server root directory. (default: %s)" % default_server_root)
|
help="Nginx server root directory. (default: %s)" % default_server_root)
|
||||||
@@ -82,11 +90,11 @@ class NginxConfigurator(common.Configurator):
|
|||||||
"to apply when reloading.")
|
"to apply when reloading.")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def nginx_conf(self):
|
def nginx_conf(self) -> str:
|
||||||
"""Nginx config file path."""
|
"""Nginx config file path."""
|
||||||
return os.path.join(self.conf("server_root"), "nginx.conf")
|
return os.path.join(self.conf("server_root"), "nginx.conf")
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""Initialize an Nginx Configurator.
|
"""Initialize an Nginx Configurator.
|
||||||
|
|
||||||
:param tup version: version of Nginx as a tuple (1, 4, 7)
|
:param tup version: version of Nginx as a tuple (1, 4, 7)
|
||||||
@@ -125,7 +133,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
self.parser: parser.NginxParser
|
self.parser: parser.NginxParser
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mod_ssl_conf_src(self):
|
def mod_ssl_conf_src(self) -> str:
|
||||||
"""Full absolute path to SSL configuration file source."""
|
"""Full absolute path to SSL configuration file source."""
|
||||||
|
|
||||||
# Why all this complexity? Well, we want to support Mozilla's intermediate
|
# Why all this complexity? Well, we want to support Mozilla's intermediate
|
||||||
@@ -159,22 +167,23 @@ class NginxConfigurator(common.Configurator):
|
|||||||
"certbot_nginx", os.path.join("_internal", "tls_configs", config_filename))
|
"certbot_nginx", os.path.join("_internal", "tls_configs", config_filename))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mod_ssl_conf(self):
|
def mod_ssl_conf(self) -> str:
|
||||||
"""Full absolute path to SSL configuration file."""
|
"""Full absolute path to SSL configuration file."""
|
||||||
return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST)
|
return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def updated_mod_ssl_conf_digest(self):
|
def updated_mod_ssl_conf_digest(self) -> str:
|
||||||
"""Full absolute path to digest of updated SSL configuration file."""
|
"""Full absolute path to digest of updated SSL configuration file."""
|
||||||
return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST)
|
return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST)
|
||||||
|
|
||||||
def install_ssl_options_conf(self, options_ssl, options_ssl_digest):
|
def install_ssl_options_conf(self, options_ssl: str, options_ssl_digest: str) -> None:
|
||||||
"""Copy Certbot's SSL options file into the system's config dir if required."""
|
"""Copy Certbot's SSL options file into the system's config dir if required."""
|
||||||
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
|
common.install_version_controlled_file(
|
||||||
|
options_ssl, options_ssl_digest,
|
||||||
self.mod_ssl_conf_src, constants.ALL_SSL_OPTIONS_HASHES)
|
self.mod_ssl_conf_src, constants.ALL_SSL_OPTIONS_HASHES)
|
||||||
|
|
||||||
# This is called in determine_authenticator and determine_installer
|
# This is called in determine_authenticator and determine_installer
|
||||||
def prepare(self):
|
def prepare(self) -> None:
|
||||||
"""Prepare the authenticator/installer.
|
"""Prepare the authenticator/installer.
|
||||||
|
|
||||||
:raises .errors.NoInstallationError: If Nginx ctl cannot be found
|
:raises .errors.NoInstallationError: If Nginx ctl cannot be found
|
||||||
@@ -210,8 +219,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
raise errors.PluginError('Unable to lock {0}'.format(self.conf('server-root')))
|
raise errors.PluginError('Unable to lock {0}'.format(self.conf('server-root')))
|
||||||
|
|
||||||
# Entry point in main.py for installing cert
|
# Entry point in main.py for installing cert
|
||||||
def deploy_cert(self, domain, cert_path, key_path,
|
def deploy_cert(self, domain: str, cert_path: str, key_path: str, chain_path: str,
|
||||||
chain_path=None, fullchain_path=None):
|
fullchain_path: str) -> None:
|
||||||
"""Deploys certificate to specified virtual host.
|
"""Deploys certificate to specified virtual host.
|
||||||
|
|
||||||
.. note:: Aborts if the vhost is missing ssl_certificate or
|
.. note:: Aborts if the vhost is missing ssl_certificate or
|
||||||
@@ -234,7 +243,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
display_util.notify("Successfully deployed certificate for {} to {}"
|
display_util.notify("Successfully deployed certificate for {} to {}"
|
||||||
.format(domain, vhost.filep))
|
.format(domain, vhost.filep))
|
||||||
|
|
||||||
def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path): # pylint: disable=unused-argument
|
def _deploy_cert(self, vhost: obj.VirtualHost, _cert_path: str, key_path: str,
|
||||||
|
_chain_path: str, fullchain_path: str) -> None:
|
||||||
"""
|
"""
|
||||||
Helper function for deploy_cert() that handles the actual deployment
|
Helper function for deploy_cert() that handles the actual deployment
|
||||||
this exists because we might want to do multiple deployments per
|
this exists because we might want to do multiple deployments per
|
||||||
@@ -244,8 +254,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
cert_directives = [['\n ', 'ssl_certificate', ' ', fullchain_path],
|
cert_directives = [['\n ', 'ssl_certificate', ' ', fullchain_path],
|
||||||
['\n ', 'ssl_certificate_key', ' ', key_path]]
|
['\n ', 'ssl_certificate_key', ' ', key_path]]
|
||||||
|
|
||||||
self.parser.update_or_add_server_directives(vhost,
|
self.parser.update_or_add_server_directives(vhost, cert_directives)
|
||||||
cert_directives)
|
|
||||||
logger.info("Deploying Certificate to VirtualHost %s", vhost.filep)
|
logger.info("Deploying Certificate to VirtualHost %s", vhost.filep)
|
||||||
|
|
||||||
self.save_notes += ("Changed vhost at %s with addresses of %s\n" %
|
self.save_notes += ("Changed vhost at %s with addresses of %s\n" %
|
||||||
@@ -254,7 +263,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
self.save_notes += "\tssl_certificate %s\n" % fullchain_path
|
self.save_notes += "\tssl_certificate %s\n" % fullchain_path
|
||||||
self.save_notes += "\tssl_certificate_key %s\n" % key_path
|
self.save_notes += "\tssl_certificate_key %s\n" % key_path
|
||||||
|
|
||||||
def _choose_vhosts_wildcard(self, domain, prefer_ssl, no_ssl_filter_port=None):
|
def _choose_vhosts_wildcard(self, domain: str, prefer_ssl: bool,
|
||||||
|
no_ssl_filter_port: Optional[str] = None) -> List[obj.VirtualHost]:
|
||||||
"""Prompts user to choose vhosts to install a wildcard certificate for"""
|
"""Prompts user to choose vhosts to install a wildcard certificate for"""
|
||||||
if prefer_ssl:
|
if prefer_ssl:
|
||||||
vhosts_cache = self._wildcard_vhosts
|
vhosts_cache = self._wildcard_vhosts
|
||||||
@@ -303,12 +313,13 @@ class NginxConfigurator(common.Configurator):
|
|||||||
#######################
|
#######################
|
||||||
# Vhost parsing methods
|
# Vhost parsing methods
|
||||||
#######################
|
#######################
|
||||||
def _choose_vhost_single(self, target_name):
|
def _choose_vhost_single(self, target_name: str) -> List[obj.VirtualHost]:
|
||||||
matches = self._get_ranked_matches(target_name)
|
matches = self._get_ranked_matches(target_name)
|
||||||
vhosts = [x for x in [self._select_best_name_match(matches)] if x is not None]
|
vhosts = [x for x in [self._select_best_name_match(matches)] if x is not None]
|
||||||
return vhosts
|
return vhosts
|
||||||
|
|
||||||
def choose_vhosts(self, target_name, create_if_no_match=False):
|
def choose_vhosts(self, target_name: str,
|
||||||
|
create_if_no_match: bool = False) -> List[obj.VirtualHost]:
|
||||||
"""Chooses a virtual host based on the given domain name.
|
"""Chooses a virtual host based on the given domain name.
|
||||||
|
|
||||||
.. note:: This makes the vhost SSL-enabled if it isn't already. Follows
|
.. note:: This makes the vhost SSL-enabled if it isn't already. Follows
|
||||||
@@ -352,7 +363,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
|
|
||||||
return vhosts
|
return vhosts
|
||||||
|
|
||||||
def ipv6_info(self, port):
|
def ipv6_info(self, port: str) -> Tuple[bool, bool]:
|
||||||
"""Returns tuple of booleans (ipv6_active, ipv6only_present)
|
"""Returns tuple of booleans (ipv6_active, ipv6only_present)
|
||||||
ipv6_active is true if any server block listens ipv6 address in any port
|
ipv6_active is true if any server block listens ipv6 address in any port
|
||||||
|
|
||||||
@@ -365,9 +376,6 @@ class NginxConfigurator(common.Configurator):
|
|||||||
configuration, and existence of ipv6only directive for specified port
|
configuration, and existence of ipv6only directive for specified port
|
||||||
:rtype: tuple of type (bool, bool)
|
:rtype: tuple of type (bool, bool)
|
||||||
"""
|
"""
|
||||||
# port should be a string, but it's easy to mess up, so let's
|
|
||||||
# make sure it is one
|
|
||||||
port = str(port)
|
|
||||||
vhosts = self.parser.get_vhosts()
|
vhosts = self.parser.get_vhosts()
|
||||||
ipv6_active = False
|
ipv6_active = False
|
||||||
ipv6only_present = False
|
ipv6only_present = False
|
||||||
@@ -377,10 +385,10 @@ class NginxConfigurator(common.Configurator):
|
|||||||
ipv6_active = True
|
ipv6_active = True
|
||||||
if addr.ipv6only and addr.get_port() == port:
|
if addr.ipv6only and addr.get_port() == port:
|
||||||
ipv6only_present = True
|
ipv6only_present = True
|
||||||
return (ipv6_active, ipv6only_present)
|
return ipv6_active, ipv6only_present
|
||||||
|
|
||||||
def _vhost_from_duplicated_default(self, domain: str, allow_port_mismatch: bool, port: str
|
def _vhost_from_duplicated_default(self, domain: str, allow_port_mismatch: bool,
|
||||||
) -> obj.VirtualHost:
|
port: str) -> obj.VirtualHost:
|
||||||
"""if allow_port_mismatch is False, only server blocks with matching ports will be
|
"""if allow_port_mismatch is False, only server blocks with matching ports will be
|
||||||
used as a default server block template.
|
used as a default server block template.
|
||||||
"""
|
"""
|
||||||
@@ -395,7 +403,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
self._add_server_name_to_vhost(self.new_vhost, domain)
|
self._add_server_name_to_vhost(self.new_vhost, domain)
|
||||||
return self.new_vhost
|
return self.new_vhost
|
||||||
|
|
||||||
def _add_server_name_to_vhost(self, vhost, domain):
|
def _add_server_name_to_vhost(self, vhost: obj.VirtualHost, domain: str) -> None:
|
||||||
vhost.names.add(domain)
|
vhost.names.add(domain)
|
||||||
name_block = [['\n ', 'server_name']]
|
name_block = [['\n ', 'server_name']]
|
||||||
for name in vhost.names:
|
for name in vhost.names:
|
||||||
@@ -403,7 +411,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
name_block[0].append(name)
|
name_block[0].append(name)
|
||||||
self.parser.update_or_add_server_directives(vhost, name_block)
|
self.parser.update_or_add_server_directives(vhost, name_block)
|
||||||
|
|
||||||
def _get_default_vhost(self, domain, allow_port_mismatch, port):
|
def _get_default_vhost(self, domain: str, allow_port_mismatch: bool,
|
||||||
|
port: str) -> obj.VirtualHost:
|
||||||
"""Helper method for _vhost_from_duplicated_default; see argument documentation there"""
|
"""Helper method for _vhost_from_duplicated_default; see argument documentation there"""
|
||||||
vhost_list = self.parser.get_vhosts()
|
vhost_list = self.parser.get_vhosts()
|
||||||
# if one has default_server set, return that one
|
# if one has default_server set, return that one
|
||||||
@@ -424,10 +433,11 @@ class NginxConfigurator(common.Configurator):
|
|||||||
|
|
||||||
# TODO: present a list of vhosts for user to choose from
|
# TODO: present a list of vhosts for user to choose from
|
||||||
|
|
||||||
raise errors.MisconfigurationError("Could not automatically find a matching server"
|
raise errors.MisconfigurationError("Could not automatically find a matching server "
|
||||||
" block for %s. Set the `server_name` directive to use the Nginx installer." % domain)
|
f"block for {domain}. Set the `server_name` directive "
|
||||||
|
"to use the Nginx installer.")
|
||||||
|
|
||||||
def _get_ranked_matches(self, target_name):
|
def _get_ranked_matches(self, target_name: str) -> List[Dict[str, Any]]:
|
||||||
"""Returns a ranked list of vhosts that match target_name.
|
"""Returns a ranked list of vhosts that match target_name.
|
||||||
The ranking gives preference to SSL vhosts.
|
The ranking gives preference to SSL vhosts.
|
||||||
|
|
||||||
@@ -440,7 +450,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
vhost_list = self.parser.get_vhosts()
|
vhost_list = self.parser.get_vhosts()
|
||||||
return self._rank_matches_by_name_and_ssl(vhost_list, target_name)
|
return self._rank_matches_by_name_and_ssl(vhost_list, target_name)
|
||||||
|
|
||||||
def _select_best_name_match(self, matches):
|
def _select_best_name_match(self,
|
||||||
|
matches: Sequence[Mapping[str, Any]]) -> Optional[obj.VirtualHost]:
|
||||||
"""Returns the best name match of a ranked list of vhosts.
|
"""Returns the best name match of a ranked list of vhosts.
|
||||||
|
|
||||||
:param list matches: list of dicts containing the vhost, the matching name,
|
:param list matches: list of dicts containing the vhost, the matching name,
|
||||||
@@ -460,7 +471,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
# Exact or regex match
|
# Exact or regex match
|
||||||
return matches[0]['vhost']
|
return matches[0]['vhost']
|
||||||
|
|
||||||
def _rank_matches_by_name(self, vhost_list, target_name):
|
def _rank_matches_by_name(self, vhost_list: Iterable[obj.VirtualHost],
|
||||||
|
target_name: str) -> List[Dict[str, Any]]:
|
||||||
"""Returns a ranked list of vhosts from vhost_list that match target_name.
|
"""Returns a ranked list of vhosts from vhost_list that match target_name.
|
||||||
This method should always be followed by a call to _select_best_name_match.
|
This method should always be followed by a call to _select_best_name_match.
|
||||||
|
|
||||||
@@ -497,7 +509,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
'rank': REGEX_RANK})
|
'rank': REGEX_RANK})
|
||||||
return sorted(matches, key=lambda x: x['rank'])
|
return sorted(matches, key=lambda x: x['rank'])
|
||||||
|
|
||||||
def _rank_matches_by_name_and_ssl(self, vhost_list, target_name):
|
def _rank_matches_by_name_and_ssl(self, vhost_list: Iterable[obj.VirtualHost],
|
||||||
|
target_name: str) -> List[Dict[str, Any]]:
|
||||||
"""Returns a ranked list of vhosts from vhost_list that match target_name.
|
"""Returns a ranked list of vhosts from vhost_list that match target_name.
|
||||||
The ranking gives preference to SSLishness before name match level.
|
The ranking gives preference to SSLishness before name match level.
|
||||||
|
|
||||||
@@ -610,7 +623,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
def _vhost_listening_on_port_no_ssl(self, vhost: obj.VirtualHost, port: str) -> bool:
|
def _vhost_listening_on_port_no_ssl(self, vhost: obj.VirtualHost, port: str) -> bool:
|
||||||
return self._vhost_listening(vhost, port, False)
|
return self._vhost_listening(vhost, port, False)
|
||||||
|
|
||||||
def _get_redirect_ranked_matches(self, target_name, port):
|
def _get_redirect_ranked_matches(self, target_name: str, port: str) -> List[Dict[str, Any]]:
|
||||||
"""Gets a ranked list of plaintextish port-listening vhosts matching target_name
|
"""Gets a ranked list of plaintextish port-listening vhosts matching target_name
|
||||||
|
|
||||||
Filter all hosts for those listening on port without using ssl.
|
Filter all hosts for those listening on port without using ssl.
|
||||||
@@ -625,14 +638,14 @@ class NginxConfigurator(common.Configurator):
|
|||||||
"""
|
"""
|
||||||
all_vhosts = self.parser.get_vhosts()
|
all_vhosts = self.parser.get_vhosts()
|
||||||
|
|
||||||
def _vhost_matches(vhost, port):
|
def _vhost_matches(vhost: obj.VirtualHost, port: str) -> bool:
|
||||||
return self._vhost_listening_on_port_no_ssl(vhost, port)
|
return self._vhost_listening_on_port_no_ssl(vhost, port)
|
||||||
|
|
||||||
matching_vhosts = [vhost for vhost in all_vhosts if _vhost_matches(vhost, port)]
|
matching_vhosts = [vhost for vhost in all_vhosts if _vhost_matches(vhost, port)]
|
||||||
|
|
||||||
return self._rank_matches_by_name(matching_vhosts, target_name)
|
return self._rank_matches_by_name(matching_vhosts, target_name)
|
||||||
|
|
||||||
def get_all_names(self):
|
def get_all_names(self) -> Set[str]:
|
||||||
"""Returns all names found in the Nginx Configuration.
|
"""Returns all names found in the Nginx Configuration.
|
||||||
|
|
||||||
:returns: All ServerNames, ServerAliases, and reverse DNS entries for
|
:returns: All ServerNames, ServerAliases, and reverse DNS entries for
|
||||||
@@ -670,7 +683,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
|
|
||||||
return util.get_filtered_names(all_names)
|
return util.get_filtered_names(all_names)
|
||||||
|
|
||||||
def _get_snakeoil_paths(self):
|
def _get_snakeoil_paths(self) -> Tuple[str, str]:
|
||||||
"""Generate invalid certs that let us create ssl directives for Nginx"""
|
"""Generate invalid certs that let us create ssl directives for Nginx"""
|
||||||
# TODO: generate only once
|
# TODO: generate only once
|
||||||
tmp_dir = os.path.join(self.config.work_dir, "snakeoil")
|
tmp_dir = os.path.join(self.config.work_dir, "snakeoil")
|
||||||
@@ -688,7 +701,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
cert_file.write(cert_pem)
|
cert_file.write(cert_pem)
|
||||||
return cert_path, le_key.file
|
return cert_path, le_key.file
|
||||||
|
|
||||||
def _make_server_ssl(self, vhost):
|
def _make_server_ssl(self, vhost: obj.VirtualHost) -> None:
|
||||||
"""Make a server SSL.
|
"""Make a server SSL.
|
||||||
|
|
||||||
Make a server SSL by adding new listen and SSL directives.
|
Make a server SSL by adding new listen and SSL directives.
|
||||||
@@ -698,7 +711,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
https_port = self.config.https_port
|
https_port = self.config.https_port
|
||||||
ipv6info = self.ipv6_info(https_port)
|
ipv6info = self.ipv6_info(str(https_port))
|
||||||
ipv6_block = ['']
|
ipv6_block = ['']
|
||||||
ipv4_block = ['']
|
ipv4_block = ['']
|
||||||
|
|
||||||
@@ -745,11 +758,12 @@ class NginxConfigurator(common.Configurator):
|
|||||||
##################################
|
##################################
|
||||||
# enhancement methods (Installer)
|
# enhancement methods (Installer)
|
||||||
##################################
|
##################################
|
||||||
def supported_enhancements(self):
|
def supported_enhancements(self) -> List[str]:
|
||||||
"""Returns currently supported enhancements."""
|
"""Returns currently supported enhancements."""
|
||||||
return ['redirect', 'ensure-http-header', 'staple-ocsp']
|
return ['redirect', 'ensure-http-header', 'staple-ocsp']
|
||||||
|
|
||||||
def enhance(self, domain, enhancement, options=None):
|
def enhance(self, domain: str, enhancement: str,
|
||||||
|
options: Optional[Union[str, List[str]]] = None) -> None:
|
||||||
"""Enhance configuration.
|
"""Enhance configuration.
|
||||||
|
|
||||||
:param str domain: domain to enhance
|
:param str domain: domain to enhance
|
||||||
@@ -761,16 +775,16 @@ class NginxConfigurator(common.Configurator):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self._enhance_func[enhancement](domain, options)
|
self._enhance_func[enhancement](domain, options)
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
raise errors.PluginError(
|
raise errors.PluginError(
|
||||||
"Unsupported enhancement: {0}".format(enhancement))
|
"Unsupported enhancement: {0}".format(enhancement))
|
||||||
|
|
||||||
def _has_certbot_redirect(self, vhost, domain):
|
def _has_certbot_redirect(self, vhost: obj.VirtualHost, domain: str) -> bool:
|
||||||
test_redirect_block = _test_block_from_block(_redirect_block_for_domain(domain))
|
test_redirect_block = _test_block_from_block(_redirect_block_for_domain(domain))
|
||||||
return vhost.contains_list(test_redirect_block)
|
return vhost.contains_list(test_redirect_block)
|
||||||
|
|
||||||
def _set_http_header(self, domain, header_substring):
|
def _set_http_header(self, domain: str, header_substring: Union[str, List[str], None]) -> None:
|
||||||
"""Enables header identified by header_substring on domain.
|
"""Enables header identified by header_substring on domain.
|
||||||
|
|
||||||
If the vhost is listening plaintextishly, separates out the relevant
|
If the vhost is listening plaintextishly, separates out the relevant
|
||||||
@@ -784,7 +798,10 @@ class NginxConfigurator(common.Configurator):
|
|||||||
:raises .errors.PluginError: If no viable HTTPS host can be created or
|
:raises .errors.PluginError: If no viable HTTPS host can be created or
|
||||||
set with header header_substring.
|
set with header header_substring.
|
||||||
"""
|
"""
|
||||||
if not header_substring in constants.HEADER_ARGS:
|
if not isinstance(header_substring, str):
|
||||||
|
raise errors.NotSupportedError("Invalid header_substring type "
|
||||||
|
f"{type(header_substring)}, expected a str.")
|
||||||
|
if header_substring not in constants.HEADER_ARGS:
|
||||||
raise errors.NotSupportedError(
|
raise errors.NotSupportedError(
|
||||||
f"{header_substring} is not supported by the nginx plugin.")
|
f"{header_substring} is not supported by the nginx plugin.")
|
||||||
|
|
||||||
@@ -808,7 +825,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
['\n']]
|
['\n']]
|
||||||
self.parser.add_server_directives(vhost, header_directives)
|
self.parser.add_server_directives(vhost, header_directives)
|
||||||
|
|
||||||
def _add_redirect_block(self, vhost, domain):
|
def _add_redirect_block(self, vhost: obj.VirtualHost, domain: str) -> None:
|
||||||
"""Add redirect directive to vhost
|
"""Add redirect directive to vhost
|
||||||
"""
|
"""
|
||||||
redirect_block = _redirect_block_for_domain(domain)
|
redirect_block = _redirect_block_for_domain(domain)
|
||||||
@@ -816,7 +833,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
self.parser.add_server_directives(
|
self.parser.add_server_directives(
|
||||||
vhost, redirect_block, insert_at_top=True)
|
vhost, redirect_block, insert_at_top=True)
|
||||||
|
|
||||||
def _split_block(self, vhost, only_directives=None):
|
def _split_block(self, vhost: obj.VirtualHost, only_directives: Optional[List[str]] = None
|
||||||
|
) -> Tuple[obj.VirtualHost, obj.VirtualHost]:
|
||||||
"""Splits this "virtual host" (i.e. this nginx server block) into
|
"""Splits this "virtual host" (i.e. this nginx server block) into
|
||||||
separate HTTP and HTTPS blocks.
|
separate HTTP and HTTPS blocks.
|
||||||
|
|
||||||
@@ -829,13 +847,13 @@ class NginxConfigurator(common.Configurator):
|
|||||||
"""
|
"""
|
||||||
http_vhost = self.parser.duplicate_vhost(vhost, only_directives=only_directives)
|
http_vhost = self.parser.duplicate_vhost(vhost, only_directives=only_directives)
|
||||||
|
|
||||||
def _ssl_match_func(directive):
|
def _ssl_match_func(directive: str) -> bool:
|
||||||
return 'ssl' in directive
|
return 'ssl' in directive
|
||||||
|
|
||||||
def _ssl_config_match_func(directive):
|
def _ssl_config_match_func(directive: str) -> bool:
|
||||||
return self.mod_ssl_conf in directive
|
return self.mod_ssl_conf in directive
|
||||||
|
|
||||||
def _no_ssl_match_func(directive):
|
def _no_ssl_match_func(directive: str) -> bool:
|
||||||
return 'ssl' not in directive
|
return 'ssl' not in directive
|
||||||
|
|
||||||
# remove all ssl addresses and related directives from the new block
|
# remove all ssl addresses and related directives from the new block
|
||||||
@@ -849,7 +867,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
self.parser.remove_server_directives(vhost, 'listen', match_func=_no_ssl_match_func)
|
self.parser.remove_server_directives(vhost, 'listen', match_func=_no_ssl_match_func)
|
||||||
return http_vhost, vhost
|
return http_vhost, vhost
|
||||||
|
|
||||||
def _enable_redirect(self, domain, unused_options):
|
def _enable_redirect(self, domain: str,
|
||||||
|
unused_options: Optional[Union[str, List[str]]]) -> None:
|
||||||
"""Redirect all equivalent HTTP traffic to ssl_vhost.
|
"""Redirect all equivalent HTTP traffic to ssl_vhost.
|
||||||
|
|
||||||
If the vhost is listening plaintextishly, separate out the
|
If the vhost is listening plaintextishly, separate out the
|
||||||
@@ -876,7 +895,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
for vhost in vhosts:
|
for vhost in vhosts:
|
||||||
self._enable_redirect_single(domain, vhost)
|
self._enable_redirect_single(domain, vhost)
|
||||||
|
|
||||||
def _enable_redirect_single(self, domain, vhost):
|
def _enable_redirect_single(self, domain: str, vhost: obj.VirtualHost) -> None:
|
||||||
"""Redirect all equivalent HTTP traffic to ssl_vhost.
|
"""Redirect all equivalent HTTP traffic to ssl_vhost.
|
||||||
|
|
||||||
If the vhost is listening plaintextishly, separate out the
|
If the vhost is listening plaintextishly, separate out the
|
||||||
@@ -905,7 +924,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
logger.info("Redirecting all traffic on port %s to ssl in %s",
|
logger.info("Redirecting all traffic on port %s to ssl in %s",
|
||||||
self.DEFAULT_LISTEN_PORT, vhost.filep)
|
self.DEFAULT_LISTEN_PORT, vhost.filep)
|
||||||
|
|
||||||
def _enable_ocsp_stapling(self, domain, chain_path):
|
def _enable_ocsp_stapling(self, domain: str,
|
||||||
|
chain_path: Optional[Union[str, List[str]]]) -> None:
|
||||||
"""Include OCSP response in TLS handshake
|
"""Include OCSP response in TLS handshake
|
||||||
|
|
||||||
:param str domain: domain to enable OCSP response for
|
:param str domain: domain to enable OCSP response for
|
||||||
@@ -913,11 +933,15 @@ class NginxConfigurator(common.Configurator):
|
|||||||
:type chain_path: `str` or `None`
|
:type chain_path: `str` or `None`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not isinstance(chain_path, str) and chain_path is not None:
|
||||||
|
raise errors.NotSupportedError(f"Invalid chain_path type {type(chain_path)}, "
|
||||||
|
"expected a str or None.")
|
||||||
vhosts = self.choose_vhosts(domain)
|
vhosts = self.choose_vhosts(domain)
|
||||||
for vhost in vhosts:
|
for vhost in vhosts:
|
||||||
self._enable_ocsp_stapling_single(vhost, chain_path)
|
self._enable_ocsp_stapling_single(vhost, chain_path)
|
||||||
|
|
||||||
def _enable_ocsp_stapling_single(self, vhost, chain_path):
|
def _enable_ocsp_stapling_single(self, vhost: obj.VirtualHost,
|
||||||
|
chain_path: Optional[str]) -> None:
|
||||||
"""Include OCSP response in TLS handshake
|
"""Include OCSP response in TLS handshake
|
||||||
|
|
||||||
:param str vhost: vhost to enable OCSP response for
|
:param str vhost: vhost to enable OCSP response for
|
||||||
@@ -957,7 +981,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
######################################
|
######################################
|
||||||
# Nginx server management (Installer)
|
# Nginx server management (Installer)
|
||||||
######################################
|
######################################
|
||||||
def restart(self):
|
def restart(self) -> None:
|
||||||
"""Restarts nginx server.
|
"""Restarts nginx server.
|
||||||
|
|
||||||
:raises .errors.MisconfigurationError: If either the reload fails.
|
:raises .errors.MisconfigurationError: If either the reload fails.
|
||||||
@@ -965,7 +989,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
"""
|
"""
|
||||||
nginx_restart(self.conf('ctl'), self.nginx_conf, self.conf('sleep-seconds'))
|
nginx_restart(self.conf('ctl'), self.nginx_conf, self.conf('sleep-seconds'))
|
||||||
|
|
||||||
def config_test(self):
|
def config_test(self) -> None:
|
||||||
"""Check the configuration of Nginx for errors.
|
"""Check the configuration of Nginx for errors.
|
||||||
|
|
||||||
:raises .errors.MisconfigurationError: If config_test fails
|
:raises .errors.MisconfigurationError: If config_test fails
|
||||||
@@ -976,7 +1000,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
except errors.SubprocessError as err:
|
except errors.SubprocessError as err:
|
||||||
raise errors.MisconfigurationError(str(err))
|
raise errors.MisconfigurationError(str(err))
|
||||||
|
|
||||||
def _nginx_version(self):
|
def _nginx_version(self) -> str:
|
||||||
"""Return results of nginx -V
|
"""Return results of nginx -V
|
||||||
|
|
||||||
:returns: version text
|
:returns: version text
|
||||||
@@ -1000,7 +1024,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
"Unable to run %s -V" % self.conf('ctl'))
|
"Unable to run %s -V" % self.conf('ctl'))
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def get_version(self):
|
def get_version(self) -> Tuple[int, ...]:
|
||||||
"""Return version of Nginx Server.
|
"""Return version of Nginx Server.
|
||||||
|
|
||||||
Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7))
|
Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7))
|
||||||
@@ -1045,7 +1069,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
|
|
||||||
return nginx_version
|
return nginx_version
|
||||||
|
|
||||||
def _get_openssl_version(self):
|
def _get_openssl_version(self) -> str:
|
||||||
"""Return version of OpenSSL linked to Nginx.
|
"""Return version of OpenSSL linked to Nginx.
|
||||||
|
|
||||||
Version is returned as string. If no version can be found, empty string is returned.
|
Version is returned as string. If no version can be found, empty string is returned.
|
||||||
@@ -1067,7 +1091,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
return ""
|
return ""
|
||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
def more_info(self):
|
def more_info(self) -> str:
|
||||||
"""Human-readable string to help understand the module"""
|
"""Human-readable string to help understand the module"""
|
||||||
return (
|
return (
|
||||||
"Configures Nginx to authenticate and install HTTPS.{0}"
|
"Configures Nginx to authenticate and install HTTPS.{0}"
|
||||||
@@ -1077,7 +1101,8 @@ class NginxConfigurator(common.Configurator):
|
|||||||
version=".".join(str(i) for i in self.version))
|
version=".".join(str(i) for i in self.version))
|
||||||
)
|
)
|
||||||
|
|
||||||
def auth_hint(self, failed_achalls): # pragma: no cover
|
def auth_hint(self, # pragma: no cover
|
||||||
|
failed_achalls: Iterable[achallenges.AnnotatedChallenge]) -> str:
|
||||||
return (
|
return (
|
||||||
"The Certificate Authority failed to verify the temporary nginx configuration changes "
|
"The Certificate Authority failed to verify the temporary nginx configuration changes "
|
||||||
"made by Certbot. Ensure the listed domains point to this nginx server and that it is "
|
"made by Certbot. Ensure the listed domains point to this nginx server and that it is "
|
||||||
@@ -1087,7 +1112,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
###################################################
|
###################################################
|
||||||
# Wrapper functions for Reverter class (Installer)
|
# Wrapper functions for Reverter class (Installer)
|
||||||
###################################################
|
###################################################
|
||||||
def save(self, title=None, temporary=False):
|
def save(self, title: str = None, temporary: bool = False) -> None:
|
||||||
"""Saves all changes to the configuration files.
|
"""Saves all changes to the configuration files.
|
||||||
|
|
||||||
:param str title: The title of the save. If a title is given, the
|
:param str title: The title of the save. If a title is given, the
|
||||||
@@ -1111,7 +1136,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
if title and not temporary:
|
if title and not temporary:
|
||||||
self.finalize_checkpoint(title)
|
self.finalize_checkpoint(title)
|
||||||
|
|
||||||
def recovery_routine(self):
|
def recovery_routine(self) -> None:
|
||||||
"""Revert all previously modified files.
|
"""Revert all previously modified files.
|
||||||
|
|
||||||
Reverts all modified files that have not been saved as a checkpoint
|
Reverts all modified files that have not been saved as a checkpoint
|
||||||
@@ -1123,7 +1148,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
self.new_vhost = None
|
self.new_vhost = None
|
||||||
self.parser.load()
|
self.parser.load()
|
||||||
|
|
||||||
def revert_challenge_config(self):
|
def revert_challenge_config(self) -> None:
|
||||||
"""Used to cleanup challenge configurations.
|
"""Used to cleanup challenge configurations.
|
||||||
|
|
||||||
:raises .errors.PluginError: If unable to revert the challenge config.
|
:raises .errors.PluginError: If unable to revert the challenge config.
|
||||||
@@ -1133,7 +1158,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
self.new_vhost = None
|
self.new_vhost = None
|
||||||
self.parser.load()
|
self.parser.load()
|
||||||
|
|
||||||
def rollback_checkpoints(self, rollback=1):
|
def rollback_checkpoints(self, rollback: int = 1) -> None:
|
||||||
"""Rollback saved checkpoints.
|
"""Rollback saved checkpoints.
|
||||||
|
|
||||||
:param int rollback: Number of checkpoints to revert
|
:param int rollback: Number of checkpoints to revert
|
||||||
@@ -1149,12 +1174,13 @@ class NginxConfigurator(common.Configurator):
|
|||||||
###########################################################################
|
###########################################################################
|
||||||
# Challenges Section for Authenticator
|
# Challenges Section for Authenticator
|
||||||
###########################################################################
|
###########################################################################
|
||||||
def get_chall_pref(self, unused_domain):
|
def get_chall_pref(self, unused_domain: str) -> List[Type[challenges.Challenge]]:
|
||||||
"""Return list of challenge preferences."""
|
"""Return list of challenge preferences."""
|
||||||
return [challenges.HTTP01]
|
return [challenges.HTTP01]
|
||||||
|
|
||||||
# Entry point in main.py for performing challenges
|
# Entry point in main.py for performing challenges
|
||||||
def perform(self, achalls):
|
def perform(self, achalls: List[achallenges.AnnotatedChallenge]
|
||||||
|
) -> List[challenges.HTTP01Response]:
|
||||||
"""Perform the configuration related challenge.
|
"""Perform the configuration related challenge.
|
||||||
|
|
||||||
This function currently assumes all challenges will be fulfilled.
|
This function currently assumes all challenges will be fulfilled.
|
||||||
@@ -1163,7 +1189,7 @@ class NginxConfigurator(common.Configurator):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
self._chall_out += len(achalls)
|
self._chall_out += len(achalls)
|
||||||
responses = [None] * len(achalls)
|
responses: List[Optional[challenges.HTTP01Response]] = [None] * len(achalls)
|
||||||
http_doer = http_01.NginxHttp01(self)
|
http_doer = http_01.NginxHttp01(self)
|
||||||
|
|
||||||
for i, achall in enumerate(achalls):
|
for i, achall in enumerate(achalls):
|
||||||
@@ -1183,10 +1209,10 @@ class NginxConfigurator(common.Configurator):
|
|||||||
for i, resp in enumerate(http_response):
|
for i, resp in enumerate(http_response):
|
||||||
responses[http_doer.indices[i]] = resp
|
responses[http_doer.indices[i]] = resp
|
||||||
|
|
||||||
return responses
|
return [response for response in responses if response]
|
||||||
|
|
||||||
# called after challenges are performed
|
# called after challenges are performed
|
||||||
def cleanup(self, achalls):
|
def cleanup(self, achalls: List[achallenges.AnnotatedChallenge]) -> None:
|
||||||
"""Revert all challenges."""
|
"""Revert all challenges."""
|
||||||
self._chall_out -= len(achalls)
|
self._chall_out -= len(achalls)
|
||||||
|
|
||||||
@@ -1196,13 +1222,13 @@ class NginxConfigurator(common.Configurator):
|
|||||||
self.restart()
|
self.restart()
|
||||||
|
|
||||||
|
|
||||||
def _test_block_from_block(block):
|
def _test_block_from_block(block: List[Any]) -> List[Any]:
|
||||||
test_block = nginxparser.UnspacedList(block)
|
test_block = nginxparser.UnspacedList(block)
|
||||||
parser.comment_directive(test_block, 0)
|
parser.comment_directive(test_block, 0)
|
||||||
return test_block[:-1]
|
return test_block[:-1]
|
||||||
|
|
||||||
|
|
||||||
def _redirect_block_for_domain(domain):
|
def _redirect_block_for_domain(domain: str) -> List[Any]:
|
||||||
updated_domain = domain
|
updated_domain = domain
|
||||||
match_symbol = '='
|
match_symbol = '='
|
||||||
if util.is_wildcard_domain(domain):
|
if util.is_wildcard_domain(domain):
|
||||||
@@ -1218,7 +1244,7 @@ def _redirect_block_for_domain(domain):
|
|||||||
return redirect_block
|
return redirect_block
|
||||||
|
|
||||||
|
|
||||||
def nginx_restart(nginx_ctl, nginx_conf, sleep_duration):
|
def nginx_restart(nginx_ctl: str, nginx_conf: str, sleep_duration: int) -> None:
|
||||||
"""Restarts the Nginx Server.
|
"""Restarts the Nginx Server.
|
||||||
|
|
||||||
.. todo:: Nginx restart is fatal if the configuration references
|
.. todo:: Nginx restart is fatal if the configuration references
|
||||||
@@ -1263,10 +1289,10 @@ def nginx_restart(nginx_ctl, nginx_conf, sleep_duration):
|
|||||||
time.sleep(sleep_duration)
|
time.sleep(sleep_duration)
|
||||||
|
|
||||||
|
|
||||||
def _determine_default_server_root():
|
def _determine_default_server_root() -> str:
|
||||||
if os.environ.get("CERTBOT_DOCS") == "1":
|
if os.environ.get("CERTBOT_DOCS") == "1":
|
||||||
default_server_root = "%s or %s" % (constants.LINUX_SERVER_ROOT,
|
default_server_root = (f"{constants.LINUX_SERVER_ROOT} "
|
||||||
constants.FREEBSD_DARWIN_SERVER_ROOT)
|
f"or {constants.FREEBSD_DARWIN_SERVER_ROOT}")
|
||||||
else:
|
else:
|
||||||
default_server_root = constants.CLI_DEFAULTS["server_root"]
|
default_server_root = constants.CLI_DEFAULTS["server_root"]
|
||||||
return default_server_root
|
return default_server_root
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ ALL_SSL_OPTIONS_HASHES = [
|
|||||||
]
|
]
|
||||||
"""SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC"""
|
"""SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC"""
|
||||||
|
|
||||||
def os_constant(key):
|
|
||||||
|
def os_constant(key: str) -> Any:
|
||||||
# XXX TODO: In the future, this could return different constants
|
# XXX TODO: In the future, this could return different constants
|
||||||
# based on what OS we are running under. To see an
|
# based on what OS we are running under. To see an
|
||||||
# approach to how to handle different OSes, see the
|
# approach to how to handle different OSes, see the
|
||||||
@@ -61,11 +62,12 @@ def os_constant(key):
|
|||||||
"""
|
"""
|
||||||
Get a constant value for operating system
|
Get a constant value for operating system
|
||||||
|
|
||||||
:param key: name of cli constant
|
:param str key: name of cli constant
|
||||||
:return: value of constant for active os
|
:return: value of constant for active os
|
||||||
"""
|
"""
|
||||||
return CLI_DEFAULTS[key]
|
return CLI_DEFAULTS[key]
|
||||||
|
|
||||||
|
|
||||||
HSTS_ARGS = ['\"max-age=31536000\"', ' ', 'always']
|
HSTS_ARGS = ['\"max-age=31536000\"', ' ', 'always']
|
||||||
|
|
||||||
HEADER_ARGS = {'Strict-Transport-Security': HSTS_ARGS}
|
HEADER_ARGS = {'Strict-Transport-Security': HSTS_ARGS}
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
"""Contains UI methods for Nginx operations."""
|
"""Contains UI methods for Nginx operations."""
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Iterable
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from certbot_nginx._internal.obj import VirtualHost
|
||||||
|
|
||||||
from certbot.display import util as display_util
|
from certbot.display import util as display_util
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def select_vhost_multiple(vhosts):
|
def select_vhost_multiple(vhosts: Optional[Iterable[VirtualHost]]) -> List[VirtualHost]:
|
||||||
"""Select multiple Vhosts to install the certificate for
|
"""Select multiple Vhosts to install the certificate for
|
||||||
:param vhosts: Available Nginx VirtualHosts
|
:param vhosts: Available Nginx VirtualHosts
|
||||||
:type vhosts: :class:`list` of type `~obj.Vhost`
|
:type vhosts: :class:`list` of type `~obj.Vhost`
|
||||||
@@ -28,7 +33,7 @@ def select_vhost_multiple(vhosts):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _reversemap_vhosts(names, vhosts):
|
def _reversemap_vhosts(names: Iterable[str], vhosts: Iterable[VirtualHost]) -> List[VirtualHost]:
|
||||||
"""Helper function for select_vhost_multiple for mapping string
|
"""Helper function for select_vhost_multiple for mapping string
|
||||||
representations back to actual vhost objects"""
|
representations back to actual vhost objects"""
|
||||||
return_vhosts = []
|
return_vhosts = []
|
||||||
|
|||||||
@@ -2,17 +2,20 @@
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from certbot_nginx._internal import nginxparser
|
||||||
|
from certbot_nginx._internal.obj import Addr
|
||||||
|
|
||||||
from acme import challenges
|
from acme import challenges
|
||||||
from certbot import achallenges
|
from acme.challenges import HTTP01Response
|
||||||
from certbot import errors
|
from certbot import errors
|
||||||
|
from certbot.achallenges import KeyAuthorizationAnnotatedChallenge
|
||||||
from certbot.compat import os
|
from certbot.compat import os
|
||||||
from certbot.plugins import common
|
from certbot.plugins import common
|
||||||
from certbot_nginx._internal import nginxparser
|
|
||||||
from certbot_nginx._internal import obj
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from certbot_nginx._internal.configurator import NginxConfigurator
|
from certbot_nginx._internal.configurator import NginxConfigurator
|
||||||
@@ -46,7 +49,7 @@ class NginxHttp01(common.ChallengePerformer):
|
|||||||
self.challenge_conf = os.path.join(
|
self.challenge_conf = os.path.join(
|
||||||
configurator.config.config_dir, "le_http_01_cert_challenge.conf")
|
configurator.config.config_dir, "le_http_01_cert_challenge.conf")
|
||||||
|
|
||||||
def perform(self):
|
def perform(self) -> List[HTTP01Response]:
|
||||||
"""Perform a challenge on Nginx.
|
"""Perform a challenge on Nginx.
|
||||||
|
|
||||||
:returns: list of :class:`certbot.acme.challenges.HTTP01Response`
|
:returns: list of :class:`certbot.acme.challenges.HTTP01Response`
|
||||||
@@ -66,7 +69,7 @@ class NginxHttp01(common.ChallengePerformer):
|
|||||||
|
|
||||||
return responses
|
return responses
|
||||||
|
|
||||||
def _mod_config(self):
|
def _mod_config(self) -> None:
|
||||||
"""Modifies Nginx config to include server_names_hash_bucket_size directive
|
"""Modifies Nginx config to include server_names_hash_bucket_size directive
|
||||||
and server challenge blocks.
|
and server challenge blocks.
|
||||||
|
|
||||||
@@ -113,39 +116,40 @@ class NginxHttp01(common.ChallengePerformer):
|
|||||||
with io.open(self.challenge_conf, "w", encoding="utf-8") as new_conf:
|
with io.open(self.challenge_conf, "w", encoding="utf-8") as new_conf:
|
||||||
nginxparser.dump(config, new_conf)
|
nginxparser.dump(config, new_conf)
|
||||||
|
|
||||||
def _default_listen_addresses(self):
|
def _default_listen_addresses(self) -> List[Addr]:
|
||||||
"""Finds addresses for a challenge block to listen on.
|
"""Finds addresses for a challenge block to listen on.
|
||||||
:returns: list of :class:`certbot_nginx._internal.obj.Addr` to apply
|
:returns: list of :class:`certbot_nginx._internal.obj.Addr` to apply
|
||||||
:rtype: list
|
:rtype: list
|
||||||
"""
|
"""
|
||||||
addresses: List[obj.Addr] = []
|
addresses: List[Optional[Addr]] = []
|
||||||
default_addr = "%s" % self.configurator.config.http01_port
|
default_addr = "%s" % self.configurator.config.http01_port
|
||||||
ipv6_addr = "[::]:{0}".format(
|
ipv6_addr = "[::]:{0}".format(
|
||||||
self.configurator.config.http01_port)
|
self.configurator.config.http01_port)
|
||||||
port = self.configurator.config.http01_port
|
port = self.configurator.config.http01_port
|
||||||
|
|
||||||
ipv6, ipv6only = self.configurator.ipv6_info(port)
|
ipv6, ipv6only = self.configurator.ipv6_info(str(port))
|
||||||
|
|
||||||
if ipv6:
|
if ipv6:
|
||||||
# If IPv6 is active in Nginx configuration
|
# If IPv6 is active in Nginx configuration
|
||||||
if not ipv6only:
|
if not ipv6only:
|
||||||
# If ipv6only=on is not already present in the config
|
# If ipv6only=on is not already present in the config
|
||||||
ipv6_addr = ipv6_addr + " ipv6only=on"
|
ipv6_addr = ipv6_addr + " ipv6only=on"
|
||||||
addresses = [obj.Addr.fromstring(default_addr),
|
addresses = [Addr.fromstring(default_addr),
|
||||||
obj.Addr.fromstring(ipv6_addr)]
|
Addr.fromstring(ipv6_addr)]
|
||||||
logger.debug(("Using default addresses %s and %s for authentication."),
|
logger.debug(("Using default addresses %s and %s for authentication."),
|
||||||
default_addr,
|
default_addr,
|
||||||
ipv6_addr)
|
ipv6_addr)
|
||||||
else:
|
else:
|
||||||
addresses = [obj.Addr.fromstring(default_addr)]
|
addresses = [Addr.fromstring(default_addr)]
|
||||||
logger.debug("Using default address %s for authentication.",
|
logger.debug("Using default address %s for authentication.",
|
||||||
default_addr)
|
default_addr)
|
||||||
return addresses
|
|
||||||
|
|
||||||
def _get_validation_path(self, achall):
|
return [address for address in addresses if address]
|
||||||
|
|
||||||
|
def _get_validation_path(self, achall: KeyAuthorizationAnnotatedChallenge) -> str:
|
||||||
return os.sep + os.path.join(challenges.HTTP01.URI_ROOT_PATH, achall.chall.encode("token"))
|
return os.sep + os.path.join(challenges.HTTP01.URI_ROOT_PATH, achall.chall.encode("token"))
|
||||||
|
|
||||||
def _make_server_block(self, achall: achallenges.KeyAuthorizationAnnotatedChallenge) -> List:
|
def _make_server_block(self, achall: KeyAuthorizationAnnotatedChallenge) -> List[Any]:
|
||||||
"""Creates a server block for a challenge.
|
"""Creates a server block for a challenge.
|
||||||
|
|
||||||
:param achall: Annotated HTTP-01 challenge
|
:param achall: Annotated HTTP-01 challenge
|
||||||
@@ -168,7 +172,8 @@ class NginxHttp01(common.ChallengePerformer):
|
|||||||
# TODO: do we want to return something else if they otherwise access this block?
|
# TODO: do we want to return something else if they otherwise access this block?
|
||||||
return [['server'], block]
|
return [['server'], block]
|
||||||
|
|
||||||
def _location_directive_for_achall(self, achall):
|
def _location_directive_for_achall(self, achall: KeyAuthorizationAnnotatedChallenge
|
||||||
|
) -> List[Any]:
|
||||||
validation = achall.validation(achall.account_key)
|
validation = achall.validation(achall.account_key)
|
||||||
validation_path = self._get_validation_path(achall)
|
validation_path = self._get_validation_path(achall)
|
||||||
|
|
||||||
@@ -177,9 +182,8 @@ class NginxHttp01(common.ChallengePerformer):
|
|||||||
['return', ' ', '200', ' ', validation]]]
|
['return', ' ', '200', ' ', validation]]]
|
||||||
return location_directive
|
return location_directive
|
||||||
|
|
||||||
|
def _make_or_mod_server_block(self, achall: KeyAuthorizationAnnotatedChallenge
|
||||||
def _make_or_mod_server_block(self, achall: achallenges.KeyAuthorizationAnnotatedChallenge
|
) -> Optional[List[Any]]:
|
||||||
) -> Optional[List]:
|
|
||||||
"""Modifies server blocks to respond to a challenge. Returns a new HTTP server block
|
"""Modifies server blocks to respond to a challenge. Returns a new HTTP server block
|
||||||
to add to the configuration if an existing one can't be found.
|
to add to the configuration if an existing one can't be found.
|
||||||
|
|
||||||
@@ -192,7 +196,7 @@ class NginxHttp01(common.ChallengePerformer):
|
|||||||
"""
|
"""
|
||||||
http_vhosts, https_vhosts = self.configurator.choose_auth_vhosts(achall.domain)
|
http_vhosts, https_vhosts = self.configurator.choose_auth_vhosts(achall.domain)
|
||||||
|
|
||||||
new_vhost: Optional[list] = None
|
new_vhost: Optional[List[Any]] = None
|
||||||
if not http_vhosts:
|
if not http_vhosts:
|
||||||
# Couldn't find either a matching name+port server block
|
# Couldn't find either a matching name+port server block
|
||||||
# or a port+default_server block, so create a dummy block
|
# or a port+default_server block, so create a dummy block
|
||||||
@@ -205,8 +209,8 @@ class NginxHttp01(common.ChallengePerformer):
|
|||||||
self.configurator.parser.add_server_directives(vhost, location_directive)
|
self.configurator.parser.add_server_directives(vhost, location_directive)
|
||||||
|
|
||||||
rewrite_directive = [['rewrite', ' ', '^(/.well-known/acme-challenge/.*)',
|
rewrite_directive = [['rewrite', ' ', '^(/.well-known/acme-challenge/.*)',
|
||||||
' ', '$1', ' ', 'break']]
|
' ', '$1', ' ', 'break']]
|
||||||
self.configurator.parser.add_server_directives(vhost,
|
self.configurator.parser.add_server_directives(
|
||||||
rewrite_directive, insert_at_top=True)
|
vhost, rewrite_directive, insert_at_top=True)
|
||||||
|
|
||||||
return new_vhost
|
return new_vhost
|
||||||
|
|||||||
@@ -2,14 +2,23 @@
|
|||||||
# Forked from https://github.com/fatiherikli/nginxparser (MIT Licensed)
|
# Forked from https://github.com/fatiherikli/nginxparser (MIT Licensed)
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
|
import typing
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import IO
|
from typing import IO
|
||||||
|
from typing import Iterable
|
||||||
|
from typing import Iterator
|
||||||
|
from typing import List
|
||||||
|
from typing import overload
|
||||||
|
from typing import Tuple
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from pyparsing import Combine
|
from pyparsing import Combine
|
||||||
from pyparsing import Forward
|
from pyparsing import Forward
|
||||||
from pyparsing import Group
|
from pyparsing import Group
|
||||||
from pyparsing import Literal
|
from pyparsing import Literal
|
||||||
from pyparsing import Optional
|
from pyparsing import Optional
|
||||||
|
from pyparsing import ParseResults
|
||||||
from pyparsing import QuotedString
|
from pyparsing import QuotedString
|
||||||
from pyparsing import Regex
|
from pyparsing import Regex
|
||||||
from pyparsing import restOfLine
|
from pyparsing import restOfLine
|
||||||
@@ -17,6 +26,9 @@ from pyparsing import stringEnd
|
|||||||
from pyparsing import White
|
from pyparsing import White
|
||||||
from pyparsing import ZeroOrMore
|
from pyparsing import ZeroOrMore
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import SupportsIndex # typing.SupportsIndex not supported on Python 3.6
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -59,23 +71,24 @@ class RawNginxParser:
|
|||||||
script = ZeroOrMore(contents) + space + stringEnd
|
script = ZeroOrMore(contents) + space + stringEnd
|
||||||
script.parseWithTabs().leaveWhitespace()
|
script.parseWithTabs().leaveWhitespace()
|
||||||
|
|
||||||
def __init__(self, source):
|
def __init__(self, source: str) -> None:
|
||||||
self.source = source
|
self.source = source
|
||||||
|
|
||||||
def parse(self):
|
def parse(self) -> ParseResults:
|
||||||
"""Returns the parsed tree."""
|
"""Returns the parsed tree."""
|
||||||
return self.script.parseString(self.source)
|
return self.script.parseString(self.source)
|
||||||
|
|
||||||
def as_list(self):
|
def as_list(self) -> List[Any]:
|
||||||
"""Returns the parsed tree as a list."""
|
"""Returns the parsed tree as a list."""
|
||||||
return self.parse().asList()
|
return self.parse().asList()
|
||||||
|
|
||||||
|
|
||||||
class RawNginxDumper:
|
class RawNginxDumper:
|
||||||
"""A class that dumps nginx configuration from the provided tree."""
|
"""A class that dumps nginx configuration from the provided tree."""
|
||||||
def __init__(self, blocks):
|
def __init__(self, blocks: List[Any]) -> None:
|
||||||
self.blocks = blocks
|
self.blocks = blocks
|
||||||
|
|
||||||
def __iter__(self, blocks=None):
|
def __iter__(self, blocks: typing.Optional[List[Any]] = None) -> Iterator[str]:
|
||||||
"""Iterates the dumped nginx content."""
|
"""Iterates the dumped nginx content."""
|
||||||
blocks = blocks or self.blocks
|
blocks = blocks or self.blocks
|
||||||
for b0 in blocks:
|
for b0 in blocks:
|
||||||
@@ -100,7 +113,7 @@ class RawNginxDumper:
|
|||||||
semicolon = ""
|
semicolon = ""
|
||||||
yield "".join(item) + semicolon
|
yield "".join(item) + semicolon
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
"""Return the parsed block as a string."""
|
"""Return the parsed block as a string."""
|
||||||
return ''.join(self)
|
return ''.join(self)
|
||||||
|
|
||||||
@@ -108,10 +121,10 @@ class RawNginxDumper:
|
|||||||
spacey = lambda x: (isinstance(x, str) and x.isspace()) or x == ''
|
spacey = lambda x: (isinstance(x, str) and x.isspace()) or x == ''
|
||||||
|
|
||||||
|
|
||||||
class UnspacedList(list):
|
class UnspacedList(List[Any]):
|
||||||
"""Wrap a list [of lists], making any whitespace entries magically invisible"""
|
"""Wrap a list [of lists], making any whitespace entries magically invisible"""
|
||||||
|
|
||||||
def __init__(self, list_source):
|
def __init__(self, list_source: Iterable[Any]) -> None:
|
||||||
# ensure our argument is not a generator, and duplicate any sublists
|
# ensure our argument is not a generator, and duplicate any sublists
|
||||||
self.spaced = copy.deepcopy(list(list_source))
|
self.spaced = copy.deepcopy(list(list_source))
|
||||||
self.dirty = False
|
self.dirty = False
|
||||||
@@ -122,14 +135,23 @@ class UnspacedList(list):
|
|||||||
for i, entry in reversed(list(enumerate(self))):
|
for i, entry in reversed(list(enumerate(self))):
|
||||||
if isinstance(entry, list):
|
if isinstance(entry, list):
|
||||||
sublist = UnspacedList(entry)
|
sublist = UnspacedList(entry)
|
||||||
list.__setitem__(self, i, sublist)
|
super().__setitem__(i, sublist)
|
||||||
self.spaced[i] = sublist.spaced
|
self.spaced[i] = sublist.spaced
|
||||||
elif spacey(entry):
|
elif spacey(entry):
|
||||||
# don't delete comments
|
# don't delete comments
|
||||||
if "#" not in self[:i]:
|
if "#" not in self[:i]:
|
||||||
list.__delitem__(self, i)
|
super().__delitem__(i)
|
||||||
|
|
||||||
def _coerce(self, inbound):
|
@overload
|
||||||
|
def _coerce(self, inbound: None) -> Tuple[None, None]: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def _coerce(self, inbound: str) -> Tuple[str, str]: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def _coerce(self, inbound: List[Any]) -> Tuple["UnspacedList", List[Any]]: ...
|
||||||
|
|
||||||
|
def _coerce(self, inbound: Any) -> Tuple[Any, Any]:
|
||||||
"""
|
"""
|
||||||
Coerce some inbound object to be appropriately usable in this object
|
Coerce some inbound object to be appropriately usable in this object
|
||||||
|
|
||||||
@@ -138,100 +160,114 @@ class UnspacedList(list):
|
|||||||
:rtype: tuple
|
:rtype: tuple
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(inbound, list): # str or None
|
if not isinstance(inbound, list): # str or None
|
||||||
return inbound, inbound
|
return inbound, inbound
|
||||||
else:
|
else:
|
||||||
if not hasattr(inbound, "spaced"):
|
if not hasattr(inbound, "spaced"):
|
||||||
inbound = UnspacedList(inbound)
|
inbound = UnspacedList(inbound)
|
||||||
return inbound, inbound.spaced
|
return inbound, inbound.spaced
|
||||||
|
|
||||||
def insert(self, i, x):
|
def insert(self, i: int, x: Any) -> None:
|
||||||
|
"""Insert object before index."""
|
||||||
item, spaced_item = self._coerce(x)
|
item, spaced_item = self._coerce(x)
|
||||||
slicepos = self._spaced_position(i) if i < len(self) else len(self.spaced)
|
slicepos = self._spaced_position(i) if i < len(self) else len(self.spaced)
|
||||||
self.spaced.insert(slicepos, spaced_item)
|
self.spaced.insert(slicepos, spaced_item)
|
||||||
if not spacey(item):
|
if not spacey(item):
|
||||||
list.insert(self, i, item)
|
super().insert(i, item)
|
||||||
self.dirty = True
|
self.dirty = True
|
||||||
|
|
||||||
def append(self, x):
|
def append(self, x: Any) -> None:
|
||||||
|
"""Append object to the end of the list."""
|
||||||
item, spaced_item = self._coerce(x)
|
item, spaced_item = self._coerce(x)
|
||||||
self.spaced.append(spaced_item)
|
self.spaced.append(spaced_item)
|
||||||
if not spacey(item):
|
if not spacey(item):
|
||||||
list.append(self, item)
|
super().append(item)
|
||||||
self.dirty = True
|
self.dirty = True
|
||||||
|
|
||||||
def extend(self, x):
|
def extend(self, x: Any) -> None:
|
||||||
|
"""Extend list by appending elements from the iterable."""
|
||||||
item, spaced_item = self._coerce(x)
|
item, spaced_item = self._coerce(x)
|
||||||
self.spaced.extend(spaced_item)
|
self.spaced.extend(spaced_item)
|
||||||
list.extend(self, item)
|
super().extend(item)
|
||||||
self.dirty = True
|
self.dirty = True
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other: List[Any]) -> "UnspacedList":
|
||||||
l = copy.deepcopy(self)
|
new_list = copy.deepcopy(self)
|
||||||
l.extend(other)
|
new_list.extend(other)
|
||||||
l.dirty = True
|
new_list.dirty = True
|
||||||
return l
|
return new_list
|
||||||
|
|
||||||
def pop(self, _i=None):
|
def pop(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Function pop() is not implemented for UnspacedList"""
|
||||||
raise NotImplementedError("UnspacedList.pop() not yet implemented")
|
raise NotImplementedError("UnspacedList.pop() not yet implemented")
|
||||||
def remove(self, _):
|
|
||||||
|
def remove(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Function remove() is not implemented for UnspacedList"""
|
||||||
raise NotImplementedError("UnspacedList.remove() not yet implemented")
|
raise NotImplementedError("UnspacedList.remove() not yet implemented")
|
||||||
def reverse(self):
|
|
||||||
|
def reverse(self) -> None:
|
||||||
|
"""Function reverse() is not implemented for UnspacedList"""
|
||||||
raise NotImplementedError("UnspacedList.reverse() not yet implemented")
|
raise NotImplementedError("UnspacedList.reverse() not yet implemented")
|
||||||
def sort(self, _cmp=None, _key=None, _Rev=None):
|
|
||||||
|
def sort(self, *_args: Any, **_kwargs: Any) -> None:
|
||||||
|
"""Function sort() is not implemented for UnspacedList"""
|
||||||
raise NotImplementedError("UnspacedList.sort() not yet implemented")
|
raise NotImplementedError("UnspacedList.sort() not yet implemented")
|
||||||
def __setslice__(self, _i, _j, _newslice):
|
|
||||||
|
def __setslice__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
raise NotImplementedError("Slice operations on UnspacedLists not yet implemented")
|
raise NotImplementedError("Slice operations on UnspacedLists not yet implemented")
|
||||||
|
|
||||||
def __setitem__(self, i, value):
|
def __setitem__(self, i: Union["SupportsIndex", slice], value: Any) -> None:
|
||||||
if isinstance(i, slice):
|
if isinstance(i, slice):
|
||||||
raise NotImplementedError("Slice operations on UnspacedLists not yet implemented")
|
raise NotImplementedError("Slice operations on UnspacedLists not yet implemented")
|
||||||
item, spaced_item = self._coerce(value)
|
item, spaced_item = self._coerce(value)
|
||||||
self.spaced.__setitem__(self._spaced_position(i), spaced_item)
|
self.spaced.__setitem__(self._spaced_position(i), spaced_item)
|
||||||
if not spacey(item):
|
if not spacey(item):
|
||||||
list.__setitem__(self, i, item)
|
super().__setitem__(i, item)
|
||||||
self.dirty = True
|
self.dirty = True
|
||||||
|
|
||||||
def __delitem__(self, i):
|
def __delitem__(self, i: Union["SupportsIndex", slice]) -> None:
|
||||||
|
if isinstance(i, slice):
|
||||||
|
raise NotImplementedError("Slice operations on UnspacedLists not yet implemented")
|
||||||
self.spaced.__delitem__(self._spaced_position(i))
|
self.spaced.__delitem__(self._spaced_position(i))
|
||||||
list.__delitem__(self, i)
|
super().__delitem__(i)
|
||||||
self.dirty = True
|
self.dirty = True
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo: Any) -> "UnspacedList":
|
||||||
new_spaced = copy.deepcopy(self.spaced, memo=memo)
|
new_spaced = copy.deepcopy(self.spaced, memo=memo)
|
||||||
l = UnspacedList(new_spaced)
|
new_list = UnspacedList(new_spaced)
|
||||||
l.dirty = self.dirty
|
new_list.dirty = self.dirty
|
||||||
return l
|
return new_list
|
||||||
|
|
||||||
def is_dirty(self):
|
def is_dirty(self) -> bool:
|
||||||
"""Recurse through the parse tree to figure out if any sublists are dirty"""
|
"""Recurse through the parse tree to figure out if any sublists are dirty"""
|
||||||
if self.dirty:
|
if self.dirty:
|
||||||
return True
|
return True
|
||||||
return any((isinstance(x, UnspacedList) and x.is_dirty() for x in self))
|
return any((isinstance(x, UnspacedList) and x.is_dirty() for x in self))
|
||||||
|
|
||||||
def _spaced_position(self, idx):
|
def _spaced_position(self, idx: "SupportsIndex") -> int:
|
||||||
"Convert from indexes in the unspaced list to positions in the spaced one"
|
"""Convert from indexes in the unspaced list to positions in the spaced one"""
|
||||||
|
int_idx = idx.__index__()
|
||||||
pos = spaces = 0
|
pos = spaces = 0
|
||||||
# Normalize indexes like list[-1] etc, and save the result
|
# Normalize indexes like list[-1] etc, and save the result
|
||||||
if idx < 0:
|
if int_idx < 0:
|
||||||
idx = len(self) + idx
|
int_idx = len(self) + int_idx
|
||||||
if not 0 <= idx < len(self):
|
if not 0 <= int_idx < len(self):
|
||||||
raise IndexError("list index out of range")
|
raise IndexError("list index out of range")
|
||||||
idx0 = idx
|
int_idx0 = int_idx
|
||||||
# Count the number of spaces in the spaced list before idx in the unspaced one
|
# Count the number of spaces in the spaced list before int_idx in the unspaced one
|
||||||
while idx != -1:
|
while int_idx != -1:
|
||||||
if spacey(self.spaced[pos]):
|
if spacey(self.spaced[pos]):
|
||||||
spaces += 1
|
spaces += 1
|
||||||
else:
|
else:
|
||||||
idx -= 1
|
int_idx -= 1
|
||||||
pos += 1
|
pos += 1
|
||||||
return idx0 + spaces
|
return int_idx0 + spaces
|
||||||
|
|
||||||
|
|
||||||
# Shortcut functions to respect Python's serialization interface
|
# Shortcut functions to respect Python's serialization interface
|
||||||
# (like pyyaml, picker or json)
|
# (like pyyaml, picker or json)
|
||||||
|
|
||||||
def loads(source):
|
def loads(source: str) -> UnspacedList:
|
||||||
"""Parses from a string.
|
"""Parses from a string.
|
||||||
|
|
||||||
:param str source: The string to parse
|
:param str source: The string to parse
|
||||||
@@ -242,34 +278,34 @@ def loads(source):
|
|||||||
return UnspacedList(RawNginxParser(source).as_list())
|
return UnspacedList(RawNginxParser(source).as_list())
|
||||||
|
|
||||||
|
|
||||||
def load(_file):
|
def load(file_: IO[Any]) -> UnspacedList:
|
||||||
"""Parses from a file.
|
"""Parses from a file.
|
||||||
|
|
||||||
:param file _file: The file to parse
|
:param file file_: The file to parse
|
||||||
:returns: The parsed tree
|
:returns: The parsed tree
|
||||||
:rtype: list
|
:rtype: list
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return loads(_file.read())
|
return loads(file_.read())
|
||||||
|
|
||||||
|
|
||||||
def dumps(blocks: UnspacedList) -> str:
|
def dumps(blocks: UnspacedList) -> str:
|
||||||
"""Dump to a Unicode string.
|
"""Dump to a Unicode string.
|
||||||
|
|
||||||
:param UnspacedList block: The parsed tree
|
:param UnspacedList blocks: The parsed tree
|
||||||
:rtype: six.text_type
|
:rtype: six.text_type
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return str(RawNginxDumper(blocks.spaced))
|
return str(RawNginxDumper(blocks.spaced))
|
||||||
|
|
||||||
|
|
||||||
def dump(blocks: UnspacedList, _file: IO[Any]) -> None:
|
def dump(blocks: UnspacedList, file_: IO[Any]) -> None:
|
||||||
"""Dump to a file.
|
"""Dump to a file.
|
||||||
|
|
||||||
:param UnspacedList block: The parsed tree
|
:param UnspacedList blocks: The parsed tree
|
||||||
:param IO[Any] _file: The file stream to dump to. It must be opened with
|
:param IO[Any] file_: The file stream to dump to. It must be opened with
|
||||||
Unicode encoding.
|
Unicode encoding.
|
||||||
:rtype: None
|
:rtype: None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
_file.write(dumps(blocks))
|
file_.write(dumps(blocks))
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
"""Module contains classes used by the Nginx Configurator."""
|
"""Module contains classes used by the Nginx Configurator."""
|
||||||
import re
|
import re
|
||||||
|
from typing import Any
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
from typing import Set
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from certbot.plugins import common
|
from certbot.plugins import common
|
||||||
|
|
||||||
ADD_HEADER_DIRECTIVE = 'add_header'
|
ADD_HEADER_DIRECTIVE = 'add_header'
|
||||||
|
|
||||||
|
|
||||||
class Addr(common.Addr):
|
class Addr(common.Addr):
|
||||||
r"""Represents an Nginx address, i.e. what comes after the 'listen'
|
r"""Represents an Nginx address, i.e. what comes after the 'listen'
|
||||||
directive.
|
directive.
|
||||||
@@ -34,7 +41,8 @@ class Addr(common.Addr):
|
|||||||
UNSPECIFIED_IPV4_ADDRESSES = ('', '*', '0.0.0.0')
|
UNSPECIFIED_IPV4_ADDRESSES = ('', '*', '0.0.0.0')
|
||||||
CANONICAL_UNSPECIFIED_ADDRESS = UNSPECIFIED_IPV4_ADDRESSES[0]
|
CANONICAL_UNSPECIFIED_ADDRESS = UNSPECIFIED_IPV4_ADDRESSES[0]
|
||||||
|
|
||||||
def __init__(self, host, port, ssl, default, ipv6, ipv6only):
|
def __init__(self, host: str, port: str, ssl: bool, default: bool,
|
||||||
|
ipv6: bool, ipv6only: bool) -> None:
|
||||||
super().__init__((host, port))
|
super().__init__((host, port))
|
||||||
self.ssl = ssl
|
self.ssl = ssl
|
||||||
self.default = default
|
self.default = default
|
||||||
@@ -43,7 +51,7 @@ class Addr(common.Addr):
|
|||||||
self.unspecified_address = host in self.UNSPECIFIED_IPV4_ADDRESSES
|
self.unspecified_address = host in self.UNSPECIFIED_IPV4_ADDRESSES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromstring(cls, str_addr):
|
def fromstring(cls, str_addr: str) -> Optional["Addr"]:
|
||||||
"""Initialize Addr from string."""
|
"""Initialize Addr from string."""
|
||||||
parts = str_addr.split(' ')
|
parts = str_addr.split(' ')
|
||||||
ssl = False
|
ssl = False
|
||||||
@@ -94,7 +102,7 @@ class Addr(common.Addr):
|
|||||||
|
|
||||||
return cls(host, port, ssl, default, ipv6, ipv6only)
|
return cls(host, port, ssl, default, ipv6, ipv6only)
|
||||||
|
|
||||||
def to_string(self, include_default=True):
|
def to_string(self, include_default: bool = True) -> str:
|
||||||
"""Return string representation of Addr"""
|
"""Return string representation of Addr"""
|
||||||
parts = ''
|
parts = ''
|
||||||
if self.tup[0] and self.tup[1]:
|
if self.tup[0] and self.tup[1]:
|
||||||
@@ -111,18 +119,18 @@ class Addr(common.Addr):
|
|||||||
|
|
||||||
return parts
|
return parts
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return self.to_string()
|
return self.to_string()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return "Addr(" + self.__str__() + ")"
|
return "Addr(" + self.__str__() + ")"
|
||||||
|
|
||||||
def __hash__(self): # pylint: disable=useless-super-delegation
|
def __hash__(self) -> int: # pylint: disable=useless-super-delegation
|
||||||
# Python 3 requires explicit overridden for __hash__
|
# Python 3 requires explicit overridden for __hash__
|
||||||
# See certbot-apache/certbot_apache/_internal/obj.py for more information
|
# See certbot-apache/certbot_apache/_internal/obj.py for more information
|
||||||
return super().__hash__()
|
return super().__hash__()
|
||||||
|
|
||||||
def super_eq(self, other):
|
def super_eq(self, other: "Addr") -> bool:
|
||||||
"""Check ip/port equality, with IPv6 support.
|
"""Check ip/port equality, with IPv6 support.
|
||||||
"""
|
"""
|
||||||
# If both addresses got an unspecified address, then make sure the
|
# If both addresses got an unspecified address, then make sure the
|
||||||
@@ -134,7 +142,7 @@ class Addr(common.Addr):
|
|||||||
other.tup[1]), other.ipv6)
|
other.tup[1]), other.ipv6)
|
||||||
return super().__eq__(other)
|
return super().__eq__(other)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
if isinstance(other, self.__class__):
|
if isinstance(other, self.__class__):
|
||||||
return (self.super_eq(other) and
|
return (self.super_eq(other) and
|
||||||
self.ssl == other.ssl and
|
self.ssl == other.ssl and
|
||||||
@@ -158,7 +166,8 @@ class VirtualHost:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, filep, addrs, ssl, enabled, names, raw, path):
|
def __init__(self, filep: str, addrs: Sequence[Addr], ssl: bool, enabled: bool,
|
||||||
|
names: Set[str], raw: List[Any], path: List[int]) -> None:
|
||||||
"""Initialize a VH."""
|
"""Initialize a VH."""
|
||||||
self.filep = filep
|
self.filep = filep
|
||||||
self.addrs = addrs
|
self.addrs = addrs
|
||||||
@@ -168,7 +177,7 @@ class VirtualHost:
|
|||||||
self.raw = raw
|
self.raw = raw
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
addr_str = ", ".join(str(addr) for addr in sorted(self.addrs, key=str))
|
addr_str = ", ".join(str(addr) for addr in sorted(self.addrs, key=str))
|
||||||
# names might be a set, and it has different representations in Python
|
# names might be a set, and it has different representations in Python
|
||||||
# 2 and 3. Force it to be a list here for consistent outputs
|
# 2 and 3. Force it to be a list here for consistent outputs
|
||||||
@@ -179,10 +188,10 @@ class VirtualHost:
|
|||||||
"enabled: %s" % (self.filep, addr_str,
|
"enabled: %s" % (self.filep, addr_str,
|
||||||
list(self.names), self.ssl, self.enabled))
|
list(self.names), self.ssl, self.enabled))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return "VirtualHost(" + self.__str__().replace("\n", ", ") + ")\n"
|
return "VirtualHost(" + self.__str__().replace("\n", ", ") + ")\n"
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other: Any) -> bool:
|
||||||
if isinstance(other, self.__class__):
|
if isinstance(other, self.__class__):
|
||||||
return (self.filep == other.filep and
|
return (self.filep == other.filep and
|
||||||
sorted(self.addrs, key=str) == sorted(other.addrs, key=str) and
|
sorted(self.addrs, key=str) == sorted(other.addrs, key=str) and
|
||||||
@@ -193,12 +202,12 @@ class VirtualHost:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self) -> int:
|
||||||
return hash((self.filep, tuple(self.path),
|
return hash((self.filep, tuple(self.path),
|
||||||
tuple(self.addrs), tuple(self.names),
|
tuple(self.addrs), tuple(self.names),
|
||||||
self.ssl, self.enabled))
|
self.ssl, self.enabled))
|
||||||
|
|
||||||
def has_header(self, header_name):
|
def has_header(self, header_name: str) -> bool:
|
||||||
"""Determine if this server block has a particular header set.
|
"""Determine if this server block has a particular header set.
|
||||||
:param str header_name: The name of the header to check for, e.g.
|
:param str header_name: The name of the header to check for, e.g.
|
||||||
'Strict-Transport-Security'
|
'Strict-Transport-Security'
|
||||||
@@ -206,7 +215,7 @@ class VirtualHost:
|
|||||||
found = _find_directive(self.raw, ADD_HEADER_DIRECTIVE, header_name)
|
found = _find_directive(self.raw, ADD_HEADER_DIRECTIVE, header_name)
|
||||||
return found is not None
|
return found is not None
|
||||||
|
|
||||||
def contains_list(self, test):
|
def contains_list(self, test: List[Any]) -> bool:
|
||||||
"""Determine if raw server block contains test list at top level
|
"""Determine if raw server block contains test list at top level
|
||||||
"""
|
"""
|
||||||
for i in range(0, len(self.raw) - len(test) + 1):
|
for i in range(0, len(self.raw) - len(test) + 1):
|
||||||
@@ -214,7 +223,7 @@ class VirtualHost:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def ipv6_enabled(self):
|
def ipv6_enabled(self) -> bool:
|
||||||
"""Return true if one or more of the listen directives in vhost supports
|
"""Return true if one or more of the listen directives in vhost supports
|
||||||
IPv6"""
|
IPv6"""
|
||||||
for a in self.addrs:
|
for a in self.addrs:
|
||||||
@@ -222,7 +231,7 @@ class VirtualHost:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def ipv4_enabled(self):
|
def ipv4_enabled(self) -> bool:
|
||||||
"""Return true if one or more of the listen directives in vhost are IPv4
|
"""Return true if one or more of the listen directives in vhost are IPv4
|
||||||
only"""
|
only"""
|
||||||
if not self.addrs:
|
if not self.addrs:
|
||||||
@@ -232,7 +241,7 @@ class VirtualHost:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def display_repr(self):
|
def display_repr(self) -> str:
|
||||||
"""Return a representation of VHost to be used in dialog"""
|
"""Return a representation of VHost to be used in dialog"""
|
||||||
return (
|
return (
|
||||||
"File: {filename}\n"
|
"File: {filename}\n"
|
||||||
@@ -244,7 +253,9 @@ class VirtualHost:
|
|||||||
names=", ".join(self.names),
|
names=", ".join(self.names),
|
||||||
https="Yes" if self.ssl else "No"))
|
https="Yes" if self.ssl else "No"))
|
||||||
|
|
||||||
def _find_directive(directives, directive_name, match_content=None):
|
|
||||||
|
def _find_directive(directives: Optional[Union[str, List[Any]]], directive_name: str,
|
||||||
|
match_content: Optional[Any] = None) -> Optional[Any]:
|
||||||
"""Find a directive of type directive_name in directives. If match_content is given,
|
"""Find a directive of type directive_name in directives. If match_content is given,
|
||||||
Searches for `match_content` in the directive arguments.
|
Searches for `match_content` in the directive arguments.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -5,20 +5,26 @@ import glob
|
|||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from typing import Any
|
||||||
|
from typing import Callable
|
||||||
|
from typing import cast
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
from typing import Iterable
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Mapping
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
from certbot_nginx._internal import nginxparser
|
||||||
|
from certbot_nginx._internal import obj
|
||||||
|
from certbot_nginx._internal.nginxparser import UnspacedList
|
||||||
import pyparsing
|
import pyparsing
|
||||||
|
|
||||||
from certbot import errors
|
from certbot import errors
|
||||||
from certbot.compat import os
|
from certbot.compat import os
|
||||||
from certbot_nginx._internal import nginxparser
|
|
||||||
from certbot_nginx._internal import obj
|
|
||||||
from certbot_nginx._internal.nginxparser import UnspacedList
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -32,8 +38,8 @@ class NginxParser:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, root):
|
def __init__(self, root: str) -> None:
|
||||||
self.parsed: Dict[str, Union[List, nginxparser.UnspacedList]] = {}
|
self.parsed: Dict[str, UnspacedList] = {}
|
||||||
self.root = os.path.abspath(root)
|
self.root = os.path.abspath(root)
|
||||||
self.config_root = self._find_config_root()
|
self.config_root = self._find_config_root()
|
||||||
|
|
||||||
@@ -42,14 +48,14 @@ class NginxParser:
|
|||||||
# not enable sites from there.
|
# not enable sites from there.
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def load(self):
|
def load(self) -> None:
|
||||||
"""Loads Nginx files into a parsed tree.
|
"""Loads Nginx files into a parsed tree.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.parsed = {}
|
self.parsed = {}
|
||||||
self._parse_recursively(self.config_root)
|
self._parse_recursively(self.config_root)
|
||||||
|
|
||||||
def _parse_recursively(self, filepath):
|
def _parse_recursively(self, filepath: str) -> None:
|
||||||
"""Parses nginx config files recursively by looking at 'include'
|
"""Parses nginx config files recursively by looking at 'include'
|
||||||
directives inside 'http' and 'server' blocks. Note that this only
|
directives inside 'http' and 'server' blocks. Note that this only
|
||||||
reads Nginx files that potentially declare a virtual host.
|
reads Nginx files that potentially declare a virtual host.
|
||||||
@@ -77,7 +83,7 @@ class NginxParser:
|
|||||||
if _is_include_directive(server_entry):
|
if _is_include_directive(server_entry):
|
||||||
self._parse_recursively(server_entry[1])
|
self._parse_recursively(server_entry[1])
|
||||||
|
|
||||||
def abs_path(self, path):
|
def abs_path(self, path: str) -> str:
|
||||||
"""Converts a relative path to an absolute path relative to the root.
|
"""Converts a relative path to an absolute path relative to the root.
|
||||||
Does nothing for paths that are already absolute.
|
Does nothing for paths that are already absolute.
|
||||||
|
|
||||||
@@ -90,7 +96,7 @@ class NginxParser:
|
|||||||
return os.path.normpath(os.path.join(self.root, path))
|
return os.path.normpath(os.path.join(self.root, path))
|
||||||
return os.path.normpath(path)
|
return os.path.normpath(path)
|
||||||
|
|
||||||
def _build_addr_to_ssl(self):
|
def _build_addr_to_ssl(self) -> Dict[Tuple[str, str], bool]:
|
||||||
"""Builds a map from address to whether it listens on ssl in any server block
|
"""Builds a map from address to whether it listens on ssl in any server block
|
||||||
"""
|
"""
|
||||||
servers = self._get_raw_servers()
|
servers = self._get_raw_servers()
|
||||||
@@ -107,11 +113,11 @@ class NginxParser:
|
|||||||
addr_to_ssl[addr_tuple] = addr.ssl or addr_to_ssl[addr_tuple]
|
addr_to_ssl[addr_tuple] = addr.ssl or addr_to_ssl[addr_tuple]
|
||||||
return addr_to_ssl
|
return addr_to_ssl
|
||||||
|
|
||||||
def _get_raw_servers(self) -> Dict:
|
def _get_raw_servers(self) -> Dict[str, Union[List[Any], UnspacedList]]:
|
||||||
# pylint: disable=cell-var-from-loop
|
# pylint: disable=cell-var-from-loop
|
||||||
"""Get a map of unparsed all server blocks
|
"""Get a map of unparsed all server blocks
|
||||||
"""
|
"""
|
||||||
servers: Dict[str, Union[List, nginxparser.UnspacedList]] = {}
|
servers: Dict[str, Union[List[Any], nginxparser.UnspacedList]] = {}
|
||||||
for filename, tree in self.parsed.items():
|
for filename, tree in self.parsed.items():
|
||||||
servers[filename] = []
|
servers[filename] = []
|
||||||
srv = servers[filename] # workaround undefined loop var in lambdas
|
srv = servers[filename] # workaround undefined loop var in lambdas
|
||||||
@@ -126,7 +132,7 @@ class NginxParser:
|
|||||||
servers[filename][i] = (new_server, path)
|
servers[filename][i] = (new_server, path)
|
||||||
return servers
|
return servers
|
||||||
|
|
||||||
def get_vhosts(self):
|
def get_vhosts(self) -> List[obj.VirtualHost]:
|
||||||
"""Gets list of all 'virtual hosts' found in Nginx configuration.
|
"""Gets list of all 'virtual hosts' found in Nginx configuration.
|
||||||
Technically this is a misnomer because Nginx does not have virtual
|
Technically this is a misnomer because Nginx does not have virtual
|
||||||
hosts, it has 'server blocks'.
|
hosts, it has 'server blocks'.
|
||||||
@@ -158,7 +164,7 @@ class NginxParser:
|
|||||||
|
|
||||||
return vhosts
|
return vhosts
|
||||||
|
|
||||||
def _update_vhosts_addrs_ssl(self, vhosts):
|
def _update_vhosts_addrs_ssl(self, vhosts: Iterable[obj.VirtualHost]) -> None:
|
||||||
"""Update a list of raw parsed vhosts to include global address sslishness
|
"""Update a list of raw parsed vhosts to include global address sslishness
|
||||||
"""
|
"""
|
||||||
addr_to_ssl = self._build_addr_to_ssl()
|
addr_to_ssl = self._build_addr_to_ssl()
|
||||||
@@ -168,7 +174,7 @@ class NginxParser:
|
|||||||
if addr.ssl:
|
if addr.ssl:
|
||||||
vhost.ssl = True
|
vhost.ssl = True
|
||||||
|
|
||||||
def _get_included_directives(self, block):
|
def _get_included_directives(self, block: UnspacedList) -> UnspacedList:
|
||||||
"""Returns array with the "include" directives expanded out by
|
"""Returns array with the "include" directives expanded out by
|
||||||
concatenating the contents of the included file to the block.
|
concatenating the contents of the included file to the block.
|
||||||
|
|
||||||
@@ -188,7 +194,7 @@ class NginxParser:
|
|||||||
pass
|
pass
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _parse_files(self, filepath, override=False):
|
def _parse_files(self, filepath: str, override: bool = False) -> List[UnspacedList]:
|
||||||
"""Parse files from a glob
|
"""Parse files from a glob
|
||||||
|
|
||||||
:param str filepath: Nginx config file path
|
:param str filepath: Nginx config file path
|
||||||
@@ -219,7 +225,7 @@ class NginxParser:
|
|||||||
logger.warning("Could not parse file: %s due to %s", item, err)
|
logger.warning("Could not parse file: %s due to %s", item, err)
|
||||||
return trees
|
return trees
|
||||||
|
|
||||||
def _find_config_root(self):
|
def _find_config_root(self) -> str:
|
||||||
"""Return the Nginx Configuration Root file."""
|
"""Return the Nginx Configuration Root file."""
|
||||||
location = ['nginx.conf']
|
location = ['nginx.conf']
|
||||||
|
|
||||||
@@ -230,7 +236,7 @@ class NginxParser:
|
|||||||
raise errors.NoInstallationError(
|
raise errors.NoInstallationError(
|
||||||
"Could not find Nginx root configuration file (nginx.conf)")
|
"Could not find Nginx root configuration file (nginx.conf)")
|
||||||
|
|
||||||
def filedump(self, ext='tmp', lazy=True):
|
def filedump(self, ext: str = 'tmp', lazy: bool = True) -> None:
|
||||||
"""Dumps parsed configurations into files.
|
"""Dumps parsed configurations into files.
|
||||||
|
|
||||||
:param str ext: The file extension to use for the dumped files. If
|
:param str ext: The file extension to use for the dumped files. If
|
||||||
@@ -255,7 +261,7 @@ class NginxParser:
|
|||||||
except IOError:
|
except IOError:
|
||||||
logger.error("Could not open file for writing: %s", filename)
|
logger.error("Could not open file for writing: %s", filename)
|
||||||
|
|
||||||
def parse_server(self, server):
|
def parse_server(self, server: UnspacedList) -> Dict[str, Any]:
|
||||||
"""Parses a list of server directives, accounting for global address sslishness.
|
"""Parses a list of server directives, accounting for global address sslishness.
|
||||||
|
|
||||||
:param list server: list of directives in a server block
|
:param list server: list of directives in a server block
|
||||||
@@ -266,7 +272,7 @@ class NginxParser:
|
|||||||
_apply_global_addr_ssl(addr_to_ssl, parsed_server)
|
_apply_global_addr_ssl(addr_to_ssl, parsed_server)
|
||||||
return parsed_server
|
return parsed_server
|
||||||
|
|
||||||
def has_ssl_on_directive(self, vhost):
|
def has_ssl_on_directive(self, vhost: obj.VirtualHost) -> bool:
|
||||||
"""Does vhost have ssl on for all ports?
|
"""Does vhost have ssl on for all ports?
|
||||||
|
|
||||||
:param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost in question
|
:param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost in question
|
||||||
@@ -284,7 +290,8 @@ class NginxParser:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def add_server_directives(self, vhost, directives, insert_at_top=False):
|
def add_server_directives(self, vhost: obj.VirtualHost, directives: List[Any],
|
||||||
|
insert_at_top: bool = False) -> None:
|
||||||
"""Add directives to the server block identified by vhost.
|
"""Add directives to the server block identified by vhost.
|
||||||
|
|
||||||
This method modifies vhost to be fully consistent with the new directives.
|
This method modifies vhost to be fully consistent with the new directives.
|
||||||
@@ -305,7 +312,8 @@ class NginxParser:
|
|||||||
self._modify_server_directives(vhost,
|
self._modify_server_directives(vhost,
|
||||||
functools.partial(_add_directives, directives, insert_at_top))
|
functools.partial(_add_directives, directives, insert_at_top))
|
||||||
|
|
||||||
def update_or_add_server_directives(self, vhost, directives, insert_at_top=False):
|
def update_or_add_server_directives(self, vhost: obj.VirtualHost, directives: List[Any],
|
||||||
|
insert_at_top: bool = False) -> None:
|
||||||
"""Add or replace directives in the server block identified by vhost.
|
"""Add or replace directives in the server block identified by vhost.
|
||||||
|
|
||||||
This method modifies vhost to be fully consistent with the new directives.
|
This method modifies vhost to be fully consistent with the new directives.
|
||||||
@@ -327,7 +335,8 @@ class NginxParser:
|
|||||||
self._modify_server_directives(vhost,
|
self._modify_server_directives(vhost,
|
||||||
functools.partial(_update_or_add_directives, directives, insert_at_top))
|
functools.partial(_update_or_add_directives, directives, insert_at_top))
|
||||||
|
|
||||||
def remove_server_directives(self, vhost, directive_name, match_func=None):
|
def remove_server_directives(self, vhost: obj.VirtualHost, directive_name: str,
|
||||||
|
match_func: Optional[Callable[[Any], bool]] = None) -> None:
|
||||||
"""Remove all directives of type directive_name.
|
"""Remove all directives of type directive_name.
|
||||||
|
|
||||||
:param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost
|
:param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost
|
||||||
@@ -339,7 +348,8 @@ class NginxParser:
|
|||||||
self._modify_server_directives(vhost,
|
self._modify_server_directives(vhost,
|
||||||
functools.partial(_remove_directives, directive_name, match_func))
|
functools.partial(_remove_directives, directive_name, match_func))
|
||||||
|
|
||||||
def _update_vhost_based_on_new_directives(self, vhost, directives_list):
|
def _update_vhost_based_on_new_directives(self, vhost: obj.VirtualHost,
|
||||||
|
directives_list: UnspacedList) -> None:
|
||||||
new_server = self._get_included_directives(directives_list)
|
new_server = self._get_included_directives(directives_list)
|
||||||
parsed_server = self.parse_server(new_server)
|
parsed_server = self.parse_server(new_server)
|
||||||
vhost.addrs = parsed_server['addrs']
|
vhost.addrs = parsed_server['addrs']
|
||||||
@@ -347,7 +357,8 @@ class NginxParser:
|
|||||||
vhost.names = parsed_server['names']
|
vhost.names = parsed_server['names']
|
||||||
vhost.raw = new_server
|
vhost.raw = new_server
|
||||||
|
|
||||||
def _modify_server_directives(self, vhost, block_func):
|
def _modify_server_directives(self, vhost: obj.VirtualHost,
|
||||||
|
block_func: Callable[[List[Any]], None]) -> None:
|
||||||
filename = vhost.filep
|
filename = vhost.filep
|
||||||
try:
|
try:
|
||||||
result = self.parsed[filename]
|
result = self.parsed[filename]
|
||||||
@@ -364,7 +375,7 @@ class NginxParser:
|
|||||||
|
|
||||||
def duplicate_vhost(self, vhost_template: obj.VirtualHost,
|
def duplicate_vhost(self, vhost_template: obj.VirtualHost,
|
||||||
remove_singleton_listen_params: bool = False,
|
remove_singleton_listen_params: bool = False,
|
||||||
only_directives: Optional[List] = None) -> obj.VirtualHost:
|
only_directives: Optional[List[Any]] = None) -> obj.VirtualHost:
|
||||||
"""Duplicate the vhost in the configuration files.
|
"""Duplicate the vhost in the configuration files.
|
||||||
|
|
||||||
:param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost_template: The vhost
|
:param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost_template: The vhost
|
||||||
@@ -417,7 +428,7 @@ class NginxParser:
|
|||||||
return new_vhost
|
return new_vhost
|
||||||
|
|
||||||
|
|
||||||
def _parse_ssl_options(ssl_options):
|
def _parse_ssl_options(ssl_options: Optional[str]) -> List[UnspacedList]:
|
||||||
if ssl_options is not None:
|
if ssl_options is not None:
|
||||||
try:
|
try:
|
||||||
with io.open(ssl_options, "r", encoding="utf-8") as _file:
|
with io.open(ssl_options, "r", encoding="utf-8") as _file:
|
||||||
@@ -429,9 +440,12 @@ def _parse_ssl_options(ssl_options):
|
|||||||
"Only UTF-8 encoding is supported.", ssl_options)
|
"Only UTF-8 encoding is supported.", ssl_options)
|
||||||
except pyparsing.ParseBaseException as err:
|
except pyparsing.ParseBaseException as err:
|
||||||
logger.warning("Could not parse file: %s due to %s", ssl_options, err)
|
logger.warning("Could not parse file: %s due to %s", ssl_options, err)
|
||||||
return []
|
return UnspacedList([])
|
||||||
|
|
||||||
def _do_for_subarray(entry, condition, func, path=None):
|
|
||||||
|
def _do_for_subarray(entry: List[Any], condition: Callable[[List[Any]], bool],
|
||||||
|
func: Callable[[List[Any], List[int]], None],
|
||||||
|
path: Optional[List[int]] = None) -> None:
|
||||||
"""Executes a function for a subarray of a nested array if it matches
|
"""Executes a function for a subarray of a nested array if it matches
|
||||||
the given condition.
|
the given condition.
|
||||||
|
|
||||||
@@ -450,7 +464,7 @@ def _do_for_subarray(entry, condition, func, path=None):
|
|||||||
_do_for_subarray(item, condition, func, path + [index])
|
_do_for_subarray(item, condition, func, path + [index])
|
||||||
|
|
||||||
|
|
||||||
def get_best_match(target_name, names):
|
def get_best_match(target_name: str, names: Iterable[str]) -> Tuple[Optional[str], Optional[str]]:
|
||||||
"""Finds the best match for target_name out of names using the Nginx
|
"""Finds the best match for target_name out of names using the Nginx
|
||||||
name-matching rules (exact > longest wildcard starting with * >
|
name-matching rules (exact > longest wildcard starting with * >
|
||||||
longest wildcard ending with * > regex).
|
longest wildcard ending with * > regex).
|
||||||
@@ -479,29 +493,29 @@ def get_best_match(target_name, names):
|
|||||||
if exact:
|
if exact:
|
||||||
# There can be more than one exact match; e.g. eff.org, .eff.org
|
# There can be more than one exact match; e.g. eff.org, .eff.org
|
||||||
match = min(exact, key=len)
|
match = min(exact, key=len)
|
||||||
return ('exact', match)
|
return 'exact', match
|
||||||
if wildcard_start:
|
if wildcard_start:
|
||||||
# Return the longest wildcard
|
# Return the longest wildcard
|
||||||
match = max(wildcard_start, key=len)
|
match = max(wildcard_start, key=len)
|
||||||
return ('wildcard_start', match)
|
return 'wildcard_start', match
|
||||||
if wildcard_end:
|
if wildcard_end:
|
||||||
# Return the longest wildcard
|
# Return the longest wildcard
|
||||||
match = max(wildcard_end, key=len)
|
match = max(wildcard_end, key=len)
|
||||||
return ('wildcard_end', match)
|
return 'wildcard_end', match
|
||||||
if regex:
|
if regex:
|
||||||
# Just return the first one for now
|
# Just return the first one for now
|
||||||
match = regex[0]
|
match = regex[0]
|
||||||
return ('regex', match)
|
return 'regex', match
|
||||||
|
|
||||||
return (None, None)
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def _exact_match(target_name, name):
|
def _exact_match(target_name: str, name: str) -> bool:
|
||||||
target_lower = target_name.lower()
|
target_lower = target_name.lower()
|
||||||
return name.lower() in (target_lower, '.' + target_lower)
|
return name.lower() in (target_lower, '.' + target_lower)
|
||||||
|
|
||||||
|
|
||||||
def _wildcard_match(target_name, name, start):
|
def _wildcard_match(target_name: str, name: str, start: bool) -> bool:
|
||||||
# Degenerate case
|
# Degenerate case
|
||||||
if name == '*':
|
if name == '*':
|
||||||
return True
|
return True
|
||||||
@@ -526,7 +540,7 @@ def _wildcard_match(target_name, name, start):
|
|||||||
return target_name_lower.endswith('.' + name_lower)
|
return target_name_lower.endswith('.' + name_lower)
|
||||||
|
|
||||||
|
|
||||||
def _regex_match(target_name, name):
|
def _regex_match(target_name: str, name: str) -> bool:
|
||||||
# Must start with a tilde
|
# Must start with a tilde
|
||||||
if len(name) < 2 or name[0] != '~':
|
if len(name) < 2 or name[0] != '~':
|
||||||
return False
|
return False
|
||||||
@@ -534,13 +548,13 @@ def _regex_match(target_name, name):
|
|||||||
# After tilde is a perl-compatible regex
|
# After tilde is a perl-compatible regex
|
||||||
try:
|
try:
|
||||||
regex = re.compile(name[1:])
|
regex = re.compile(name[1:])
|
||||||
return re.match(regex, target_name)
|
return bool(re.match(regex, target_name))
|
||||||
except re.error: # pragma: no cover
|
except re.error: # pragma: no cover
|
||||||
# perl-compatible regexes are sometimes not recognized by python
|
# perl-compatible regexes are sometimes not recognized by python
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _is_include_directive(entry):
|
def _is_include_directive(entry: Any) -> bool:
|
||||||
"""Checks if an nginx parsed entry is an 'include' directive.
|
"""Checks if an nginx parsed entry is an 'include' directive.
|
||||||
|
|
||||||
:param list entry: the parsed entry
|
:param list entry: the parsed entry
|
||||||
@@ -552,7 +566,8 @@ def _is_include_directive(entry):
|
|||||||
len(entry) == 2 and entry[0] == 'include' and
|
len(entry) == 2 and entry[0] == 'include' and
|
||||||
isinstance(entry[1], str))
|
isinstance(entry[1], str))
|
||||||
|
|
||||||
def _is_ssl_on_directive(entry):
|
|
||||||
|
def _is_ssl_on_directive(entry: Any) -> bool:
|
||||||
"""Checks if an nginx parsed entry is an 'ssl on' directive.
|
"""Checks if an nginx parsed entry is an 'ssl on' directive.
|
||||||
|
|
||||||
:param list entry: the parsed entry
|
:param list entry: the parsed entry
|
||||||
@@ -564,14 +579,18 @@ def _is_ssl_on_directive(entry):
|
|||||||
len(entry) == 2 and entry[0] == 'ssl' and
|
len(entry) == 2 and entry[0] == 'ssl' and
|
||||||
entry[1] == 'on')
|
entry[1] == 'on')
|
||||||
|
|
||||||
def _add_directives(directives, insert_at_top, block):
|
|
||||||
|
def _add_directives(directives: List[Any], insert_at_top: bool,
|
||||||
|
block: UnspacedList) -> None:
|
||||||
"""Adds directives to a config block."""
|
"""Adds directives to a config block."""
|
||||||
for directive in directives:
|
for directive in directives:
|
||||||
_add_directive(block, directive, insert_at_top)
|
_add_directive(block, directive, insert_at_top)
|
||||||
if block and '\n' not in block[-1]: # could be " \n " or ["\n"] !
|
if block and '\n' not in block[-1]: # could be " \n " or ["\n"] !
|
||||||
block.append(nginxparser.UnspacedList('\n'))
|
block.append(nginxparser.UnspacedList('\n'))
|
||||||
|
|
||||||
def _update_or_add_directives(directives, insert_at_top, block):
|
|
||||||
|
def _update_or_add_directives(directives: List[Any], insert_at_top: bool,
|
||||||
|
block: UnspacedList) -> None:
|
||||||
"""Adds or replaces directives in a config block."""
|
"""Adds or replaces directives in a config block."""
|
||||||
for directive in directives:
|
for directive in directives:
|
||||||
_update_or_add_directive(block, directive, insert_at_top)
|
_update_or_add_directive(block, directive, insert_at_top)
|
||||||
@@ -584,7 +603,8 @@ REPEATABLE_DIRECTIVES = {'server_name', 'listen', INCLUDE, 'rewrite', 'add_heade
|
|||||||
COMMENT = ' managed by Certbot'
|
COMMENT = ' managed by Certbot'
|
||||||
COMMENT_BLOCK = [' ', '#', COMMENT]
|
COMMENT_BLOCK = [' ', '#', COMMENT]
|
||||||
|
|
||||||
def comment_directive(block, location):
|
|
||||||
|
def comment_directive(block: UnspacedList, location: int) -> None:
|
||||||
"""Add a ``#managed by Certbot`` comment to the end of the line at location.
|
"""Add a ``#managed by Certbot`` comment to the end of the line at location.
|
||||||
|
|
||||||
:param list block: The block containing the directive to be commented
|
:param list block: The block containing the directive to be commented
|
||||||
@@ -603,40 +623,45 @@ def comment_directive(block, location):
|
|||||||
if next_entry is not None and "\n" not in next_entry:
|
if next_entry is not None and "\n" not in next_entry:
|
||||||
block.insert(location + 2, '\n')
|
block.insert(location + 2, '\n')
|
||||||
|
|
||||||
def _comment_out_directive(block, location, include_location):
|
|
||||||
|
def _comment_out_directive(block: UnspacedList, location: int, include_location: str) -> None:
|
||||||
"""Comment out the line at location, with a note of explanation."""
|
"""Comment out the line at location, with a note of explanation."""
|
||||||
comment_message = ' duplicated in {0}'.format(include_location)
|
comment_message = ' duplicated in {0}'.format(include_location)
|
||||||
# add the end comment
|
# add the end comment
|
||||||
# create a dumpable object out of block[location] (so it includes the ;)
|
# create a dumpable object out of block[location] (so it includes the ;)
|
||||||
directive = block[location]
|
directive = block[location]
|
||||||
new_dir_block = nginxparser.UnspacedList([]) # just a wrapper
|
new_dir_block = nginxparser.UnspacedList([]) # just a wrapper
|
||||||
new_dir_block.append(directive)
|
new_dir_block.append(directive)
|
||||||
dumped = nginxparser.dumps(new_dir_block)
|
dumped = nginxparser.dumps(new_dir_block)
|
||||||
commented = dumped + ' #' + comment_message # add the comment directly to the one-line string
|
commented = dumped + ' #' + comment_message # add the comment directly to the one-line string
|
||||||
new_dir = nginxparser.loads(commented) # reload into UnspacedList
|
new_dir = nginxparser.loads(commented) # reload into UnspacedList
|
||||||
|
|
||||||
# add the beginning comment
|
# add the beginning comment
|
||||||
insert_location = 0
|
insert_location = 0
|
||||||
if new_dir[0].spaced[0] != new_dir[0][0]: # if there's whitespace at the beginning
|
if new_dir[0].spaced[0] != new_dir[0][0]: # if there's whitespace at the beginning
|
||||||
insert_location = 1
|
insert_location = 1
|
||||||
new_dir[0].spaced.insert(insert_location, "# ") # comment out the line
|
new_dir[0].spaced.insert(insert_location, "# ") # comment out the line
|
||||||
new_dir[0].spaced.append(";") # directly add in the ;, because now dumping won't work properly
|
new_dir[0].spaced.append(";") # directly add in the ;, because now dumping won't work properly
|
||||||
dumped = nginxparser.dumps(new_dir)
|
dumped = nginxparser.dumps(new_dir)
|
||||||
new_dir = nginxparser.loads(dumped) # reload into an UnspacedList
|
new_dir = nginxparser.loads(dumped) # reload into an UnspacedList
|
||||||
|
|
||||||
block[location] = new_dir[0] # set the now-single-line-comment directive back in place
|
block[location] = new_dir[0] # set the now-single-line-comment directive back in place
|
||||||
|
|
||||||
def _find_location(block, directive_name, match_func=None):
|
|
||||||
|
def _find_location(block: UnspacedList, directive_name: str,
|
||||||
|
match_func: Optional[Callable[[Any], bool]] = None) -> Optional[int]:
|
||||||
"""Finds the index of the first instance of directive_name in block.
|
"""Finds the index of the first instance of directive_name in block.
|
||||||
If no line exists, use None."""
|
If no line exists, use None."""
|
||||||
return next((index for index, line in enumerate(block) \
|
return next((index for index, line in enumerate(block) if (
|
||||||
if line and line[0] == directive_name and (match_func is None or match_func(line))), None)
|
line and line[0] == directive_name and (match_func is None or match_func(line)))), None)
|
||||||
|
|
||||||
def _is_whitespace_or_comment(directive):
|
|
||||||
|
def _is_whitespace_or_comment(directive: Sequence[Any]) -> bool:
|
||||||
"""Is this directive either a whitespace or comment directive?"""
|
"""Is this directive either a whitespace or comment directive?"""
|
||||||
return len(directive) == 0 or directive[0] == '#'
|
return len(directive) == 0 or directive[0] == '#'
|
||||||
|
|
||||||
def _add_directive(block, directive, insert_at_top):
|
|
||||||
|
def _add_directive(block: UnspacedList, directive: Sequence[Any], insert_at_top: bool) -> None:
|
||||||
if not isinstance(directive, nginxparser.UnspacedList):
|
if not isinstance(directive, nginxparser.UnspacedList):
|
||||||
directive = nginxparser.UnspacedList(directive)
|
directive = nginxparser.UnspacedList(directive)
|
||||||
if _is_whitespace_or_comment(directive):
|
if _is_whitespace_or_comment(directive):
|
||||||
@@ -653,10 +678,11 @@ def _add_directive(block, directive, insert_at_top):
|
|||||||
# handle flat include files
|
# handle flat include files
|
||||||
|
|
||||||
directive_name = directive[0]
|
directive_name = directive[0]
|
||||||
def can_append(loc, dir_name):
|
|
||||||
|
def can_append(loc: Optional[int], dir_name: str) -> bool:
|
||||||
""" Can we append this directive to the block? """
|
""" Can we append this directive to the block? """
|
||||||
return loc is None or (isinstance(dir_name, str)
|
return loc is None or (isinstance(dir_name, str)
|
||||||
and dir_name in REPEATABLE_DIRECTIVES)
|
and dir_name in REPEATABLE_DIRECTIVES)
|
||||||
|
|
||||||
err_fmt = 'tried to insert directive "{0}" but found conflicting "{1}".'
|
err_fmt = 'tried to insert directive "{0}" but found conflicting "{1}".'
|
||||||
|
|
||||||
@@ -672,10 +698,14 @@ def _add_directive(block, directive, insert_at_top):
|
|||||||
included_dir_name = included_directive[0]
|
included_dir_name = included_directive[0]
|
||||||
if (not _is_whitespace_or_comment(included_directive)
|
if (not _is_whitespace_or_comment(included_directive)
|
||||||
and not can_append(included_dir_loc, included_dir_name)):
|
and not can_append(included_dir_loc, included_dir_name)):
|
||||||
if block[included_dir_loc] != included_directive:
|
|
||||||
raise errors.MisconfigurationError(err_fmt.format(included_directive,
|
# By construction of can_append(), included_dir_loc cannot be None at that point
|
||||||
block[included_dir_loc]))
|
resolved_included_dir_loc = cast(int, included_dir_loc)
|
||||||
_comment_out_directive(block, included_dir_loc, directive[1])
|
|
||||||
|
if block[resolved_included_dir_loc] != included_directive:
|
||||||
|
raise errors.MisconfigurationError(err_fmt.format(
|
||||||
|
included_directive, block[resolved_included_dir_loc]))
|
||||||
|
_comment_out_directive(block, resolved_included_dir_loc, directive[1])
|
||||||
|
|
||||||
if can_append(location, directive_name):
|
if can_append(location, directive_name):
|
||||||
if insert_at_top:
|
if insert_at_top:
|
||||||
@@ -687,14 +717,22 @@ def _add_directive(block, directive, insert_at_top):
|
|||||||
else:
|
else:
|
||||||
block.append(directive)
|
block.append(directive)
|
||||||
comment_directive(block, len(block) - 1)
|
comment_directive(block, len(block) - 1)
|
||||||
elif block[location] != directive:
|
return
|
||||||
raise errors.MisconfigurationError(err_fmt.format(directive, block[location]))
|
|
||||||
|
|
||||||
def _update_directive(block, directive, location):
|
# By construction of can_append(), location cannot be None at that point
|
||||||
|
resolved_location = cast(int, location)
|
||||||
|
|
||||||
|
if block[resolved_location] != directive:
|
||||||
|
raise errors.MisconfigurationError(err_fmt.format(directive, block[resolved_location]))
|
||||||
|
|
||||||
|
|
||||||
|
def _update_directive(block: UnspacedList, directive: Sequence[Any], location: int) -> None:
|
||||||
block[location] = directive
|
block[location] = directive
|
||||||
comment_directive(block, location)
|
comment_directive(block, location)
|
||||||
|
|
||||||
def _update_or_add_directive(block, directive, insert_at_top):
|
|
||||||
|
def _update_or_add_directive(block: UnspacedList, directive: Sequence[Any],
|
||||||
|
insert_at_top: bool) -> None:
|
||||||
if not isinstance(directive, nginxparser.UnspacedList):
|
if not isinstance(directive, nginxparser.UnspacedList):
|
||||||
directive = nginxparser.UnspacedList(directive)
|
directive = nginxparser.UnspacedList(directive)
|
||||||
if _is_whitespace_or_comment(directive):
|
if _is_whitespace_or_comment(directive):
|
||||||
@@ -711,10 +749,13 @@ def _update_or_add_directive(block, directive, insert_at_top):
|
|||||||
|
|
||||||
_add_directive(block, directive, insert_at_top)
|
_add_directive(block, directive, insert_at_top)
|
||||||
|
|
||||||
def _is_certbot_comment(directive):
|
|
||||||
|
def _is_certbot_comment(directive: Sequence[Any]) -> bool:
|
||||||
return '#' in directive and COMMENT in directive
|
return '#' in directive and COMMENT in directive
|
||||||
|
|
||||||
def _remove_directives(directive_name, match_func, block):
|
|
||||||
|
def _remove_directives(directive_name: str, match_func: Callable[[Any], bool],
|
||||||
|
block: UnspacedList) -> None:
|
||||||
"""Removes directives of name directive_name from a config block if match_func matches.
|
"""Removes directives of name directive_name from a config block if match_func matches.
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
@@ -726,7 +767,9 @@ def _remove_directives(directive_name, match_func, block):
|
|||||||
del block[location + 1]
|
del block[location + 1]
|
||||||
del block[location]
|
del block[location]
|
||||||
|
|
||||||
def _apply_global_addr_ssl(addr_to_ssl, parsed_server):
|
|
||||||
|
def _apply_global_addr_ssl(addr_to_ssl: Mapping[Tuple[str, str], bool],
|
||||||
|
parsed_server: Dict[str, Any]) -> None:
|
||||||
"""Apply global sslishness information to the parsed server block
|
"""Apply global sslishness information to the parsed server block
|
||||||
"""
|
"""
|
||||||
for addr in parsed_server['addrs']:
|
for addr in parsed_server['addrs']:
|
||||||
@@ -734,7 +777,8 @@ def _apply_global_addr_ssl(addr_to_ssl, parsed_server):
|
|||||||
if addr.ssl:
|
if addr.ssl:
|
||||||
parsed_server['ssl'] = True
|
parsed_server['ssl'] = True
|
||||||
|
|
||||||
def _parse_server_raw(server):
|
|
||||||
|
def _parse_server_raw(server: UnspacedList) -> Dict[str, Any]:
|
||||||
"""Parses a list of server directives.
|
"""Parses a list of server directives.
|
||||||
|
|
||||||
:param list server: list of directives in a server block
|
:param list server: list of directives in a server block
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ raw lists of tokens from pyparsing. """
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
from typing import Callable
|
||||||
|
from typing import Iterator
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import Sequence
|
||||||
|
from typing import Tuple
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
from certbot import errors
|
from certbot import errors
|
||||||
|
|
||||||
@@ -23,24 +30,24 @@ class Parsable:
|
|||||||
|
|
||||||
__metaclass__ = abc.ABCMeta
|
__metaclass__ = abc.ABCMeta
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent: Optional["Parsable"] = None):
|
||||||
self._data: List[object] = []
|
self._data: List[Any] = []
|
||||||
self._tabs = None
|
self._tabs = None
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parsing_hooks(cls):
|
def parsing_hooks(cls) -> Tuple[Type["Block"], Type["Sentence"], Type["Statements"]]:
|
||||||
"""Returns object types that this class should be able to `parse` recusrively.
|
"""Returns object types that this class should be able to `parse` recusrively.
|
||||||
The order of the objects indicates the order in which the parser should
|
The order of the objects indicates the order in which the parser should
|
||||||
try to parse each subitem.
|
try to parse each subitem.
|
||||||
:returns: A list of Parsable classes.
|
:returns: A list of Parsable classes.
|
||||||
:rtype list:
|
:rtype list:
|
||||||
"""
|
"""
|
||||||
return (Block, Sentence, Statements)
|
return Block, Sentence, Statements
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def should_parse(lists):
|
def should_parse(lists: Any) -> bool:
|
||||||
""" Returns whether the contents of `lists` can be parsed into this object.
|
""" Returns whether the contents of `lists` can be parsed into this object.
|
||||||
|
|
||||||
:returns: Whether `lists` can be parsed as this object.
|
:returns: Whether `lists` can be parsed as this object.
|
||||||
@@ -49,7 +56,7 @@ class Parsable:
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def parse(self, raw_list, add_spaces=False):
|
def parse(self, raw_list: List[Any], add_spaces: bool = False) -> None:
|
||||||
""" Loads information into this object from underlying raw_list structure.
|
""" Loads information into this object from underlying raw_list structure.
|
||||||
Each Parsable object might make different assumptions about the structure of
|
Each Parsable object might make different assumptions about the structure of
|
||||||
raw_list.
|
raw_list.
|
||||||
@@ -64,7 +71,8 @@ class Parsable:
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def iterate(self, expanded=False, match=None):
|
def iterate(self, expanded: bool = False,
|
||||||
|
match: Optional[Callable[["Parsable"], bool]] = None) -> Iterator[Any]:
|
||||||
""" Iterates across this object. If this object is a leaf object, only yields
|
""" Iterates across this object. If this object is a leaf object, only yields
|
||||||
itself. If it contains references other parsing objects, and `expanded` is set,
|
itself. If it contains references other parsing objects, and `expanded` is set,
|
||||||
this function should first yield itself, then recursively iterate across all of them.
|
this function should first yield itself, then recursively iterate across all of them.
|
||||||
@@ -77,7 +85,7 @@ class Parsable:
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_tabs(self):
|
def get_tabs(self) -> str:
|
||||||
""" Guess at the tabbing style of this parsed object, based on whitespace.
|
""" Guess at the tabbing style of this parsed object, based on whitespace.
|
||||||
|
|
||||||
If this object is a leaf, it deducts the tabbing based on its own contents.
|
If this object is a leaf, it deducts the tabbing based on its own contents.
|
||||||
@@ -90,7 +98,7 @@ class Parsable:
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def set_tabs(self, tabs=" "):
|
def set_tabs(self, tabs: str = " ") -> None:
|
||||||
"""This tries to set and alter the tabbing of the current object to a desired
|
"""This tries to set and alter the tabbing of the current object to a desired
|
||||||
whitespace string. Primarily meant for objects that were constructed, so they
|
whitespace string. Primarily meant for objects that were constructed, so they
|
||||||
can conform to surrounding whitespace.
|
can conform to surrounding whitespace.
|
||||||
@@ -99,7 +107,7 @@ class Parsable:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def dump(self, include_spaces=False):
|
def dump(self, include_spaces: bool = False) -> List[Any]:
|
||||||
""" Dumps back to pyparsing-like list tree. The opposite of `parse`.
|
""" Dumps back to pyparsing-like list tree. The opposite of `parse`.
|
||||||
|
|
||||||
Note: if this object has not been modified, `dump` with `include_spaces=True`
|
Note: if this object has not been modified, `dump` with `include_spaces=True`
|
||||||
@@ -121,17 +129,17 @@ class Statements(Parsable):
|
|||||||
an extra `_trailing_whitespace` string to keep track of the whitespace that does not
|
an extra `_trailing_whitespace` string to keep track of the whitespace that does not
|
||||||
precede any more statements.
|
precede any more statements.
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent: Optional[Parsable] = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._trailing_whitespace = None
|
self._trailing_whitespace = None
|
||||||
|
|
||||||
# ======== Begin overridden functions
|
# ======== Begin overridden functions
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_parse(lists):
|
def should_parse(lists: Any) -> bool:
|
||||||
return isinstance(lists, list)
|
return isinstance(lists, list)
|
||||||
|
|
||||||
def set_tabs(self, tabs=" "):
|
def set_tabs(self, tabs: str = " ") -> None:
|
||||||
""" Sets the tabbing for this set of statements. Does this by calling `set_tabs`
|
""" Sets the tabbing for this set of statements. Does this by calling `set_tabs`
|
||||||
on each of the child statements.
|
on each of the child statements.
|
||||||
|
|
||||||
@@ -144,7 +152,7 @@ class Statements(Parsable):
|
|||||||
if self.parent is not None:
|
if self.parent is not None:
|
||||||
self._trailing_whitespace = "\n" + self.parent.get_tabs()
|
self._trailing_whitespace = "\n" + self.parent.get_tabs()
|
||||||
|
|
||||||
def parse(self, raw_list, add_spaces=False):
|
def parse(self, raw_list: List[Any], add_spaces: bool = False) -> None:
|
||||||
""" Parses a list of statements.
|
""" Parses a list of statements.
|
||||||
Expects all elements in `raw_list` to be parseable by `type(self).parsing_hooks`,
|
Expects all elements in `raw_list` to be parseable by `type(self).parsing_hooks`,
|
||||||
with an optional whitespace string at the last index of `raw_list`.
|
with an optional whitespace string at the last index of `raw_list`.
|
||||||
@@ -157,14 +165,14 @@ class Statements(Parsable):
|
|||||||
raw_list = raw_list[:-1]
|
raw_list = raw_list[:-1]
|
||||||
self._data = [parse_raw(elem, self, add_spaces) for elem in raw_list]
|
self._data = [parse_raw(elem, self, add_spaces) for elem in raw_list]
|
||||||
|
|
||||||
def get_tabs(self):
|
def get_tabs(self) -> str:
|
||||||
""" Takes a guess at the tabbing of all contained Statements by retrieving the
|
""" Takes a guess at the tabbing of all contained Statements by retrieving the
|
||||||
tabbing of the first Statement."""
|
tabbing of the first Statement."""
|
||||||
if self._data:
|
if self._data:
|
||||||
return self._data[0].get_tabs()
|
return self._data[0].get_tabs()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def dump(self, include_spaces=False):
|
def dump(self, include_spaces: bool = False) -> List[Any]:
|
||||||
""" Dumps this object by first dumping each statement, then appending its
|
""" Dumps this object by first dumping each statement, then appending its
|
||||||
trailing whitespace (if `include_spaces` is set) """
|
trailing whitespace (if `include_spaces` is set) """
|
||||||
data = super().dump(include_spaces)
|
data = super().dump(include_spaces)
|
||||||
@@ -172,7 +180,8 @@ class Statements(Parsable):
|
|||||||
return data + [self._trailing_whitespace]
|
return data + [self._trailing_whitespace]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def iterate(self, expanded=False, match=None):
|
def iterate(self, expanded: bool = False,
|
||||||
|
match: Optional[Callable[["Parsable"], bool]] = None) -> Iterator[Any]:
|
||||||
""" Combines each statement's iterator. """
|
""" Combines each statement's iterator. """
|
||||||
for elem in self._data:
|
for elem in self._data:
|
||||||
for sub_elem in elem.iterate(expanded, match):
|
for sub_elem in elem.iterate(expanded, match):
|
||||||
@@ -181,7 +190,7 @@ class Statements(Parsable):
|
|||||||
# ======== End overridden functions
|
# ======== End overridden functions
|
||||||
|
|
||||||
|
|
||||||
def _space_list(list_):
|
def _space_list(list_: Sequence[Any]) -> List[str]:
|
||||||
""" Inserts whitespace between adjacent non-whitespace tokens. """
|
""" Inserts whitespace between adjacent non-whitespace tokens. """
|
||||||
spaced_statement: List[str] = []
|
spaced_statement: List[str] = []
|
||||||
for i in reversed(range(len(list_))):
|
for i in reversed(range(len(list_))):
|
||||||
@@ -197,7 +206,7 @@ class Sentence(Parsable):
|
|||||||
# ======== Begin overridden functions
|
# ======== Begin overridden functions
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_parse(lists):
|
def should_parse(lists: Any) -> bool:
|
||||||
""" Returns True if `lists` can be parseable as a `Sentence`-- that is,
|
""" Returns True if `lists` can be parseable as a `Sentence`-- that is,
|
||||||
every element is a string type.
|
every element is a string type.
|
||||||
|
|
||||||
@@ -205,38 +214,39 @@ class Sentence(Parsable):
|
|||||||
|
|
||||||
:returns: whether this lists is parseable by `Sentence`.
|
:returns: whether this lists is parseable by `Sentence`.
|
||||||
"""
|
"""
|
||||||
return isinstance(lists, list) and len(lists) > 0 and \
|
return (isinstance(lists, list) and len(lists) > 0 and
|
||||||
all(isinstance(elem, str) for elem in lists)
|
all(isinstance(elem, str) for elem in lists))
|
||||||
|
|
||||||
def parse(self, raw_list, add_spaces=False):
|
def parse(self, raw_list: List[Any], add_spaces: bool = False) -> None:
|
||||||
""" Parses a list of string types into this object.
|
""" Parses a list of string types into this object.
|
||||||
If add_spaces is set, adds whitespace tokens between adjacent non-whitespace tokens."""
|
If add_spaces is set, adds whitespace tokens between adjacent non-whitespace tokens."""
|
||||||
if add_spaces:
|
if add_spaces:
|
||||||
raw_list = _space_list(raw_list)
|
raw_list = _space_list(raw_list)
|
||||||
if not isinstance(raw_list, list) or \
|
if (not isinstance(raw_list, list)
|
||||||
any(not isinstance(elem, str) for elem in raw_list):
|
or any(not isinstance(elem, str) for elem in raw_list)):
|
||||||
raise errors.MisconfigurationError("Sentence parsing expects a list of string types.")
|
raise errors.MisconfigurationError("Sentence parsing expects a list of string types.")
|
||||||
self._data = raw_list
|
self._data = raw_list
|
||||||
|
|
||||||
def iterate(self, expanded=False, match=None):
|
def iterate(self, expanded: bool = False,
|
||||||
|
match: Optional[Callable[[Parsable], bool]] = None) -> Iterator[Any]:
|
||||||
""" Simply yields itself. """
|
""" Simply yields itself. """
|
||||||
if match is None or match(self):
|
if match is None or match(self):
|
||||||
yield self
|
yield self
|
||||||
|
|
||||||
def set_tabs(self, tabs=" "):
|
def set_tabs(self, tabs: str = " ") -> None:
|
||||||
""" Sets the tabbing on this sentence. Inserts a newline and `tabs` at the
|
""" Sets the tabbing on this sentence. Inserts a newline and `tabs` at the
|
||||||
beginning of `self._data`. """
|
beginning of `self._data`. """
|
||||||
if self._data[0].isspace():
|
if self._data[0].isspace():
|
||||||
return
|
return
|
||||||
self._data.insert(0, "\n" + tabs)
|
self._data.insert(0, "\n" + tabs)
|
||||||
|
|
||||||
def dump(self, include_spaces=False):
|
def dump(self, include_spaces: bool = False) -> List[Any]:
|
||||||
""" Dumps this sentence. If include_spaces is set, includes whitespace tokens."""
|
""" Dumps this sentence. If include_spaces is set, includes whitespace tokens."""
|
||||||
if not include_spaces:
|
if not include_spaces:
|
||||||
return self.words
|
return self.words
|
||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
def get_tabs(self):
|
def get_tabs(self) -> str:
|
||||||
""" Guesses at the tabbing of this sentence. If the first element is whitespace,
|
""" Guesses at the tabbing of this sentence. If the first element is whitespace,
|
||||||
returns the whitespace after the rightmost newline in the string. """
|
returns the whitespace after the rightmost newline in the string. """
|
||||||
first = self._data[0]
|
first = self._data[0]
|
||||||
@@ -248,14 +258,14 @@ class Sentence(Parsable):
|
|||||||
# ======== End overridden functions
|
# ======== End overridden functions
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def words(self):
|
def words(self) -> List[str]:
|
||||||
""" Iterates over words, but without spaces. Like Unspaced List. """
|
""" Iterates over words, but without spaces. Like Unspaced List. """
|
||||||
return [word.strip("\"\'") for word in self._data if not word.isspace()]
|
return [word.strip("\"\'") for word in self._data if not word.isspace()]
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index: int) -> str:
|
||||||
return self.words[index]
|
return self.words[index]
|
||||||
|
|
||||||
def __contains__(self, word):
|
def __contains__(self, word: str) -> bool:
|
||||||
return word in self.words
|
return word in self.words
|
||||||
|
|
||||||
|
|
||||||
@@ -270,13 +280,13 @@ class Block(Parsable):
|
|||||||
names = ["block", " ", "name", " "]
|
names = ["block", " ", "name", " "]
|
||||||
contents = [["\n ", "content", " ", "1"], ["\n ", "content", " ", "2"], "\n"]
|
contents = [["\n ", "content", " ", "1"], ["\n ", "content", " ", "2"], "\n"]
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent: Optional[Parsable] = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.names: Sentence = None
|
self.names: Optional[Sentence] = None
|
||||||
self.contents: Block = None
|
self.contents: Optional[Block] = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_parse(lists):
|
def should_parse(lists: Any) -> bool:
|
||||||
""" Returns True if `lists` can be parseable as a `Block`-- that is,
|
""" Returns True if `lists` can be parseable as a `Block`-- that is,
|
||||||
it's got a length of 2, the first element is a `Sentence` and the second can be
|
it's got a length of 2, the first element is a `Sentence` and the second can be
|
||||||
a `Statements`.
|
a `Statements`.
|
||||||
@@ -287,13 +297,14 @@ class Block(Parsable):
|
|||||||
return isinstance(lists, list) and len(lists) == 2 and \
|
return isinstance(lists, list) and len(lists) == 2 and \
|
||||||
Sentence.should_parse(lists[0]) and isinstance(lists[1], list)
|
Sentence.should_parse(lists[0]) and isinstance(lists[1], list)
|
||||||
|
|
||||||
def set_tabs(self, tabs=" "):
|
def set_tabs(self, tabs: str = " ") -> None:
|
||||||
""" Sets tabs by setting equivalent tabbing on names, then adding tabbing
|
""" Sets tabs by setting equivalent tabbing on names, then adding tabbing
|
||||||
to contents."""
|
to contents."""
|
||||||
self.names.set_tabs(tabs)
|
self.names.set_tabs(tabs)
|
||||||
self.contents.set_tabs(tabs + " ")
|
self.contents.set_tabs(tabs + " ")
|
||||||
|
|
||||||
def iterate(self, expanded=False, match=None):
|
def iterate(self, expanded: bool = False,
|
||||||
|
match: Optional[Callable[[Parsable], bool]] = None) -> Iterator[Any]:
|
||||||
""" Iterator over self, and if expanded is set, over its contents. """
|
""" Iterator over self, and if expanded is set, over its contents. """
|
||||||
if match is None or match(self):
|
if match is None or match(self):
|
||||||
yield self
|
yield self
|
||||||
@@ -301,7 +312,7 @@ class Block(Parsable):
|
|||||||
for elem in self.contents.iterate(expanded, match):
|
for elem in self.contents.iterate(expanded, match):
|
||||||
yield elem
|
yield elem
|
||||||
|
|
||||||
def parse(self, raw_list, add_spaces=False):
|
def parse(self, raw_list: List[Any], add_spaces: bool = False) -> None:
|
||||||
""" Parses a list that resembles a block.
|
""" Parses a list that resembles a block.
|
||||||
|
|
||||||
The assumptions that this routine makes are:
|
The assumptions that this routine makes are:
|
||||||
@@ -323,11 +334,12 @@ class Block(Parsable):
|
|||||||
self.contents.parse(raw_list[1], add_spaces)
|
self.contents.parse(raw_list[1], add_spaces)
|
||||||
self._data = [self.names, self.contents]
|
self._data = [self.names, self.contents]
|
||||||
|
|
||||||
def get_tabs(self):
|
def get_tabs(self) -> str:
|
||||||
""" Guesses tabbing by retrieving tabbing guess of self.names. """
|
""" Guesses tabbing by retrieving tabbing guess of self.names. """
|
||||||
return self.names.get_tabs()
|
return self.names.get_tabs()
|
||||||
|
|
||||||
def _is_comment(parsed_obj):
|
|
||||||
|
def _is_comment(parsed_obj: Parsable) -> bool:
|
||||||
""" Checks whether parsed_obj is a comment.
|
""" Checks whether parsed_obj is a comment.
|
||||||
|
|
||||||
:param .Parsable parsed_obj:
|
:param .Parsable parsed_obj:
|
||||||
@@ -339,7 +351,8 @@ def _is_comment(parsed_obj):
|
|||||||
return False
|
return False
|
||||||
return parsed_obj.words[0] == "#"
|
return parsed_obj.words[0] == "#"
|
||||||
|
|
||||||
def _is_certbot_comment(parsed_obj):
|
|
||||||
|
def _is_certbot_comment(parsed_obj: Parsable) -> bool:
|
||||||
""" Checks whether parsed_obj is a "managed by Certbot" comment.
|
""" Checks whether parsed_obj is a "managed by Certbot" comment.
|
||||||
|
|
||||||
:param .Parsable parsed_obj:
|
:param .Parsable parsed_obj:
|
||||||
@@ -356,7 +369,8 @@ def _is_certbot_comment(parsed_obj):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _certbot_comment(parent, preceding_spaces=4):
|
|
||||||
|
def _certbot_comment(parent: Parsable, preceding_spaces: int = 4) -> Sentence:
|
||||||
""" A "Managed by Certbot" comment.
|
""" A "Managed by Certbot" comment.
|
||||||
:param int preceding_spaces: Number of spaces between the end of the previous
|
:param int preceding_spaces: Number of spaces between the end of the previous
|
||||||
statement and the comment.
|
statement and the comment.
|
||||||
@@ -367,7 +381,8 @@ def _certbot_comment(parent, preceding_spaces=4):
|
|||||||
result.parse([" " * preceding_spaces] + COMMENT_BLOCK)
|
result.parse([" " * preceding_spaces] + COMMENT_BLOCK)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _choose_parser(parent, list_):
|
|
||||||
|
def _choose_parser(parent: Parsable, list_: Any) -> Parsable:
|
||||||
""" Choose a parser from type(parent).parsing_hooks, depending on whichever hook
|
""" Choose a parser from type(parent).parsing_hooks, depending on whichever hook
|
||||||
returns True first. """
|
returns True first. """
|
||||||
hooks = Parsable.parsing_hooks()
|
hooks = Parsable.parsing_hooks()
|
||||||
@@ -379,7 +394,8 @@ def _choose_parser(parent, list_):
|
|||||||
raise errors.MisconfigurationError(
|
raise errors.MisconfigurationError(
|
||||||
"None of the parsing hooks succeeded, so we don't know how to parse this set of lists.")
|
"None of the parsing hooks succeeded, so we don't know how to parse this set of lists.")
|
||||||
|
|
||||||
def parse_raw(lists_, parent=None, add_spaces=False):
|
|
||||||
|
def parse_raw(lists_: Any, parent: Optional[Parsable] = None, add_spaces: bool = False) -> Parsable:
|
||||||
""" Primary parsing factory function.
|
""" Primary parsing factory function.
|
||||||
|
|
||||||
:param list lists_: raw lists from pyparsing to parse.
|
:param list lists_: raw lists from pyparsing to parse.
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ class Addr:
|
|||||||
self.ipv6 = ipv6
|
self.ipv6 = ipv6
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromstring(cls, str_addr: str) -> 'Addr':
|
def fromstring(cls, str_addr: str) -> Optional['Addr']:
|
||||||
"""Initialize Addr from string."""
|
"""Initialize Addr from string."""
|
||||||
if str_addr.startswith('['):
|
if str_addr.startswith('['):
|
||||||
# ipv6 addresses starts with [
|
# ipv6 addresses starts with [
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ install_and_test = python {toxinidir}/tools/install_and_test.py
|
|||||||
dns_packages = certbot-dns-cloudflare certbot-dns-cloudxns certbot-dns-digitalocean certbot-dns-dnsimple certbot-dns-dnsmadeeasy certbot-dns-gehirn certbot-dns-google certbot-dns-linode certbot-dns-luadns certbot-dns-nsone certbot-dns-ovh certbot-dns-rfc2136 certbot-dns-route53 certbot-dns-sakuracloud
|
dns_packages = certbot-dns-cloudflare certbot-dns-cloudxns certbot-dns-digitalocean certbot-dns-dnsimple certbot-dns-dnsmadeeasy certbot-dns-gehirn certbot-dns-google certbot-dns-linode certbot-dns-luadns certbot-dns-nsone certbot-dns-ovh certbot-dns-rfc2136 certbot-dns-route53 certbot-dns-sakuracloud
|
||||||
win_all_packages = acme[test] certbot[test] {[base]dns_packages} certbot-nginx
|
win_all_packages = acme[test] certbot[test] {[base]dns_packages} certbot-nginx
|
||||||
all_packages = {[base]win_all_packages} certbot-apache
|
all_packages = {[base]win_all_packages} certbot-apache
|
||||||
fully_typed_source_paths = acme/acme certbot/certbot certbot-ci/certbot_integration_tests certbot-ci/snap_integration_tests certbot-ci/windows_installer_integration_tests certbot-compatibility-test/certbot_compatibility_test certbot-dns-cloudflare/certbot_dns_cloudflare certbot-dns-cloudxns/certbot_dns_cloudxns certbot-dns-digitalocean/certbot_dns_digitalocean certbot-dns-dnsimple/certbot_dns_dnsimple certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy certbot-dns-gehirn/certbot_dns_gehirn certbot-dns-google/certbot_dns_google certbot-dns-linode/certbot_dns_linode certbot-dns-luadns/certbot_dns_luadns certbot-dns-nsone/certbot_dns_nsone certbot-dns-ovh/certbot_dns_ovh certbot-dns-rfc2136/certbot_dns_rfc2136 certbot-dns-route53/certbot_dns_route53 certbot-dns-sakuracloud/certbot_dns_sakuracloud tests/lock_test.py
|
fully_typed_source_paths = acme/acme certbot/certbot certbot-ci/certbot_integration_tests certbot-ci/snap_integration_tests certbot-ci/windows_installer_integration_tests certbot-compatibility-test/certbot_compatibility_test certbot-dns-cloudflare/certbot_dns_cloudflare certbot-dns-cloudxns/certbot_dns_cloudxns certbot-dns-digitalocean/certbot_dns_digitalocean certbot-dns-dnsimple/certbot_dns_dnsimple certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy certbot-dns-gehirn/certbot_dns_gehirn certbot-dns-google/certbot_dns_google certbot-dns-linode/certbot_dns_linode certbot-dns-luadns/certbot_dns_luadns certbot-dns-nsone/certbot_dns_nsone certbot-dns-ovh/certbot_dns_ovh certbot-dns-rfc2136/certbot_dns_rfc2136 certbot-dns-route53/certbot_dns_route53 certbot-dns-sakuracloud/certbot_dns_sakuracloud certbot-nginx/certbot_nginx tests/lock_test.py
|
||||||
partially_typed_source_paths = certbot-apache/certbot_apache certbot-nginx/certbot_nginx
|
partially_typed_source_paths = certbot-apache/certbot_apache
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
passenv =
|
passenv =
|
||||||
|
|||||||
Reference in New Issue
Block a user