forked from openlp/openlp
Add some proper tests
This commit is contained in:
parent
347636d078
commit
ced77e4ec3
@ -1,162 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2013 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
"""
|
||||
The :mod:`http` module manages the HTTP authorisation logic. This code originates from
|
||||
http://tools.cherrypy.org/wiki/AuthenticationAndAccessRestrictions
|
||||
|
||||
"""
|
||||
|
||||
import cherrypy
|
||||
import logging
|
||||
import os
|
||||
|
||||
from mako.template import Template
|
||||
|
||||
from openlp.core.lib import Settings
|
||||
from openlp.core.utils import AppLocation, translate
|
||||
|
||||
SESSION_KEY = '_cp_openlp'
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_credentials(user_name, password):
|
||||
"""
|
||||
Verifies credentials for username and password.
|
||||
Returns None on success or a string describing the error on failure
|
||||
"""
|
||||
if user_name == Settings().value(u'remotes/user id') and password == Settings().value(u'remotes/password'):
|
||||
return None
|
||||
else:
|
||||
return translate('RemotePlugin.Mobile', 'Incorrect username or password.')
|
||||
|
||||
|
||||
def check_authentication(*args, **kwargs):
|
||||
"""
|
||||
A tool that looks in config for 'auth.require'. If found and it is not None, a login is required and the entry is
|
||||
evaluated as a list of conditions that the user must fulfill
|
||||
"""
|
||||
conditions = cherrypy.request.config.get('auth.require', None)
|
||||
a = cherrypy.request
|
||||
print a
|
||||
if not Settings().value(u'remotes/authentication enabled'):
|
||||
return None
|
||||
if conditions is not None:
|
||||
username = cherrypy.session.get(SESSION_KEY)
|
||||
if username:
|
||||
cherrypy.request.login = username
|
||||
for condition in conditions:
|
||||
# A condition is just a callable that returns true or false
|
||||
if not condition():
|
||||
raise cherrypy.HTTPRedirect("/auth/login")
|
||||
else:
|
||||
raise cherrypy.HTTPRedirect("/auth/login")
|
||||
|
||||
cherrypy.tools.auth = cherrypy.Tool('before_handler', check_authentication)
|
||||
|
||||
|
||||
def require_auth(*conditions):
|
||||
"""
|
||||
A decorator that appends conditions to the auth.require config variable.
|
||||
"""
|
||||
def decorate(f):
|
||||
"""
|
||||
Lets process a decoration.
|
||||
"""
|
||||
if not hasattr(f, '_cp_config'):
|
||||
f._cp_config = dict()
|
||||
if 'auth.require' not in f._cp_config:
|
||||
f._cp_config['auth.require'] = []
|
||||
f._cp_config['auth.require'].extend(conditions)
|
||||
return f
|
||||
return decorate
|
||||
|
||||
|
||||
class AuthController(object):
|
||||
|
||||
def on_login(self, username):
|
||||
"""
|
||||
Called on successful login
|
||||
"""
|
||||
pass
|
||||
|
||||
def on_logout(self, username):
|
||||
"""
|
||||
Called on logout
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_login_form(self, username, message=None, from_page="/"):
|
||||
"""
|
||||
Provides a login form
|
||||
"""
|
||||
if not message:
|
||||
message = translate('RemotePlugin.Mobile', 'Enter login information')
|
||||
variables = {
|
||||
'title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 User Login'),
|
||||
'from_page': from_page,
|
||||
'message': message,
|
||||
'username': username
|
||||
}
|
||||
directory = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), u'remotes', u'html')
|
||||
login_html = os.path.normpath(os.path.join(directory, u'login.html'))
|
||||
html = Template(filename=login_html, input_encoding=u'utf-8', output_encoding=u'utf-8').render(**variables)
|
||||
cherrypy.response.headers['Content-Type'] = u'text/html'
|
||||
cherrypy.response.status = 200
|
||||
return html
|
||||
|
||||
@cherrypy.expose
|
||||
def login(self, username=None, password=None, from_page="/"):
|
||||
"""
|
||||
Provides the actual login control
|
||||
"""
|
||||
if username is None or password is None:
|
||||
return self.get_login_form("", from_page=from_page)
|
||||
error_msg = check_credentials(username, password)
|
||||
if error_msg:
|
||||
return self.get_login_form(username, from_page, error_msg,)
|
||||
else:
|
||||
cherrypy.session[SESSION_KEY] = cherrypy.request.login = username
|
||||
self.on_login(username)
|
||||
raise cherrypy.HTTPRedirect(from_page or "/")
|
||||
|
||||
@cherrypy.expose
|
||||
def logout(self, from_page="/"):
|
||||
"""
|
||||
Provides the actual logout functions
|
||||
"""
|
||||
sess = cherrypy.session
|
||||
username = sess.get(SESSION_KEY, None)
|
||||
sess[SESSION_KEY] = None
|
||||
if username:
|
||||
cherrypy.request.login = None
|
||||
self.on_logout(username)
|
||||
raise cherrypy.HTTPRedirect(from_page or "/")
|
||||
|
@ -277,10 +277,9 @@ class HttpRouter(object):
|
||||
"""
|
||||
Common function to process HTTP requests where secure or insecure
|
||||
"""
|
||||
url = urlparse.urlparse(cherrypy.url())
|
||||
response = None
|
||||
for route, func in self.routes:
|
||||
match = re.match(route, url.path)
|
||||
match = re.match(route, url_path)
|
||||
if match:
|
||||
log.debug('Route "%s" matched "%s"', route, url_path)
|
||||
args = []
|
||||
|
@ -1,81 +0,0 @@
|
||||
"""
|
||||
This module contains tests for the lib submodule of the Remotes plugin.
|
||||
"""
|
||||
import os
|
||||
from unittest import TestCase
|
||||
from tempfile import mkstemp
|
||||
from mock import patch, MagicMock
|
||||
|
||||
from openlp.core.lib import Settings
|
||||
from openlp.plugins.remotes.lib.httpauth import check_credentials, check_authentication
|
||||
from PyQt4 import QtGui
|
||||
|
||||
__default_settings__ = {
|
||||
u'remotes/twelve hour': True,
|
||||
u'remotes/port': 4316,
|
||||
u'remotes/https port': 4317,
|
||||
u'remotes/https enabled': False,
|
||||
u'remotes/user id': u'openlp',
|
||||
u'remotes/password': u'password',
|
||||
u'remotes/authentication enabled': False,
|
||||
u'remotes/ip address': u'0.0.0.0'
|
||||
}
|
||||
|
||||
SESSION_KEY = '_cp_openlp'
|
||||
|
||||
|
||||
class TestAuth(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`lib` module.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Create the UI
|
||||
"""
|
||||
fd, self.ini_file = mkstemp(u'.ini')
|
||||
Settings().set_filename(self.ini_file)
|
||||
self.application = QtGui.QApplication.instance()
|
||||
Settings().extend_default_settings(__default_settings__)
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Delete all the C++ objects at the end so that we don't have a segfault
|
||||
"""
|
||||
del self.application
|
||||
os.unlink(self.ini_file)
|
||||
os.unlink(Settings().fileName())
|
||||
|
||||
def check_credentials_test(self):
|
||||
"""
|
||||
Test the Authentication check routine with credentials.
|
||||
"""
|
||||
# GIVEN: A user and password in settings
|
||||
Settings().setValue(u'remotes/user id', u'twinkle')
|
||||
Settings().setValue(u'remotes/password', u'mongoose')
|
||||
|
||||
# WHEN: We run the function with no input
|
||||
authenticated = check_credentials(u'', u'')
|
||||
|
||||
# THEN: The authentication will fail with an error message
|
||||
self.assertEqual(authenticated, u'Incorrect username or password.',
|
||||
u'The return should be a error message string')
|
||||
|
||||
# WHEN: We run the function with the correct input
|
||||
authenticated = check_credentials(u'twinkle', u'mongoose')
|
||||
|
||||
# THEN: The authentication will pass.
|
||||
self.assertEqual(authenticated, None, u'The return should be a None string')
|
||||
|
||||
def check_auth_inactive_test(self):
|
||||
"""
|
||||
Test the Authentication check routine when inactive.
|
||||
"""
|
||||
# GIVEN: A access which is secure
|
||||
Settings().setValue(u'remotes/authentication enabled', False)
|
||||
|
||||
# WHEN: We run the function with no input
|
||||
with patch(u'cherrypy.request.config'):
|
||||
authenticated = check_authentication(None, None)
|
||||
|
||||
# THEN: The authentication will fail with an error message
|
||||
self.assertEqual(authenticated, None, u'The authentication should return None as not required')
|
@ -6,10 +6,9 @@ import os
|
||||
from unittest import TestCase
|
||||
from tempfile import mkstemp
|
||||
from mock import patch, MagicMock
|
||||
import cherrypy
|
||||
|
||||
from openlp.core.lib import Settings
|
||||
from openlp.plugins.remotes.lib.httpserver import HttpConnection
|
||||
from openlp.plugins.remotes.lib.httpserver import HttpRouter
|
||||
from PyQt4 import QtGui
|
||||
|
||||
__default_settings__ = {
|
||||
@ -23,10 +22,8 @@ __default_settings__ = {
|
||||
u'remotes/ip address': u'0.0.0.0'
|
||||
}
|
||||
|
||||
SESSION_KEY = '_cp_openlp'
|
||||
|
||||
|
||||
class TestAuth(TestCase):
|
||||
class TestRouter(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`lib` module.
|
||||
"""
|
||||
@ -38,7 +35,7 @@ class TestAuth(TestCase):
|
||||
Settings().set_filename(self.ini_file)
|
||||
self.application = QtGui.QApplication.instance()
|
||||
Settings().extend_default_settings(__default_settings__)
|
||||
self.server = HttpConnection(None)
|
||||
self.router = HttpRouter()
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
@ -49,19 +46,24 @@ class TestAuth(TestCase):
|
||||
|
||||
def process_http_request_test(self):
|
||||
"""
|
||||
Test the Authentication check routine with credentials.
|
||||
Test the router control functionality
|
||||
"""
|
||||
# GIVEN: A user and password in settings
|
||||
cherrypy = MagicMock()
|
||||
cherrypy.url.return_value = "nosetest/apl/poll"
|
||||
# GIVEN: A testing set of Routes
|
||||
mocked_function = MagicMock()
|
||||
test_route = [
|
||||
(r'^/stage/api/poll$', mocked_function),
|
||||
]
|
||||
self.router.routes = test_route
|
||||
|
||||
print cherrypy.url()
|
||||
# WHEN: called with a poll route
|
||||
self.router.process_http_request(u'/stage/api/poll', None)
|
||||
|
||||
with patch(u'url.path') as mocked_url:
|
||||
mocked_url.return_value = "nosetest/apl/poll"
|
||||
self.server._process_http_request(None, None)
|
||||
# THEN: the function should have been called only once
|
||||
assert mocked_function.call_count == 1, \
|
||||
u'The mocked function should have been matched and called once.'
|
||||
|
||||
self.assertFalse()
|
||||
|
||||
#self.assertFalse()
|
||||
|
||||
# WHEN: We run the function with no input
|
||||
#authenticated = check_credentials(u'', u'')
|
Loading…
Reference in New Issue
Block a user