Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use requests and other improvements #11

Merged
merged 29 commits into from
Jan 24, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a877a56
Bump version.
Kami Jan 24, 2013
901c6e7
Default 'verify_cert' to True.
Kami Jan 24, 2013
8c9aa84
Use new style classes.
Kami Jan 24, 2013
7aaa169
Fix logger name.
Kami Jan 24, 2013
d5c0727
Simplify the code and use requests library for performing HTTP requests.
Kami Jan 24, 2013
e3c6d73
Don't use star import, remove unused imports.
Kami Jan 24, 2013
0048bb6
Remove notice which doesn't apply anymore.
Kami Jan 24, 2013
832598b
Update .gitignore.
Kami Jan 24, 2013
2d8dfd1
Add requests dependency.
Kami Jan 24, 2013
131ebba
Update changes.
Kami Jan 24, 2013
6013683
Simplify mock api server runner and properly kill the spawned process.
Kami Jan 24, 2013
f123963
Use setuptools setup function.
Kami Jan 24, 2013
5506a66
Also run tests with PyPy.
Kami Jan 24, 2013
4a73c6c
Add requirements.txt file.
Kami Jan 24, 2013
e565824
Update changes and setup.py.
Kami Jan 24, 2013
eec4589
Remove now unnecessary checks.
Kami Jan 24, 2013
c8100d6
Allow user to pass custom path to the CA certificates bundle and prop…
Kami Jan 24, 2013
a99fad2
Remove Python 2.5 compatibility stuff since, we don't support it anym…
Kami Jan 24, 2013
0873fb7
Use requests inside test.
Kami Jan 24, 2013
d65c5c8
For now skip test which times out in PyPy.
Kami Jan 24, 2013
7089152
pep8 fixes in tests/.
Kami Jan 24, 2013
3f38a43
pep8 fixes in yubico/.
Kami Jan 24, 2013
0e3ccb8
Modify travis config to also run pep8 on the files.
Kami Jan 24, 2013
4c585c6
Add pep8 to requirements.txt.
Kami Jan 24, 2013
3fa471b
Fix typo.
Kami Jan 24, 2013
f8187e8
If CA_CERTS_BUNDLE_PATH is not specified, look for a CA bundle file i…
Kami Jan 24, 2013
0799f9a
Remove noe uneccesary Python <= 2.5 check.
Kami Jan 24, 2013
dc06eea
Remove unused import.
Kami Jan 24, 2013
42df7ac
Update changes.
Kami Jan 24, 2013
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.tox
mock_api_server.log
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ language: python
python:
- "2.6"
- "2.7"
- "pypy"

script: python setup.py test
script:
- pep8 tests/
- pep8 yubico/
- python setup.py test

notifications:
email:
Expand Down
18 changes: 13 additions & 5 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
in development:
1.6.0 - in development:

- Avoid busy-looping (add time.sleep) when waiting for responses.
- Allow user to pass in value `0` for `sl` argument in `verify` and `verify_multi` method
- Throw an exception inside `verify` and `verify_multi` method if timeout has
* Allow user to specify a path to the CA bundle which is used for verifying the
server SSL certificate by setting `CA_CERTS_BUNDLE_PATH` variable
* When selecting which CA bundle is used for verifying the server SSL
certificate look for the bundle in some common locations. #10
* Drop support for Python 2.5
* Use `requests` library for performing HTTP requests and turn SSL cert
verification on by default
* Avoid busy-looping (add time.sleep) when waiting for responses.
* Allow user to pass in value `0` for `sl` argument in `verify` and
`verify_multi` method
* Throw an exception inside `verify` and `verify_multi` method if timeout has
occurred or invalid status code is returned
- Add logging
* Add logging
7 changes: 0 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,6 @@ Both methods can also throw one of the following exceptions:
**REPLAYED_REQUEST** or no response was received from any of the servers
in the specified time frame (default timeout = 10 seconds)

## Notes

If you are using secure connection (https) and want to validate the server certificate, you need to pass ``verify_cert = True`` argument when instantiating the yubico class and set ``CA_CERTS`` variable in the
``yubico/httplib_ssl.py`` file so it points to a file containing trusted CA certificates.

For a backward compatibility, ``verify_cert`` is set to ``False`` by default.

