Compare commits
19 Commits
update-pyt
...
parser_ref
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff0d851a1e | ||
|
|
5cfdfc80bd | ||
|
|
f4c962073e | ||
|
|
973c521c3e | ||
|
|
526997107b | ||
|
|
c1362b7437 | ||
|
|
371ece9ea6 | ||
|
|
dd762f3c79 | ||
|
|
8cfd228eee | ||
|
|
0aef744f29 | ||
|
|
4b77350b0a | ||
|
|
6b5c4c9f79 | ||
|
|
267a281a10 | ||
|
|
fbd7d20d47 | ||
|
|
41f24c73f5 | ||
|
|
f74b8ad2ad | ||
|
|
d8806a4c05 | ||
|
|
fd3644a4d7 | ||
|
|
0b545ab443 |
87
certbot-apache/certbot_apache/augeasparser.py
Normal file
87
certbot-apache/certbot_apache/augeasparser.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
""" Tests for ParserNode interface """
|
||||||
|
from certbot_apache import interfaces
|
||||||
|
|
||||||
|
|
||||||
|
class AugeasCommentNode(interfaces.CommentNode):
|
||||||
|
""" Augeas implementation of CommentNode interface """
|
||||||
|
ancestor = None
|
||||||
|
comment = ""
|
||||||
|
dirty = False
|
||||||
|
|
||||||
|
def __init__(self, comment, ancestor=None):
|
||||||
|
self.comment = comment
|
||||||
|
self.ancestor = ancestor
|
||||||
|
|
||||||
|
def save(self, msg): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AugeasDirectiveNode(interfaces.DirectiveNode):
|
||||||
|
""" Augeas implementation of DirectiveNode interface """
|
||||||
|
ancestor = None
|
||||||
|
parameters = tuple() # type: Tuple[str, ...]
|
||||||
|
dirty = False
|
||||||
|
enabled = True
|
||||||
|
name = ""
|
||||||
|
|
||||||
|
def __init__(self, name, parameters=tuple(), ancestor=None):
|
||||||
|
self.name = name
|
||||||
|
self.parameters = parameters
|
||||||
|
self.ancestor = ancestor
|
||||||
|
|
||||||
|
def save(self, msg): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_parameters(self, parameters): # pragma: no cover
|
||||||
|
self.parameters = tuple("CERTBOT_PASS_ASSERT")
|
||||||
|
|
||||||
|
|
||||||
|
class AugeasBlockNode(interfaces.BlockNode):
|
||||||
|
""" Augeas implementation of BlockNode interface """
|
||||||
|
ancestor = None
|
||||||
|
parameters = tuple() # type: Tuple[str, ...]
|
||||||
|
children = tuple() # type: Tuple[interfaces.ParserNode, ...]
|
||||||
|
dirty = False
|
||||||
|
enabled = True
|
||||||
|
name = ""
|
||||||
|
|
||||||
|
def __init__(self, name, parameters=tuple(), ancestor=None):
|
||||||
|
self.name = name
|
||||||
|
self.parameters = parameters
|
||||||
|
self.ancestor = ancestor
|
||||||
|
|
||||||
|
def save(self, msg): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_child_block(self, name, parameters=None, position=None): # pragma: no cover
|
||||||
|
new_block = AugeasBlockNode("CERTBOT_PASS_ASSERT", ancestor=self)
|
||||||
|
self.children += (new_block,)
|
||||||
|
return new_block
|
||||||
|
|
||||||
|
def add_child_directive(self, name, parameters=None, position=None): # pragma: no cover
|
||||||
|
new_dir = AugeasDirectiveNode("CERTBOT_PASS_ASSERT", ancestor=self)
|
||||||
|
self.children += (new_dir,)
|
||||||
|
return new_dir
|
||||||
|
|
||||||
|
def add_child_comment(self, comment="", position=None): # pragma: no cover
|
||||||
|
new_comment = AugeasCommentNode("CERTBOT_PASS_ASSERT", ancestor=self)
|
||||||
|
self.children += (new_comment,)
|
||||||
|
return new_comment
|
||||||
|
|
||||||
|
def find_blocks(self, name, exclude=True): # pragma: no cover
|
||||||
|
return [AugeasBlockNode("CERTBOT_PASS_ASSERT", ancestor=self)]
|
||||||
|
|
||||||
|
def find_directives(self, name, exclude=True): # pragma: no cover
|
||||||
|
return [AugeasDirectiveNode("CERTBOT_PASS_ASSERT", ancestor=self)]
|
||||||
|
|
||||||
|
def find_comments(self, comment, exact=False): # pragma: no cover
|
||||||
|
return [AugeasCommentNode("CERTBOT_PASS_ASSERT", ancestor=self)]
|
||||||
|
|
||||||
|
def delete_child(self, child): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_parameters(self, parameters): # pragma: no cover
|
||||||
|
self.parameters = tuple("CERTBOT_PASS_ASSERT")
|
||||||
|
|
||||||
|
def unsaved_files(self): # pragma: no cover
|
||||||
|
return ["CERTBOT_PASS_ASSERT"]
|
||||||
@@ -29,6 +29,7 @@ from certbot.plugins.util import path_surgery
|
|||||||
from certbot.plugins.enhancements import AutoHSTSEnhancement
|
from certbot.plugins.enhancements import AutoHSTSEnhancement
|
||||||
|
|
||||||
from certbot_apache import apache_util
|
from certbot_apache import apache_util
|
||||||
|
from certbot_apache import augeasparser
|
||||||
from certbot_apache import constants
|
from certbot_apache import constants
|
||||||
from certbot_apache import display_ops
|
from certbot_apache import display_ops
|
||||||
from certbot_apache import http_01
|
from certbot_apache import http_01
|
||||||
@@ -280,6 +281,10 @@ class ApacheConfigurator(common.Installer):
|
|||||||
"Unable to create a lock file in {0}. Are you running"
|
"Unable to create a lock file in {0}. Are you running"
|
||||||
" Certbot with sufficient privileges to modify your"
|
" Certbot with sufficient privileges to modify your"
|
||||||
" Apache configuration?".format(self.option("server_root")))
|
" Apache configuration?".format(self.option("server_root")))
|
||||||
|
|
||||||
|
# TODO: apache-parser-v2
|
||||||
|
self.parser_root = augeasparser.AugeasBlockNode(None, ancestor=None)
|
||||||
|
|
||||||
self._prepared = True
|
self._prepared = True
|
||||||
|
|
||||||
def save(self, title=None, temporary=False):
|
def save(self, title=None, temporary=False):
|
||||||
@@ -507,10 +512,25 @@ class ApacheConfigurator(common.Installer):
|
|||||||
"cert_key": self.parser.find_dir("SSLCertificateKeyFile",
|
"cert_key": self.parser.find_dir("SSLCertificateKeyFile",
|
||||||
None, vhost.path)}
|
None, vhost.path)}
|
||||||
|
|
||||||
|
# TODO: apache-parser-v2
|
||||||
|
# We need to add a BlockNode reference to obj.VirtualHost
|
||||||
|
# v2_path = {"cert_path": vhost.node.find_directives(
|
||||||
|
# "SSLCertificateFile"),
|
||||||
|
# "cert_key": vhost.node.find_directives(
|
||||||
|
# "SSLCertificateKeyFile")}
|
||||||
|
# parserassert.legacy_assert_dir(path["cert_path"], v2_path["cert_path"])
|
||||||
|
# parserassert.legacy_assert_dir(path["cert_key"], v2_path["cert_key"])
|
||||||
|
|
||||||
# Only include if a certificate chain is specified
|
# Only include if a certificate chain is specified
|
||||||
if chain_path is not None:
|
if chain_path is not None:
|
||||||
path["chain_path"] = self.parser.find_dir(
|
path["chain_path"] = self.parser.find_dir(
|
||||||
"SSLCertificateChainFile", None, vhost.path)
|
"SSLCertificateChainFile", None, vhost.path)
|
||||||
|
# TODO: apache-parser-v2
|
||||||
|
# v2_path["chain_path"] = vhost.node.find_directives(
|
||||||
|
# "SSLCertificateChainFile")
|
||||||
|
# parserassert.legacy_assert_dir(path["chain_path"],
|
||||||
|
# v2_path["chain_path])
|
||||||
|
|
||||||
|
|
||||||
# Handle errors when certificate/key directives cannot be found
|
# Handle errors when certificate/key directives cannot be found
|
||||||
if not path["cert_path"]:
|
if not path["cert_path"]:
|
||||||
@@ -535,9 +555,15 @@ class ApacheConfigurator(common.Installer):
|
|||||||
set_cert_path = cert_path
|
set_cert_path = cert_path
|
||||||
self.parser.aug.set(path["cert_path"][-1], cert_path)
|
self.parser.aug.set(path["cert_path"][-1], cert_path)
|
||||||
self.parser.aug.set(path["cert_key"][-1], key_path)
|
self.parser.aug.set(path["cert_key"][-1], key_path)
|
||||||
|
# TODO: apache-parser-v2
|
||||||
|
# path["cert_path"][-1].set_parameters(cert_path,)
|
||||||
|
# path["cert_key"][-1].set_parameters(key_path,)
|
||||||
if chain_path is not None:
|
if chain_path is not None:
|
||||||
self.parser.add_dir(vhost.path,
|
self.parser.add_dir(vhost.path,
|
||||||
"SSLCertificateChainFile", chain_path)
|
"SSLCertificateChainFile", chain_path)
|
||||||
|
# TODO: apache-parser-v2
|
||||||
|
# vhost.node.add_child_directive("SSLCertificateChainFile",
|
||||||
|
# (chain_path,))
|
||||||
else:
|
else:
|
||||||
raise errors.PluginError("--chain-path is required for your "
|
raise errors.PluginError("--chain-path is required for your "
|
||||||
"version of Apache")
|
"version of Apache")
|
||||||
@@ -548,6 +574,9 @@ class ApacheConfigurator(common.Installer):
|
|||||||
set_cert_path = fullchain_path
|
set_cert_path = fullchain_path
|
||||||
self.parser.aug.set(path["cert_path"][-1], fullchain_path)
|
self.parser.aug.set(path["cert_path"][-1], fullchain_path)
|
||||||
self.parser.aug.set(path["cert_key"][-1], key_path)
|
self.parser.aug.set(path["cert_key"][-1], key_path)
|
||||||
|
# TODO: apache-parser-v2
|
||||||
|
# path["cert_path"][-1].set_parameters(fullchain_path,)
|
||||||
|
# path["cert_key"][-1].set_parameters(key_path,)
|
||||||
|
|
||||||
# Enable the new vhost if needed
|
# Enable the new vhost if needed
|
||||||
if not vhost.enabled:
|
if not vhost.enabled:
|
||||||
@@ -793,29 +822,48 @@ class ApacheConfigurator(common.Installer):
|
|||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def _get_vhost_names(self, path):
|
def _get_vhost_names(self, vhost):
|
||||||
"""Helper method for getting the ServerName and
|
"""Helper method for getting the ServerName and
|
||||||
ServerAlias values from vhost in path
|
ServerAlias values from vhost
|
||||||
|
|
||||||
:param path: Path to read ServerName and ServerAliases from
|
:param vhost: VirtualHost object to read ServerName and ServerAliases from
|
||||||
|
|
||||||
:returns: Tuple including ServerName and `list` of ServerAlias strings
|
:returns: Tuple including ServerName and `list` of ServerAlias strings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
servername_match = self.parser.find_dir(
|
servername_match = self.parser.find_dir(
|
||||||
"ServerName", None, start=path, exclude=False)
|
"ServerName", None, start=vhost.path, exclude=False)
|
||||||
serveralias_match = self.parser.find_dir(
|
serveralias_match = self.parser.find_dir(
|
||||||
"ServerAlias", None, start=path, exclude=False)
|
"ServerAlias", None, start=vhost.path, exclude=False)
|
||||||
|
# TODO: apache-parser-v2
|
||||||
|
# v2_servername_match = vhost.node.find_directives("ServerName",
|
||||||
|
# exclude=False)
|
||||||
|
# v2_serveralias_match = vhost.node.find_directives("ServerAlias",
|
||||||
|
# exclude=False)
|
||||||
|
# parserassert.legacy_assert_dir(servername_match, v2_servername_match)
|
||||||
|
# parserassert.legacy_assert_dir(serveralias_match, v2_serveralias_match)
|
||||||
|
|
||||||
|
|
||||||
serveraliases = []
|
serveraliases = []
|
||||||
for alias in serveralias_match:
|
for alias in serveralias_match:
|
||||||
serveralias = self.parser.get_arg(alias)
|
serveralias = self.parser.get_arg(alias)
|
||||||
serveraliases.append(serveralias)
|
serveraliases.append(serveralias)
|
||||||
|
# TODO: apache-parser-v2
|
||||||
|
# v2_serveraliases = []
|
||||||
|
# for alias in v2_serveralias_match:
|
||||||
|
# v2_serveraliases += list(alias.parameters)
|
||||||
|
# parserassert.legacy_assert_list(serveraliases, v2_serveraliases)
|
||||||
|
|
||||||
|
|
||||||
servername = None
|
servername = None
|
||||||
if servername_match:
|
if servername_match:
|
||||||
# Get last ServerName as each overwrites the previous
|
# Get last ServerName as each overwrites the previous
|
||||||
servername = self.parser.get_arg(servername_match[-1])
|
servername = self.parser.get_arg(servername_match[-1])
|
||||||
|
# TODO: apache-parser-v2
|
||||||
|
# v2_servername = None
|
||||||
|
# if v2_servername_match:
|
||||||
|
# v2_servername = v2_servername_match[-1].parameters[-1]
|
||||||
|
# parserassert.simpleassert(servername, v2_servername)
|
||||||
|
|
||||||
return (servername, serveraliases)
|
return (servername, serveraliases)
|
||||||
|
|
||||||
@@ -827,7 +875,7 @@ class ApacheConfigurator(common.Installer):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
servername, serveraliases = self._get_vhost_names(host.path)
|
servername, serveraliases = self._get_vhost_names(host)
|
||||||
|
|
||||||
for alias in serveraliases:
|
for alias in serveraliases:
|
||||||
if not host.modmacro:
|
if not host.modmacro:
|
||||||
@@ -1476,7 +1524,7 @@ class ApacheConfigurator(common.Installer):
|
|||||||
|
|
||||||
def _add_servername_alias(self, target_name, vhost):
|
def _add_servername_alias(self, target_name, vhost):
|
||||||
vh_path = vhost.path
|
vh_path = vhost.path
|
||||||
sname, saliases = self._get_vhost_names(vh_path)
|
sname, saliases = self._get_vhost_names(vhost)
|
||||||
if target_name == sname or target_name in saliases:
|
if target_name == sname or target_name in saliases:
|
||||||
return
|
return
|
||||||
if self._has_matching_wildcard(vh_path, target_name):
|
if self._has_matching_wildcard(vh_path, target_name):
|
||||||
|
|||||||
375
certbot-apache/certbot_apache/interfaces.py
Normal file
375
certbot-apache/certbot_apache/interfaces.py
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
"""Parser interfaces."""
|
||||||
|
import abc
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class ParserNode(object):
|
||||||
|
"""
|
||||||
|
ParserNode is the basic building block of the tree of such nodes,
|
||||||
|
representing the structure of the configuration. It is largely meant to keep
|
||||||
|
the structure information intact and idiomatically accessible.
|
||||||
|
|
||||||
|
The root node as well as the child nodes of it should be instances of ParserNode.
|
||||||
|
Nodes keep track of their differences to on-disk representation of configuration
|
||||||
|
by marking modified ParserNodes as dirty to enable partial write-to-disk for
|
||||||
|
different files in the configuration structure.
|
||||||
|
|
||||||
|
While for the most parts the usage and the child types are obvious, "include"-
|
||||||
|
and similar directives are an exception to this rule. This is because of the
|
||||||
|
nature of include directives - which unroll the contents of another file or
|
||||||
|
configuration block to their place. While we could unroll the included nodes
|
||||||
|
to the parent tree, it remains important to keep the context of include nodes
|
||||||
|
separate in order to write back the original configuration as it was.
|
||||||
|
|
||||||
|
For parsers that require the implementation to keep track of the whitespacing,
|
||||||
|
it's responsibility of each ParserNode object itself to store its prepending
|
||||||
|
whitespaces in order to be able to reconstruct the complete configuration file i
|
||||||
|
as it was when originally read from the disk.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def ancestor(self): # pragma: no cover
|
||||||
|
"""
|
||||||
|
This property contains a reference to ancestor node, or None if the node
|
||||||
|
is the root node of the configuration tree.
|
||||||
|
|
||||||
|
:returns: The ancestor BlockNode object, or None for root node.
|
||||||
|
:rtype: ParserNode
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def dirty(self): # pragma: no cover
|
||||||
|
"""
|
||||||
|
This property contains a boolean value of the information if this node has
|
||||||
|
been modified since last save (or after the initial parse).
|
||||||
|
|
||||||
|
:returns: True if this node has had changes that have not yet been written
|
||||||
|
to disk.
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def save(self, msg):
|
||||||
|
"""
|
||||||
|
Save traverses the children, and attempts to write the AST to disk for
|
||||||
|
all the objects that are marked dirty. The actual operation of course
|
||||||
|
depends on the underlying implementation. save() shouldn't be called
|
||||||
|
from the Configurator outside of its designated save() method in order
|
||||||
|
to ensure that the Reverter checkpoints are created properly.
|
||||||
|
|
||||||
|
Note: this approach of keeping internal structure of the configuration
|
||||||
|
within the ParserNode tree does not represent the file inclusion structure
|
||||||
|
of actual configuration files that reside in the filesystem. To handle
|
||||||
|
file writes properly, the file specific temporary trees should be extracted
|
||||||
|
from the full ParserNode tree where necessary when writing to disk.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class CommentNode(ParserNode):
|
||||||
|
"""
|
||||||
|
CommentNode class is used for representation of comments within the parsed
|
||||||
|
configuration structure. Because of the nature of comments, it is not able
|
||||||
|
to have child nodes and hence it is always treated as a leaf node.
|
||||||
|
|
||||||
|
CommentNode stores its contents in class variable 'comment' and does not
|
||||||
|
have a specific name.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def comment(self): # pragma: no cover
|
||||||
|
"""
|
||||||
|
Comment property contains the contents of the comment without the comment
|
||||||
|
directives (typically # or /* ... */).
|
||||||
|
|
||||||
|
:returns: A string containing the comment
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class DirectiveNode(ParserNode):
|
||||||
|
"""
|
||||||
|
DirectiveNode class represents a configuration directive within the configuration.
|
||||||
|
It can have zero or more arguments attached to it. Because of the nature of
|
||||||
|
single directives, it is not able to have child nodes and hence it is always
|
||||||
|
treated as a leaf node.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def enabled(self): # pragma: no cover
|
||||||
|
"""
|
||||||
|
Configuration blocks may have conditional statements enabling or disabling
|
||||||
|
their contents. This property returns the state of this DirectiveNode.
|
||||||
|
|
||||||
|
:returns: True if the DirectiveNode is parsed and enabled in the configuration.
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def name(self): # pragma: no cover
|
||||||
|
"""
|
||||||
|
Name property contains the name of the directive.
|
||||||
|
|
||||||
|
:returns: Name of this node
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def parameters(self): # pragma: no cover
|
||||||
|
"""
|
||||||
|
This property contains a tuple of parameters of this ParserNode object
|
||||||
|
excluding whitespaces.
|
||||||
|
|
||||||
|
:returns: A tuple of parameters for this node
|
||||||
|
:rtype: tuple
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def set_parameters(self, parameters):
|
||||||
|
"""
|
||||||
|
Sets the sequence of parameters for this ParserNode object without
|
||||||
|
whitespaces, and marks this object dirty.
|
||||||
|
|
||||||
|
:param list parameters: sequence of parameters
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class BlockNode(ParserNode):
|
||||||
|
"""
|
||||||
|
BlockNode class represents a block of nested configuration directives, comments
|
||||||
|
and other blocks as its children. A BlockNode can have zero or more arguments
|
||||||
|
attached to it.
|
||||||
|
|
||||||
|
Configuration blocks typically consist of one or more child nodes of all possible
|
||||||
|
types. Because of this, the BlockNode class has various discovery and structure
|
||||||
|
management methods.
|
||||||
|
|
||||||
|
Lists of arguments used as an optional argument for some of the methods should
|
||||||
|
be lists of strings that are applicable arguments for each specific BlockNode
|
||||||
|
or DirectiveNode types. As an example, for a following configuration example:
|
||||||
|
|
||||||
|
<VirtualHost *:80>
|
||||||
|
...
|
||||||
|
</VirtualHost>
|
||||||
|
|
||||||
|
The node type would be BlockNode, name would be 'VirtualHost' and arguments
|
||||||
|
would be: ['*:80'].
|
||||||
|
|
||||||
|
While for the following example:
|
||||||
|
|
||||||
|
LoadModule alias_module /usr/lib/apache2/modules/mod_alias.so
|
||||||
|
|
||||||
|
The node type would be DirectiveNode, name would be 'LoadModule' and arguments
|
||||||
|
would be: ['alias_module', '/usr/lib/apache2/modules/mod_alias.so']
|
||||||
|
|
||||||
|
The applicable arguments are dependent on the underlying configuration language
|
||||||
|
and its grammar.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def add_child_block(self, name, arguments=None, position=None):
|
||||||
|
"""
|
||||||
|
Adds a new BlockNode child node with provided values and marks the callee
|
||||||
|
BlockNode dirty. This is used to add new children to the AST.
|
||||||
|
|
||||||
|
:param str name: The name of the child node to add
|
||||||
|
:param list arguments: list of arguments for the node
|
||||||
|
:param int position: Position in the list of children to add the new child
|
||||||
|
node to. Defaults to None, which appends the newly created node to the list.
|
||||||
|
If an integer is given, the child is inserted before that index in the
|
||||||
|
list similar to list().insert.
|
||||||
|
|
||||||
|
:returns: BlockNode instance of the created child block
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def add_child_directive(self, name, arguments=None, position=None):
|
||||||
|
"""
|
||||||
|
Adds a new DirectiveNode child node with provided values and marks the
|
||||||
|
callee BlockNode dirty. This is used to add new children to the AST.
|
||||||
|
|
||||||
|
:param str name: The name of the child node to add
|
||||||
|
:param list arguments: list of arguments for the node
|
||||||
|
:param int position: Position in the list of children to add the new child
|
||||||
|
node to. Defaults to None, which appends the newly created node to the list.
|
||||||
|
If an integer is given, the child is inserted before that index in the
|
||||||
|
list similar to list().insert.
|
||||||
|
|
||||||
|
:returns: DirectiveNode instance of the created child directive
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def add_child_comment(self, comment="", position=None):
|
||||||
|
"""
|
||||||
|
Adds a new CommentNode child node with provided value and marks the
|
||||||
|
callee BlockNode dirty. This is used to add new children to the AST.
|
||||||
|
|
||||||
|
:param str comment: Comment contents
|
||||||
|
:param int position: Position in the list of children to add the new child
|
||||||
|
node to. Defaults to None, which appends the newly created node to the list.
|
||||||
|
If an integer is given, the child is inserted before that index in the
|
||||||
|
list similar to list().insert.
|
||||||
|
|
||||||
|
:returns: CommentNode instance of the created child comment
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def children(self): # pragma: no cover
|
||||||
|
"""
|
||||||
|
This property contains a list ParserNode objects that are the children
|
||||||
|
for this node. The order of children is the same as that of the parsed
|
||||||
|
configuration block.
|
||||||
|
|
||||||
|
:returns: A tuple of this block's children
|
||||||
|
:rtype: tuple
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def enabled(self):
|
||||||
|
"""
|
||||||
|
Configuration blocks may have conditional statements enabling or disabling
|
||||||
|
their contents. This property returns the state of this configuration block.
|
||||||
|
In case of unmatched conditional statement in block, this block itself should
|
||||||
|
be set enabled while its children should be set disabled.
|
||||||
|
|
||||||
|
:returns: True if the BlockNode is parsed and enabled in the configuration.
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def find_blocks(self, name, exclude=True):
|
||||||
|
"""
|
||||||
|
Find a configuration block by name. This method walks the child tree of
|
||||||
|
ParserNodes under the instance it was called from. This way it is possible
|
||||||
|
to search for the whole configuration tree, when starting from root node or
|
||||||
|
to do a partial search when starting from a specified branch.
|
||||||
|
|
||||||
|
:param str name: The name of the directive to search for
|
||||||
|
:param bool exclude: If the search results should exclude the contents of
|
||||||
|
ParserNode objects that reside within conditional blocks and because
|
||||||
|
of current state are not enabled.
|
||||||
|
|
||||||
|
:returns: A list of found BlockNode objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def find_directives(self, name, exclude=True):
|
||||||
|
"""
|
||||||
|
Find a directive by name. This method walks the child tree of ParserNodes
|
||||||
|
under the instance it was called from. This way it is possible to search
|
||||||
|
for the whole configuration tree, when starting from root node, or to do
|
||||||
|
a partial search when starting from a specified branch.
|
||||||
|
|
||||||
|
:param str name: The name of the directive to search for
|
||||||
|
:param bool exclude: If the search results should exclude the contents of
|
||||||
|
ParserNode objects that reside within conditional blocks and because
|
||||||
|
of current state are not enabled.
|
||||||
|
|
||||||
|
:returns: A list of found DirectiveNode objects.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def find_comments(self, comment, exact=False):
|
||||||
|
"""
|
||||||
|
Find comments with value containing or being exactly the same as search term.
|
||||||
|
|
||||||
|
This method walks the child tree of ParserNodes under the instance it was
|
||||||
|
called from. This way it is possible to search for the whole configuration
|
||||||
|
tree, when starting from root node, or to do a partial search when starting
|
||||||
|
from a specified branch.
|
||||||
|
|
||||||
|
:param str comment: The content of comment to search for
|
||||||
|
:param bool exact: If the comment needs to exactly match the search term
|
||||||
|
|
||||||
|
:returns: A list of found CommentNode objects.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def delete_child(self, child):
|
||||||
|
"""
|
||||||
|
Remove a specified child node from the list of children of the called
|
||||||
|
BlockNode object.
|
||||||
|
|
||||||
|
:param ParserNode child: Child ParserNode object to remove from the list
|
||||||
|
of children of the callee.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def name(self): # pragma: no cover
|
||||||
|
"""
|
||||||
|
Name property contains the name of the block. As an example for config:
|
||||||
|
<VirtualHost *:80> ... </VirtualHost>
|
||||||
|
the name would be "VirtualHost".
|
||||||
|
|
||||||
|
:returns: Name of this node
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def parameters(self): # pragma: no cover
|
||||||
|
"""
|
||||||
|
This property contains a tuple of parameters of this ParserNode object
|
||||||
|
excluding whitespaces.
|
||||||
|
|
||||||
|
:returns: A tuple of parameters for this node
|
||||||
|
:rtype: tuple
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def set_parameters(self, parameters):
|
||||||
|
"""
|
||||||
|
Sets the sequence of parameters for this ParserNode object without
|
||||||
|
whitespaces, and marks this object dirty.
|
||||||
|
|
||||||
|
:param list parameters: sequence of parameters
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def unsaved_files(self):
|
||||||
|
"""
|
||||||
|
Returns a list of file paths that have been changed since the last save
|
||||||
|
(or the initial configuration parse). The intended use for this method
|
||||||
|
is to tell the Reverter which files need to be included in a checkpoint.
|
||||||
|
|
||||||
|
This is typically called for the root of the ParserNode tree.
|
||||||
|
|
||||||
|
:returns: list of file paths of files that have been changed but not yet
|
||||||
|
saved to disk.
|
||||||
|
"""
|
||||||
@@ -61,6 +61,7 @@ class ApacheParser(object):
|
|||||||
self.root = os.path.abspath(root)
|
self.root = os.path.abspath(root)
|
||||||
self.loc = {"root": self._find_config_root()}
|
self.loc = {"root": self._find_config_root()}
|
||||||
self.parse_file(self.loc["root"])
|
self.parse_file(self.loc["root"])
|
||||||
|
self.handle_includes()
|
||||||
|
|
||||||
if version >= (2, 4):
|
if version >= (2, 4):
|
||||||
# Look up variables from httpd and add to DOM if not already parsed
|
# Look up variables from httpd and add to DOM if not already parsed
|
||||||
@@ -269,6 +270,7 @@ class ApacheParser(object):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
mods = set() # type: Set[str]
|
mods = set() # type: Set[str]
|
||||||
|
self.handle_includes()
|
||||||
matches = self.find_dir("LoadModule")
|
matches = self.find_dir("LoadModule")
|
||||||
iterator = iter(matches)
|
iterator = iter(matches)
|
||||||
# Make sure prev_size != cur_size for do: while: iteration
|
# Make sure prev_size != cur_size for do: while: iteration
|
||||||
@@ -321,11 +323,7 @@ class ApacheParser(object):
|
|||||||
def update_includes(self):
|
def update_includes(self):
|
||||||
"""Get includes from httpd process, and add them to DOM if needed"""
|
"""Get includes from httpd process, and add them to DOM if needed"""
|
||||||
|
|
||||||
# Find_dir iterates over configuration for Include and IncludeOptional
|
self.handle_includes()
|
||||||
# directives to make sure we see the full include tree present in the
|
|
||||||
# configuration files
|
|
||||||
_ = self.find_dir("Include")
|
|
||||||
|
|
||||||
inc_cmd = [self.configurator.option("ctl"), "-t", "-D",
|
inc_cmd = [self.configurator.option("ctl"), "-t", "-D",
|
||||||
"DUMP_INCLUDES"]
|
"DUMP_INCLUDES"]
|
||||||
matches = self.parse_from_subprocess(inc_cmd, r"\(.*\) (.*)")
|
matches = self.parse_from_subprocess(inc_cmd, r"\(.*\) (.*)")
|
||||||
@@ -606,6 +604,8 @@ class ApacheParser(object):
|
|||||||
# includes = self.aug.match(start +
|
# includes = self.aug.match(start +
|
||||||
# "//* [self::directive='Include']/* [label()='arg']")
|
# "//* [self::directive='Include']/* [label()='arg']")
|
||||||
|
|
||||||
|
regex = "%s" % (case_i(directive))
|
||||||
|
|
||||||
regex = "(%s)|(%s)|(%s)" % (case_i(directive),
|
regex = "(%s)|(%s)|(%s)" % (case_i(directive),
|
||||||
case_i("Include"),
|
case_i("Include"),
|
||||||
case_i("IncludeOptional"))
|
case_i("IncludeOptional"))
|
||||||
@@ -637,6 +637,49 @@ class ApacheParser(object):
|
|||||||
|
|
||||||
return ordered_matches
|
return ordered_matches
|
||||||
|
|
||||||
|
def handle_includes(self, known_matches=None):
|
||||||
|
"""
|
||||||
|
This method searches through the configuration tree for Include and
|
||||||
|
IncludeOptional directives and includes them in the Augeas DOM tree
|
||||||
|
recursively.
|
||||||
|
|
||||||
|
:param list known_matches: List of already known Augeas DOM paths of found
|
||||||
|
include directives.
|
||||||
|
"""
|
||||||
|
|
||||||
|
start = get_aug_path(self.loc["root"])
|
||||||
|
regex = "(%s)|(%s)" % (case_i("Include"), case_i("IncludeOptional"))
|
||||||
|
|
||||||
|
all_matches = self.aug.match(
|
||||||
|
"%s//*[self::directive=~regexp('%s')]" % (start, regex))
|
||||||
|
|
||||||
|
if known_matches and all([True for m in all_matches if m in known_matches]):
|
||||||
|
# We already found everything previously
|
||||||
|
return
|
||||||
|
|
||||||
|
# Always use the exclusion as we want to have the correct state
|
||||||
|
matches = self._exclude_dirs(all_matches)
|
||||||
|
|
||||||
|
for match in matches:
|
||||||
|
incpath = self.get_arg(match + "/arg")
|
||||||
|
# Attempts to add a transform to the file if one does not already exist
|
||||||
|
|
||||||
|
# Standardize the include argument based on server root
|
||||||
|
if not incpath.startswith("/"):
|
||||||
|
# Normpath will condense ../
|
||||||
|
incpath = os.path.normpath(os.path.join(self.root, incpath))
|
||||||
|
else:
|
||||||
|
incpath = os.path.normpath(incpath)
|
||||||
|
|
||||||
|
if os.path.isdir(incpath):
|
||||||
|
self.parse_file(os.path.join(incpath, "*"))
|
||||||
|
else:
|
||||||
|
self.parse_file(incpath)
|
||||||
|
|
||||||
|
# Iterate. The results will be checked and the recursion broken when
|
||||||
|
# everything has been found.
|
||||||
|
self.handle_includes(all_matches)
|
||||||
|
|
||||||
def get_all_args(self, match):
|
def get_all_args(self, match):
|
||||||
"""
|
"""
|
||||||
Tries to fetch all arguments for a directive. See get_arg.
|
Tries to fetch all arguments for a directive. See get_arg.
|
||||||
@@ -755,10 +798,10 @@ class ApacheParser(object):
|
|||||||
arg = os.path.normpath(arg)
|
arg = os.path.normpath(arg)
|
||||||
|
|
||||||
# Attempts to add a transform to the file if one does not already exist
|
# Attempts to add a transform to the file if one does not already exist
|
||||||
if os.path.isdir(arg):
|
#if os.path.isdir(arg):
|
||||||
self.parse_file(os.path.join(arg, "*"))
|
# self.parse_file(os.path.join(arg, "*"))
|
||||||
else:
|
#else:
|
||||||
self.parse_file(arg)
|
# self.parse_file(arg)
|
||||||
|
|
||||||
# Argument represents an fnmatch regular expression, convert it
|
# Argument represents an fnmatch regular expression, convert it
|
||||||
# Split up the path and convert each into an Augeas accepted regex
|
# Split up the path and convert each into an Augeas accepted regex
|
||||||
|
|||||||
84
certbot-apache/certbot_apache/parserassert.py
Normal file
84
certbot-apache/certbot_apache/parserassert.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
"""Runtime assertions for two different parser implementations"""
|
||||||
|
|
||||||
|
def simpleassert(old_value, new_value):
|
||||||
|
"""
|
||||||
|
Simple assertion
|
||||||
|
"""
|
||||||
|
assert old_value == new_value
|
||||||
|
|
||||||
|
def legacy_assert_list(old_list, new_list):
|
||||||
|
"""
|
||||||
|
Used to assert that both of the lists contain the same values.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert len(old_list) == len(new_list)
|
||||||
|
assert all([True for ol in old_list if ol in new_list])
|
||||||
|
|
||||||
|
def legacy_assert_dir(old_result, new_result):
|
||||||
|
"""
|
||||||
|
Used to test and ensure that the new implementation search results matches
|
||||||
|
the old implementation results. This test is intended to be used only to test
|
||||||
|
the old Augeas implementation results against the ParserNode Augeas implementation.
|
||||||
|
|
||||||
|
This test expects the DirectiveNode to have Augeas path in its attribute dict
|
||||||
|
"_metadata" with key "augpath".
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if new_result != "CERTBOT_PASS_ASSERT":
|
||||||
|
return
|
||||||
|
|
||||||
|
assert len(old_result) == len(new_result)
|
||||||
|
|
||||||
|
matching = []
|
||||||
|
|
||||||
|
for oldres in old_result:
|
||||||
|
match = [res for res in new_result if res._metadata["augpath"] == oldres]
|
||||||
|
assert len(match) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def legacy_assert_args(old_result, new_result, parser):
|
||||||
|
"""
|
||||||
|
Used to test and ensure that returned parameter values are the same for
|
||||||
|
the old and the new implementation.
|
||||||
|
|
||||||
|
Uses ApacheParser to actually fetch the parameter for the old result.
|
||||||
|
|
||||||
|
This assertion is structured this way because of how parser.get_arg() is
|
||||||
|
currently used in the ApacheConfigurator, making it easier to test the
|
||||||
|
results.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(old_result, list):
|
||||||
|
for old in old_result:
|
||||||
|
oldarg = parser.get_arg(old_result)
|
||||||
|
assert oldarg in new_result.parameters
|
||||||
|
else:
|
||||||
|
oldarg = parser.get_arg(old_result)
|
||||||
|
assert oldarg in new_result.parameters
|
||||||
|
|
||||||
|
def assert_dir(first, second):
|
||||||
|
"""
|
||||||
|
Used to test that DirectiveNode results match for both implementations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if "CERTBOT_PASS_ASSERT" in [first, second]:
|
||||||
|
return
|
||||||
|
|
||||||
|
assert first.name == second.name
|
||||||
|
assert first.parameters == second.parameters
|
||||||
|
assert first.dirty == second.dirty
|
||||||
|
|
||||||
|
|
||||||
|
def assert_block(first, second):
|
||||||
|
"""
|
||||||
|
Used to test that BlockNode results match for both implementations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if "CERTBOT_PASS_ASSERT" in [first, second]:
|
||||||
|
return
|
||||||
|
|
||||||
|
assert first.name == second.name
|
||||||
|
assert first.parameters == second.parameters
|
||||||
|
assert len(first.children) == len(second.children)
|
||||||
|
assert first.dirty == second.dirty
|
||||||
@@ -91,6 +91,7 @@ class ComplexParserTest(util.ParserTest):
|
|||||||
from certbot_apache import parser
|
from certbot_apache import parser
|
||||||
self.parser.add_dir(parser.get_aug_path(self.parser.loc["default"]),
|
self.parser.add_dir(parser.get_aug_path(self.parser.loc["default"]),
|
||||||
"Include", [arg])
|
"Include", [arg])
|
||||||
|
self.parser.handle_includes()
|
||||||
if hit:
|
if hit:
|
||||||
self.assertTrue(self.parser.find_dir("FNMATCH_DIRECTIVE"))
|
self.assertTrue(self.parser.find_dir("FNMATCH_DIRECTIVE"))
|
||||||
else:
|
else:
|
||||||
|
|||||||
86
certbot-apache/certbot_apache/tests/parsernode_test.py
Normal file
86
certbot-apache/certbot_apache/tests/parsernode_test.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
""" Tests for ParserNode interface """
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module
|
||||||
|
|
||||||
|
from certbot_apache import interfaces
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class DummyCommentNode(interfaces.CommentNode):
|
||||||
|
""" A dummy class implementing CommentNode interface """
|
||||||
|
ancestor = None
|
||||||
|
comment = ""
|
||||||
|
dirty = False
|
||||||
|
|
||||||
|
def save(self, msg): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DummyDirectiveNode(interfaces.DirectiveNode):
|
||||||
|
""" A dummy class implementing DirectiveNode interface """
|
||||||
|
ancestor = None
|
||||||
|
parameters = tuple() # type: Tuple[str, ...]
|
||||||
|
dirty = False
|
||||||
|
enabled = True
|
||||||
|
name = ""
|
||||||
|
|
||||||
|
def save(self, msg): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_parameters(self, parameters): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DummyBlockNode(interfaces.BlockNode):
|
||||||
|
""" A dummy class implementing BlockNode interface """
|
||||||
|
ancestor = None
|
||||||
|
parameters = tuple() # type: Tuple[str, ...]
|
||||||
|
children = tuple() # type: Tuple[interfaces.ParserNode, ...]
|
||||||
|
dirty = False
|
||||||
|
enabled = True
|
||||||
|
name = ""
|
||||||
|
|
||||||
|
def save(self, msg): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_child_block(self, name, arguments=None, position=None): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_child_directive(self, name, arguments=None, position=None): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_child_comment(self, comment="", position=None): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def find_blocks(self, name, exclude=True): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def find_directives(self, name, exclude=True): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def find_comments(self, comment, exact=False): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete_child(self, child): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_parameters(self, parameters): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
def unsaved_files(self): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ParserNodeTest(unittest.TestCase):
|
||||||
|
"""Dummy placeholder test case for ParserNode interfaces"""
|
||||||
|
|
||||||
|
def test_dummy(self):
|
||||||
|
dummyblock = DummyBlockNode()
|
||||||
|
dummydirective = DummyDirectiveNode()
|
||||||
|
dummycomment = DummyCommentNode()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main() # pragma: no cover
|
||||||
Reference in New Issue
Block a user