Merge remote-tracking branch 'github/letsencrypt/master' into network2

This commit is contained in:
Jakub Warmuz
2015-03-25 06:12:25 +00:00
26 changed files with 346 additions and 189 deletions
+3
View File
@@ -6,3 +6,6 @@ venv/
.tox/
.coverage
m3
*~
.vagrant
*.swp
+18
View File
@@ -0,0 +1,18 @@
<!---
This file serves as an entry point for GitHub's Contributing
Guidelines [1] only.
GitHub doesn't render rST very well, especially in respect to internal
hyperlink targets and cross-references [2]. People also tend to
confuse rST and Markdown syntax. Therefore, instead of keeping the
contents here (and including from rST documentation under doc/), link
to the Sphinx generated docs is provided below.
[1] https://github.com/blog/1184-contributing-guidelines
[2] http://docutils.sourceforge.net/docs/user/rst/quickref.html#hyperlink-targets
-->
https://letsencrypt.readthedocs.org/en/latest/contributing.html
-72
View File
@@ -1,72 +0,0 @@
.. _hacking:
Hacking
=======
In order to start hacking, you will first have to create a development
environment:
::
./venv/bin/python setup.py dev
The code base, including your pull requests, **must** have 100% test statement
coverage **and** be compliant with the :ref:`coding-style`.
The following tools are there to help you:
- ``./venv/bin/tox`` starts a full set of tests. Please make sure you
run it before submitting a new pull request.
- ``./venv/bin/tox -e cover`` checks the test coverage only.
- ``./venv/bin/tox -e lint`` checks the style of the whole project,
while ``./venv/bin/pylint --rcfile=.pylintrc file`` will check a single `file` only.
.. _coding-style:
Coding style
============
Please:
1. **Be consistent with the rest of the code**.
2. Read `PEP 8 - Style Guide for Python Code`_.
3. Follow the `Google Python Style Guide`_, with the exception that we
use `Sphinx-style`_ documentation:
::
def foo(arg):
"""Short description.
:param int arg: Some number.
:returns: Argument
:rtype: int
"""
return arg
4. Remember to use ``./venv/bin/pylint``.
.. _Google Python Style Guide: https://google-styleguide.googlecode.com/svn/trunk/pyguide.html
.. _Sphinx-style: http://sphinx-doc.org/
.. _PEP 8 - Style Guide for Python Code: https://www.python.org/dev/peps/pep-0008
Updating the Documentation
==========================
In order to generate the Sphinx documentation, run the following commands.
::
cd docs
make clean html SPHINXBUILD=../venv/bin/sphinx-build
This should generate documentation in the ``docs/_build/html`` directory.
+1 -1
View File
@@ -1,6 +1,6 @@
include README.rst
include CHANGES.rst
include CONTRIBUTING.rst
include CONTRIBUTING.md
include linter_plugin.py
include letsencrypt/EULA
recursive-include letsencrypt *.json
+4
View File
@@ -57,6 +57,7 @@ Current Features
* web servers supported:
- apache2.x (tested and working on Ubuntu Linux)
- standalone (runs its own webserver to prove you control the domain)
* the private key is generated locally on your system
* can talk to the Let's Encrypt (demo) CA or optionally to other ACME
@@ -79,6 +80,8 @@ Documentation: https://letsencrypt.readthedocs.org/
Software project: https://github.com/letsencrypt/lets-encrypt-preview
Notes for developers: CONTRIBUTING.rst_
Main Website: https://letsencrypt.org/
IRC Channel: #letsencrypt on `Freenode`_
@@ -88,3 +91,4 @@ email to client-dev+subscribe@letsencrypt.org)
.. _Freenode: https://freenode.net
.. _client-dev: https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev
.. _CONTRIBUTING.rst: https://github.com/letsencrypt/lets-encrypt-preview/blob/master/CONTRIBUTING.rst
Vendored
+32
View File
@@ -0,0 +1,32 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
# Setup instructions from docs/using.rst
$ubuntu_setup_script = <<SETUP_SCRIPT
sudo apt-get update
sudo apt-get install -y python python-setuptools python-virtualenv python-dev gcc swig dialog libaugeas0 libssl-dev libffi-dev ca-certificates
cd /vagrant
if [ ! -d "venv" ]; then
virtualenv --no-site-packages -p python2 venv
./venv/bin/python setup.py dev
fi
SETUP_SCRIPT
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.define "ubuntu-trusty", primary: true do |ubuntu_trusty|
ubuntu_trusty.vm.box = "ubuntu/trusty64"
ubuntu_trusty.vm.provision "shell", inline: $ubuntu_setup_script
ubuntu_trusty.vm.provider "virtualbox" do |v|
# VM needs more memory to run test suite, got "OSError: [Errno 12]
# Cannot allocate memory" when running
# letsencrypt.client.tests.display.util_test.NcursesDisplayTest
v.memory = 1024
end
end
end
+1 -1
View File
@@ -101,7 +101,7 @@ exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
default_role = 'py:obj'
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
+197
View File
@@ -0,0 +1,197 @@
============
Contributing
============
.. _hacking:
Hacking
=======
In order to start hacking, you will first have to create a development
environment. Start by :doc:`installing dependencies and setting up
Let's Encrypt <using>`.
Now you can install the development packages:
.. code-block:: shell
./venv/bin/python setup.py dev
The code base, including your pull requests, **must** have 100% test
statement coverage **and** be compliant with the :ref:`coding style
<coding-style>`.
The following tools are there to help you:
- ``./venv/bin/tox`` starts a full set of tests. Please make sure you
run it before submitting a new pull request.
- ``./venv/bin/tox -e cover`` checks the test coverage only.
- ``./venv/bin/tox -e lint`` checks the style of the whole project,
while ``./venv/bin/pylint --rcfile=.pylintrc file`` will check a
single ``file`` only.
.. _installing dependencies and setting up Let's Encrypt:
https://letsencrypt.readthedocs.org/en/latest/using.html
Vagrant
-------
If you are a Vagrant user, Let's Encrypt comes with a Vagrantfile that
automates setting up a development environment in an Ubuntu 14.04
LTS VM. To set it up, simply run ``vagrant up``. The repository is
synced to ``/vagrant``, so you can get started with:
.. code-block:: shell
vagrant ssh
cd /vagrant
./venv/bin/python setup.py install
sudo ./venv/bin/letsencrypt
Support for other Linux distributions coming soon.
.. note::
Unfortunately, Python distutils and, by extension, setup.py and
tox, use hard linking quite extensively. Hard linking is not
supported by the default sync filesystem in Vagrant. As a result,
all actions with these commands are *significantly slower* in
Vagrant. One potential fix is to `use NFS`_ (`related issue`_).
.. _use NFS: http://docs.vagrantup.com/v2/synced-folders/nfs.html
.. _related issue: https://github.com/ClusterHQ/flocker/issues/516
Code components and layout
==========================
letsencrypt/acme
contains all protocol specific code
letsencrypt/client
all client code
letsencrypt/scripts
just the starting point of the code, main.py
Plugin-architecture
-------------------
Let's Encrypt has a plugin architecture to facilitate support for
different webservers, other TLS servers, and operating systems.
The most common kind of plugin is a "Configurator", which is likely to
implement the `~letsencrypt.client.interfaces.IAuthenticator` and
`~letsencrypt.client.interfaces.IInstaller` interfaces (though some
Configurators may implement just one of those).
There are also `~letsencrypt.client.interfaces.IDisplay` plugins,
which implement bindings to alternative UI libraries.
Authenticators
--------------
Authenticators are plugins designed to solve challenges received from
the ACME server. From the protocol, there are essentially two
different types of challenges. Challenges that must be solved by
individual plugins in order to satisfy domain validation (subclasses
of `~.DVChallenge`, i.e. `~.challenges.DVSNI`,
`~.challenges.SimpleHTTPS`, `~.challenges.DNS`) and client specific
challenges (subclasses of `~.ClientChallenge`,
i.e. `~.challenges.RecoveryToken`, `~.challenges.RecoveryContact`,
`~.challenges.ProofOfPossession`). Client specific challenges are
always handled by the `~.ClientAuthenticator`. Right now we have two
DV Authenticators, `~.ApacheConfigurator` and the
`~.StandaloneAuthenticator`. The Standalone and Apache authenticators
only solve the `~.challenges.DVSNI` challenge currently. (You can set
which challenges your authenticator can handle through the
:meth:`~.IAuthenticator.get_chall_pref`.
(FYI: We also have a partial implementation for a `~.DNSAuthenticator`
in a separate branch).
Installer
---------
Installers classes exist to actually setup the certificate and be able
to enhance the configuration. (Turn on HSTS, redirect to HTTPS,
etc). You can indicate your abilities through the
:meth:`~.IInstaller.supported_enhancements` call. We currently only
have one Installer written (still developing), `~.ApacheConfigurator`.
Installers and Authenticators will oftentimes be the same
class/object. Installers and Authenticators are kept separate because
it should be possible to use the `~.StandaloneAuthenticator` (it sets
up its own Python server to perform challenges) with a program that
cannot solve challenges itself. (I am imagining MTA installers).
Display
~~~~~~~
We currently offer a pythondialog and "text" mode for displays. I have
rewritten the interface which should be merged within the next day
(the rewrite is in the revoker branch of the repo and should be merged
within the next day). Display plugins implement
`~letsencrypt.client.interfaces.IDisplay` interface.
Augeas
------
Some plugins, especially those designed to reconfigure UNIX servers,
can take inherit from `~.AugeasConfigurator` class in order to more
efficiently handle common operations on UNIX server configuration
files.
.. _coding-style:
Coding style
============
Please:
1. **Be consistent with the rest of the code**.
2. Read `PEP 8 - Style Guide for Python Code`_.
3. Follow the `Google Python Style Guide`_, with the exception that we
use `Sphinx-style`_ documentation::
def foo(arg):
"""Short description.
:param int arg: Some number.
:returns: Argument
:rtype: int
"""
return arg
4. Remember to use ``./venv/bin/pylint``.
.. _Google Python Style Guide:
https://google-styleguide.googlecode.com/svn/trunk/pyguide.html
.. _Sphinx-style: http://sphinx-doc.org/
.. _PEP 8 - Style Guide for Python Code:
https://www.python.org/dev/peps/pep-0008
Updating the documentation
==========================
In order to generate the Sphinx documentation, run the following
commands:
.. code-block:: shell
cd docs
make clean html SPHINXBUILD=../venv/bin/sphinx-build
This should generate documentation in the ``docs/_build/html``
directory.
+1 -1
View File
@@ -6,7 +6,7 @@ Welcome to the Let's Encrypt client documentation!
intro
using
project
contributing
.. toctree::
:maxdepth: 1
-5
View File
@@ -1,5 +0,0 @@
================================
The Let's Encrypt Client Project
================================
.. include:: ../CONTRIBUTING.rst
+11 -11
View File
@@ -21,30 +21,30 @@ In general:
Ubuntu
------
::
.. code-block:: shell
sudo apt-get install python python-setuptools python-virtualenv python-dev \
gcc swig dialog libaugeas0 libssl-dev libffi-dev \
ca-certificates
sudo apt-get install python python-setuptools python-virtualenv python-dev \
gcc swig dialog libaugeas0 libssl-dev libffi-dev \
ca-certificates
.. Please keep the above command in sync with .travis.yml (before_install)
Mac OSX
-------
::
.. code-block:: shell
sudo brew install augeas swig
sudo brew install augeas swig
Installation
============
::
.. code-block:: shell
virtualenv --no-site-packages -p python2 venv
./venv/bin/python setup.py install
sudo ./venv/bin/letsencrypt
virtualenv --no-site-packages -p python2 venv
./venv/bin/python setup.py install
sudo ./venv/bin/letsencrypt
Usage
@@ -52,7 +52,7 @@ Usage
The letsencrypt commandline tool has a builtin help:
::
.. code-block:: shell
./venv/bin/letsencrypt --help
-1
View File
@@ -1 +0,0 @@
letsencrypt/scripts/main.py
+10 -32
View File
@@ -30,13 +30,7 @@ class Field(object):
:class:`~letsencrypt.acme.jose.errors.SerializationError`
(:class:`~letsencrypt.acme.jose.errors.DeserializationError`).
For greater flexibility, ``encoder2`` and ``decoder2`` accept two
parameters: the whole object ("``self``" in case of encoding, and
JSON serialized object ``jobj`` in case of decoding) and the value
to be encoded/decoded.
Note, that ``decoder`` and ``decoder2`` should perform partial
serialization only.
Note, that ``decoder`` should perform partial serialization only.
:ivar str json_name: Name of the field when encoded to JSON.
:ivar default: Default value (used when not present in JSON object).
@@ -48,18 +42,15 @@ class Field(object):
deserializing.
"""
__slots__ = ('json_name', 'default', 'omitempty',
'fdec', 'fenc', 'fdec2', 'fenc2')
__slots__ = ('json_name', 'default', 'omitempty', 'fdec', 'fenc')
def __init__(self, json_name, default=None, omitempty=False,
decoder=None, encoder=None, decoder2=None, encoder2=None):
decoder=None, encoder=None):
# pylint: disable=too-many-arguments
self.json_name = json_name
self.default = default
self.omitempty = omitempty
self.fdec2 = decoder2
self.fenc2 = encoder2
self.fdec = self.default_decoder if decoder is None else decoder
self.fenc = self.default_encoder if encoder is None else encoder
@@ -80,37 +71,24 @@ class Field(object):
def _update_params(self, **kwargs):
current = dict(json_name=self.json_name, default=self.default,
omitempty=self.omitempty,
decoder=self.fdec, encoder=self.fenc,
decoder2=self.fdec2, encoder2=self.fenc2)
decoder=self.fdec, encoder=self.fenc)
current.update(kwargs)
return type(self)(**current) # pylint: disable=star-args
def decoder(self, fdec):
"""Descriptor to change the decoder on JSON object field."""
return self._update_params(decoder=fdec, decoder2=None)
return self._update_params(decoder=fdec)
def encoder(self, fenc):
"""Descriptor to change the encoder on JSON object field."""
return self._update_params(encoder=fenc, encoder2=None)
return self._update_params(encoder=fenc)
def decoder2(self, fdec2):
"""Descriptor to change the decoder2 on JSON object field."""
return self._update_params(decoder2=fdec2, decoder=None)
def encoder2(self, fenc2):
"""Descriptor to change the encoder2 on JSON object field."""
return self._update_params(encoder2=fenc2, encoder=None)
def decode(self, value, jobj=None):
def decode(self, value):
"""Decode a value, optionally with context JSON object."""
if self.fdec2 is not None:
return self.fdec2(jobj, value)
return self.fdec(value)
def encode(self, value, obj=None):
def encode(self, value):
"""Encode a value, optionally with context JSON object."""
if self.fenc2 is not None:
return self.fenc2(obj, value)
return self.fenc(value)
@classmethod
@@ -241,7 +219,7 @@ class JSONObjectWithFields(util.ImmutableMap, interfaces.JSONDeSerializable):
logging.debug('Ommiting empty field "%s" (%s)', slot, value)
else:
try:
jobj[field.json_name] = field.encode(value, self)
jobj[field.json_name] = field.encode(value)
except errors.SerializationError as error:
raise errors.SerializationError(
'Could not encode {0} ({1}): {2}'.format(
@@ -274,7 +252,7 @@ class JSONObjectWithFields(util.ImmutableMap, interfaces.JSONDeSerializable):
else:
value = jobj[field.json_name]
try:
fields[slot] = field.decode(value, jobj)
fields[slot] = field.decode(value)
except errors.DeserializationError as error:
raise errors.DeserializationError(
'Could not decode {0!r} ({1!r}): {2}'.format(
+4 -27
View File
@@ -21,8 +21,6 @@ class FieldTest(unittest.TestCase):
"""Tests for letsencrypt.acme.jose.json_util.Field."""
def test_descriptors(self):
mock_jobj = mock.MagicMock()
mock_obj = mock.MagicMock()
mock_value = mock.MagicMock()
# pylint: disable=missing-docstring
@@ -33,36 +31,15 @@ class FieldTest(unittest.TestCase):
def encoder(unused_value):
return 'e'
def decoder2(jobj, unused_value):
self.assertTrue(jobj is mock_jobj)
return 'd2'
def encoder2(obj, unused_value):
self.assertTrue(obj is mock_obj)
return 'e2'
from letsencrypt.acme.jose.json_util import Field
field = Field('foo', decoder=decoder, encoder=encoder,
decoder2=decoder2, encoder2=encoder2)
self.assertEqual('e2', field.encode(mock_value, mock_obj))
self.assertEqual('d2', field.decode(mock_value, mock_jobj))
field = Field('foo')
field = field.encoder(encoder)
self.assertEqual('e', field.encode(mock_value, mock_obj))
self.assertEqual('d2', field.decode(mock_value, mock_jobj))
self.assertEqual('e', field.encode(mock_value))
field = field.decoder(decoder)
self.assertEqual('e', field.encode(mock_value, mock_obj))
self.assertEqual('d', field.decode(mock_value, mock_jobj))
field = field.encoder2(encoder2)
self.assertEqual('e2', field.encode(mock_value, mock_obj))
self.assertEqual('d', field.decode(mock_value, mock_jobj))
field = field.decoder2(decoder2)
self.assertEqual('e2', field.encode(mock_value, mock_obj))
self.assertEqual('d2', field.decode(mock_value, mock_jobj))
self.assertEqual('e', field.encode(mock_value))
self.assertEqual('d', field.decode(mock_value))
def test_default_encoder_is_partial(self):
class MockField(interfaces.JSONDeSerializable):
+6 -1
View File
@@ -71,7 +71,12 @@ class _JWAHS(JWASignature):
return HMAC.new(key, msg, self.digestmod).digest()
def verify(self, key, msg, sig):
# TODO: use constant compare to mitigate timing attack?
"""Verify the signature.
.. warning::
Does not protect against timing attack (no constant compare).
"""
return self.sign(key, msg) == sig
+1 -1
View File
@@ -3,7 +3,7 @@ import argparse
import base64
import sys
import M2Crypto.X509
import M2Crypto
from letsencrypt.acme.jose import b64
from letsencrypt.acme.jose import errors
+17 -13
View File
@@ -5,7 +5,7 @@ import pkg_resources
import unittest
import Crypto.PublicKey.RSA
import M2Crypto.X509
import M2Crypto
import mock
from letsencrypt.acme.jose import b64
@@ -205,29 +205,33 @@ class CLITest(unittest.TestCase):
def test_unverified(self):
from letsencrypt.acme.jose.jws import CLI
with mock.patch('sys.stdin') as sin, mock.patch('sys.stdout'):
with mock.patch('sys.stdin') as sin:
sin.read.return_value = '{"payload": "foo", "signature": "xxx"}'
self.assertEqual(-1, CLI.run(['verify']))
with mock.patch('sys.stdout'):
self.assertEqual(-1, CLI.run(['verify']))
def test_json(self):
from letsencrypt.acme.jose.jws import CLI
with mock.patch('sys.stdin') as sin, mock.patch('sys.stdout') as sout:
with mock.patch('sys.stdin') as sin:
sin.read.return_value = 'foo'
CLI.run(['sign', '-k', self.key_path, '-a', 'RS256',
'-p', 'jwk'])
sin.read.return_value = sout.write.mock_calls[0][1][0]
self.assertEqual(0, CLI.run(['verify']))
with mock.patch('sys.stdout') as sout:
CLI.run(['sign', '-k', self.key_path, '-a', 'RS256',
'-p', 'jwk'])
sin.read.return_value = sout.write.mock_calls[0][1][0]
self.assertEqual(0, CLI.run(['verify']))
def test_compact(self):
from letsencrypt.acme.jose.jws import CLI
with mock.patch('sys.stdin') as sin, mock.patch('sys.stdout') as sout:
with mock.patch('sys.stdin') as sin:
sin.read.return_value = 'foo'
CLI.run(['--compact', 'sign', '-k', self.key_path])
sin.read.return_value = sout.write.mock_calls[0][1][0]
self.assertEqual(0, CLI.run([
'--compact', 'verify', '--kty', 'RSA', '-k', self.key_path]))
with mock.patch('sys.stdout') as sout:
CLI.run(['--compact', 'sign', '-k', self.key_path])
sin.read.return_value = sout.write.mock_calls[0][1][0]
self.assertEqual(0, CLI.run([
'--compact', 'verify', '--kty', 'RSA',
'-k', self.key_path]))
if __name__ == '__main__':
+3 -2
View File
@@ -548,8 +548,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
def _enable_redirect(self, ssl_vhost, unused_options):
"""Redirect all equivalent HTTP traffic to ssl_vhost.
.. todo:: This enhancement should be rewritten and will unfortunately
require lots of debugging by hand.
.. todo:: This enhancement should be rewritten and will
unfortunately require lots of debugging by hand.
Adds Redirect directive to the port 80 equivalent of ssl_vhost
First the function attempts to find the vhost with equivalent
ip addresses that serves on non-ssl ports
+14 -14
View File
@@ -50,7 +50,7 @@ class ApacheDvsni(object):
def perform(self):
"""Peform a DVSNI challenge."""
if not self.achalls:
return None
return []
# Save any changes to the configuration as a precaution
# About to make temporary changes to the config
self.configurator.save()
@@ -160,19 +160,19 @@ class ApacheDvsni(object):
ips = " ".join(str(i) for i in ip_addrs)
document_root = os.path.join(
self.configurator.config.config_dir, "dvsni_page/")
return ("<VirtualHost " + ips + ">\n"
"ServerName " + achall.nonce_domain + "\n"
"UseCanonicalName on\n"
"SSLStrictSNIVHostCheck on\n"
"\n"
"LimitRequestBody 1048576\n"
"\n"
"Include " + self.configurator.parser.loc["ssl_options"] + "\n"
"SSLCertificateFile " + self.get_cert_file(achall) + "\n"
"SSLCertificateKeyFile " + achall.key.file + "\n"
"\n"
"DocumentRoot " + document_root + "\n"
"</VirtualHost>\n\n")
return ("<VirtualHost " + ips + ">{0}"
"ServerName " + achall.nonce_domain + "{0}"
"UseCanonicalName on{0}"
"SSLStrictSNIVHostCheck on{0}"
"{0}"
"LimitRequestBody 1048576{0}"
"{0}"
"Include " + self.configurator.parser.loc["ssl_options"] + "{0}"
"SSLCertificateFile " + self.get_cert_file(achall) + "{0}"
"SSLCertificateKeyFile " + achall.key.file + "{0}"
"{0}"
"DocumentRoot " + document_root + "{0}"
"</VirtualHost>{0}{0}".format(os.linesep))
def get_cert_file(self, achall):
"""Returns standardized name for challenge certificate.
+2 -1
View File
@@ -203,7 +203,8 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
def _path_satisfied(self, dom):
"""Returns whether a path has been completely satisfied."""
return all(self.responses[dom][i] is not None for i in self.paths[dom])
# Make sure that there are no 'None' or 'False' entries along path.
return all(self.responses[dom][i] for i in self.paths[dom])
def _get_chall_pref(self, domain):
"""Return list of challenge preferences.
+3
View File
@@ -11,6 +11,9 @@ from letsencrypt.acme import messages
from letsencrypt.client import errors
# https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning
requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()
logging.getLogger("requests").setLevel(logging.WARNING)
View File
@@ -481,7 +481,7 @@ class PathSatisfiedTest(unittest.TestCase):
self.handler.responses[dom[0]] = [None, "sat", "sat2", None]
self.handler.paths[dom[1]] = [0]
self.handler.responses[dom[1]] = ["sat", None, None, None]
self.handler.responses[dom[1]] = ["sat", None, None, False]
self.handler.paths[dom[2]] = [0]
self.handler.responses[dom[2]] = ["sat"]
@@ -496,7 +496,7 @@ class PathSatisfiedTest(unittest.TestCase):
self.assertTrue(self.handler._path_satisfied(dom[i]))
def test_not_satisfied(self):
dom = ["0", "1", "2"]
dom = ["0", "1", "2", "3"]
self.handler.paths[dom[0]] = [1, 2]
self.handler.responses[dom[0]] = ["sat1", None, "sat2", None]
@@ -506,6 +506,9 @@ class PathSatisfiedTest(unittest.TestCase):
self.handler.paths[dom[2]] = [0]
self.handler.responses[dom[2]] = [None]
self.handler.paths[dom[3]] = [0]
self.handler.responses[dom[3]] = [False]
for i in xrange(3):
self.assertFalse(self.handler._path_satisfied(dom[i]))
Executable → Regular
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
"""Parse command line and call the appropriate functions.
.. todo:: Sanity check all input. Be sure to avoid shell code etc...
Executable → Regular
+12 -2
View File
@@ -1,10 +1,15 @@
#!/usr/bin/env python
import codecs
import os
import re
from setuptools import setup
# Workaround for http://bugs.python.org/issue8876, see
# http://bugs.python.org/issue8876#msg208792
# This can be removed when using Python 2.7.9 or later:
# https://hg.python.org/cpython/raw-file/v2.7.9/Misc/NEWS
if os.path.abspath(__file__).split(os.path.sep)[1] == 'vagrant':
del os.link
def read_file(filename, encoding='utf8'):
"""Read unicode from given file."""
@@ -26,7 +31,9 @@ install_requires = [
'ConfArgParse',
'jsonschema',
'mock',
'ndg-httpsclient', # urllib3 InsecurePlatformWarning (#304)
'psutil>=2.1.0', # net_connections introduced in 2.1.0
'pyasn1', # urllib3 InsecurePlatformWarning (#304)
'pycrypto',
'PyOpenSSL',
'python-augeas',
@@ -40,7 +47,9 @@ install_requires = [
]
dev_extras = [
'pylint>=1.4.0', # upstream #248
# Pin astroid==1.3.5, pylint==1.4.2 as a workaround for #289
'astroid==1.3.5',
'pylint==1.4.2', # upstream #248
]
docs_extras = [
@@ -85,6 +94,7 @@ setup(
packages=[
'letsencrypt',
'letsencrypt.acme',
'letsencrypt.acme.jose',
'letsencrypt.client',
'letsencrypt.client.apache',
'letsencrypt.client.display',
+1 -1
View File
@@ -19,7 +19,7 @@ setenv =
basepython = python2.7
commands =
pip install -e .[testing]
python setup.py nosetests --with-coverage --cover-min-percentage=85
python setup.py nosetests --with-coverage --cover-min-percentage=86
[testenv:lint]
# recent versions of pylint do not support Python 2.6 (#97, #187)