[1]: http://www.yubico.com
[2]: http://www.yubico.com/developers/intro/
[3]: http://www.yubico.com/developers/version2/
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pep8
requests == 1.1.0
12 changes: 5 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@
from os.path import splitext, basename, join as pjoin
from unittest import TextTestRunner, TestLoader

from distutils.core import setup
from setuptools import setup
from distutils.core import Command

sys.path.insert(0, pjoin(os.path.dirname(__file__)))
from tests.utils import MockAPIServerRunner

TEST_PATHS = ['tests']

pre_python26 = (sys.version_info[0] == 2 and sys.version_info[1] < 6)

version_re = re.compile(
r'__version__ = (\(.*?\))')

Expand Down Expand Up @@ -76,19 +74,19 @@ def _run_mock_api_server(self):
setup(name='yubico',
version='.' . join(map(str, version)),
description='Python Yubico Client',
author='Tomaž Muraus',
author='Tomaz Muraus',
author_email='tomaz+pypi@tomaz.me',
license='BSD',
url='http://github.com/Kami/python-yubico-client/',
download_url='http://github.com/Kami/python-yubico-client/downloads/',
packages=['yubico'],
provides=['yubico'],
requires=([], ['ssl'],)[pre_python26],
install_requires=[
'requests == 1.1.0',
],
cmdclass={
'test': TestCommand,
},


classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
Expand Down
4 changes: 2 additions & 2 deletions tests/mock_http_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ def _send_status(self, status, signature=None):
def main():
usage = 'usage: %prog --port=<port>'
parser = OptionParser(usage=usage)
parser.add_option("--port", dest='port', default=8881,
help='Port to listen on', metavar='PORT')
parser.add_option('--port', dest='port', default=8881,
help='Port to listen on', metavar='PORT')

(options, args) = parser.parse_args()

Expand Down
34 changes: 21 additions & 13 deletions tests/test_yubico.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
import unittest
import httplib
import urllib

import requests

from yubico import yubico
from yubico.otp import OTP
Expand All @@ -13,7 +13,7 @@ class TestOTPClass(unittest.TestCase):
def test_otp_class(self):
otp1 = OTP('tlerefhcvijlngibueiiuhkeibbcbecehvjiklltnbbl')
otp2 = OTP('jjjjjjjjnhe.ngcgjeiuujjjdtgihjuecyixinxunkhj',
translate_otp=True)
translate_otp=True)

self.assertEqual(otp1.device_id, 'tlerefhcvijl')
self.assertEqual(otp2.otp,
Expand All @@ -22,16 +22,30 @@ def test_otp_class(self):

class TestYubicoVerifySingle(unittest.TestCase):
def setUp(self):
yubico.API_URLS = (
'127.0.0.1:8881/wsapi/2.0/verify',
)
yubico.API_URLS = ('127.0.0.1:8881/wsapi/2.0/verify',)
yubico.DEFAULT_TIMEOUT = 2
yubico.CA_CERTS_BUNDLE_PATH = None

self.client_no_verify_sig = yubico.Yubico('1234', None,
use_https=False)
self.client_verify_sig = yubico.Yubico('1234', 'secret123456',
use_https=False)

def test_invalid_custom_ca_certs_path(self):
if hasattr(sys, 'pypy_version_info'):
# TODO: Figure out why this breaks PyPy
return

yubico.CA_CERTS_BUNDLE_PATH = '/does/not/exist.1'
client = yubico.Yubico('1234', 'secret123456')

try:
client.verify('bad')
except requests.exceptions.SSLError:
pass
else:
self.fail('SSL exception was not thrown')

def test_replayed_otp(self):
self._set_mock_action('REPLAYED_OTP')

Expand Down Expand Up @@ -97,13 +111,7 @@ def _set_mock_action(self, action, port=8881, signature=None):
if signature:
path += '&signature=%s' % (signature)

conn = httplib.HTTPConnection('127.0.0.1:' + str(port))
conn.request('GET', path)

try:
conn.getresponse()
except:
pass
requests.get(url='http://127.0.0.1:%s%s' % (port, path))


if __name__ == '__main__':
Expand Down
60 changes: 11 additions & 49 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from os.path import join as pjoin


def waitForStartUp(process, pid, address, timeout=10):
def waitForStartUp(process, address, timeout=10):
# connect to it, with a timeout in case something went wrong
start = time.time()
while time.time() < start + timeout:
Expand All @@ -26,57 +26,18 @@ def waitForStartUp(process, pid, address, timeout=10):
# see if process is still alive
process.poll()

if pid and process.returncode is None:
os.kill(pid, signal.SIGKILL)
if process and process.returncode is None:
process.terminate()
raise RuntimeError("Couldn't connect to server; aborting test")


class ProcessRunner(object):
def setUp(self, *args, **kwargs):
# clean up old.
p = self.getPid()
if p != None:
try:
# remember, process may already be dead.
os.kill(p, 9)
time.sleep(0.01)
except:
pass
pass

def tearDown(self, *args, **kwargs):
spid = self.getPid()
if spid:
max_wait = 1
os.kill(spid, signal.SIGTERM)
slept = 0
while (slept < max_wait):
time.sleep(0.5)
if not self.isAlive(spid):
if os.path.exists(self.pid_fname):
os.unlink(self.pid_fname)
break
slept += 0.5
if (slept > max_wait and self.isAlive(spid)):
os.kill(spid, signal.SIGKILL)
if os.path.exists(self.pid_fname):
os.unlink(self.pid_fname)
raise Exception('Server did not shut down correctly')
else:
print 'Unable to locate pid file (%s)!' % self.pid_fname

def isAlive(self, pid):
try:
os.kill(pid, 0)
return 1
except OSError, err:
return err.errno == errno.EPERM

def getPid(self):
if self.process:
return self.process.pid
elif os.path.exists(self.pid_fname):
return int(open(self.pid_fname, 'r').read())
return None
self.process.terminate()


class MockAPIServerRunner(ProcessRunner):
Expand All @@ -87,16 +48,17 @@ def setUp(self, *args, **kwargs):
self.cwd = os.getcwd()
self.process = None
self.base_dir = pjoin(self.cwd)
self.pid_fname = pjoin(self.cwd, 'mock_api_server.pid')
self.log_path = pjoin(self.cwd, 'mock_api_server.log')

super(MockAPIServerRunner, self).setUp(*args, **kwargs)
script = pjoin(os.path.dirname(__file__), 'mock_http_server.py')

with open(self.log_path, 'a+') as log_fp:
args = '%s --port=%s' % (script, str(self.port))
self.process = subprocess.Popen(args, shell=True,
cwd=self.base_dir, stdout=log_fp, stderr=log_fp)
waitForStartUp(self.process, self.getPid(),
('127.0.0.1', self.port), 10)
args = [script, '--port=%s' % (self.port)]

self.process = subprocess.Popen(args, shell=False,
cwd=self.base_dir, stdout=log_fp,
stderr=log_fp)
waitForStartUp(self.process, ('127.0.0.1', self.port), 10)
atexit.register(self.tearDown)
5 changes: 1 addition & 4 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
[tox]
envlist = py25,py26,py27,pypy
envlist = py26,py27,pypy

[testenv]
commands = python setup.py test

[testenv:py25]
deps = ssl
2 changes: 1 addition & 1 deletion yubico/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = (1, 5, 'dev')
__version__ = (1, 6, 0, 'dev')
63 changes: 0 additions & 63 deletions yubico/httplib_ssl.py

This file was deleted.

4 changes: 2 additions & 2 deletions yubico/modhex.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,13 @@

index = {}
for i, alphabet in enumerate(alphabets):
for letter in alphabet:
for letter in alphabet:
index.setdefault(letter, set()).update([i])

HEX = u"0123456789abcdef"
MODHEX = u"cbdefghijklnrtuv"


def translate(otp, to=MODHEX):
"""Return set() of possible modhex interpretations of a Yubikey otp.

Expand All @@ -146,4 +147,3 @@ def translate(otp, to=MODHEX):
translation = dict(zip((ord(c) for c in a), to))
translated.add(otp.translate(translation))
return translated

4 changes: 2 additions & 2 deletions yubico/otp.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import modhex


class OTP():
class OTP(object):
def __init__(self, otp, translate_otp=True):
self.otp = self.get_otp_modehex_interpretation(otp) \
if translate_otp else otp
if translate_otp else otp

self.device_id = self.otp[:12]
self.session_counter = None
Expand Down
Loading