Skip to content

Commit a0e0fcf

Browse files
committed
[tests] WMS/WFS PKI authentication test
1 parent 16a88e6 commit a0e0fcf

File tree

4 files changed

+246
-1
lines changed

4 files changed

+246
-1
lines changed

tests/src/python/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,6 @@ IF (WITH_SERVER)
161161
ADD_PYTHON_TEST(PyQgsServerWFST test_qgsserver_wfst.py)
162162
ADD_PYTHON_TEST(PyQgsOfflineEditingWFS test_offline_editing_wfs.py)
163163
ADD_PYTHON_TEST(PyQgsAuthManagerPasswordOWSTest test_authmanager_password_ows.py)
164-
#ADD_PYTHON_TEST(PyQgsAuthManagerPKIOWSTest test_authmanager_pki_ows.py)
164+
ADD_PYTHON_TEST(PyQgsAuthManagerPKIOWSTest test_authmanager_pki_ows.py)
165165
ADD_PYTHON_TEST(PyQgsAuthManagerPKIPostgresTest test_authmanager_pki_postgres.py)
166166
ENDIF (WITH_SERVER)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Tests for auth manager WMS/WFS using QGIS Server through PKI
4+
enabled qgis_wrapped_server.py.
5+
6+
This is an integration test for QGIS Desktop Auth Manager WFS and WMS provider
7+
and QGIS Server WFS/WMS that check if QGIS can use a stored auth manager auth
8+
configuration to access an HTTP Basic protected endpoint.
9+
10+
11+
From build dir, run: ctest -R PyQgsAuthManagerPKIOWSTest -V
12+
13+
.. note:: This program is free software; you can redistribute it and/or modify
14+
it under the terms of the GNU General Public License as published by
15+
the Free Software Foundation; either version 2 of the License, or
16+
(at your option) any later version.
17+
"""
18+
import os
19+
import sys
20+
import re
21+
import subprocess
22+
import tempfile
23+
import urllib
24+
import stat
25+
26+
__author__ = 'Alessandro Pasotti'
27+
__date__ = '25/10/2016'
28+
__copyright__ = 'Copyright 2016, The QGIS Project'
29+
# This will get replaced with a git SHA1 when you do a git archive
30+
__revision__ = '$Format:%H$'
31+
32+
from shutil import rmtree
33+
34+
from utilities import unitTestDataPath, waitServer
35+
from qgis.core import (
36+
QgsAuthManager,
37+
QgsAuthMethodConfig,
38+
QgsVectorLayer,
39+
QgsRasterLayer,
40+
)
41+
42+
from qgis.PyQt.QtNetwork import QSslCertificate
43+
44+
from qgis.testing import (
45+
start_app,
46+
unittest,
47+
)
48+
49+
try:
50+
QGIS_SERVER_ENDPOINT_PORT = os.environ['QGIS_SERVER_ENDPOINT_PORT']
51+
except:
52+
QGIS_SERVER_ENDPOINT_PORT = '0' # Auto
53+
54+
55+
QGIS_AUTH_DB_DIR_PATH = tempfile.mkdtemp()
56+
57+
os.environ['QGIS_AUTH_DB_DIR_PATH'] = QGIS_AUTH_DB_DIR_PATH
58+
59+
qgis_app = start_app()
60+
61+
62+
class TestAuthManager(unittest.TestCase):
63+
64+
@classmethod
65+
def setUpAuth(cls):
66+
"""Run before all tests and set up authentication"""
67+
authm = QgsAuthManager.instance()
68+
assert (authm.setMasterPassword('masterpassword', True))
69+
cls.sslrootcert_path = os.path.join(cls.certsdata_path, 'chains_subissuer-issuer-root_issuer2-root2.pem')
70+
cls.sslcert = os.path.join(cls.certsdata_path, 'gerardus_cert.pem')
71+
cls.sslkey = os.path.join(cls.certsdata_path, 'gerardus_key.pem')
72+
assert os.path.isfile(cls.sslcert)
73+
assert os.path.isfile(cls.sslkey)
74+
assert os.path.isfile(cls.sslrootcert_path)
75+
os.chmod(cls.sslcert, stat.S_IRUSR)
76+
os.chmod(cls.sslkey, stat.S_IRUSR)
77+
os.chmod(cls.sslrootcert_path, stat.S_IRUSR)
78+
cls.auth_config = QgsAuthMethodConfig("PKI-Paths")
79+
cls.auth_config.setConfig('certpath', cls.sslcert)
80+
cls.auth_config.setConfig('keypath', cls.sslkey)
81+
cls.auth_config.setName('test_pki_auth_config')
82+
cls.username = 'Gerardus'
83+
cls.sslrootcert = QSslCertificate.fromPath(cls.sslrootcert_path)
84+
assert cls.sslrootcert is not None
85+
authm.storeCertAuthorities(cls.sslrootcert)
86+
authm.rebuildCaCertsCache()
87+
authm.rebuildTrustedCaCertsCache()
88+
assert (authm.storeAuthenticationConfig(cls.auth_config)[0])
89+
assert cls.auth_config.isValid()
90+
91+
# cls.server_cert = os.path.join(cls.certsdata_path, 'localhost_ssl_cert.pem')
92+
cls.server_cert = os.path.join(cls.certsdata_path, '127_0_0_1_ssl_cert.pem')
93+
# cls.server_key = os.path.join(cls.certsdata_path, 'localhost_ssl_key.pem')
94+
cls.server_key = os.path.join(cls.certsdata_path, '127_0_0_1_ssl_key.pem')
95+
cls.server_rootcert = cls.sslrootcert_path
96+
os.chmod(cls.server_cert, stat.S_IRUSR)
97+
os.chmod(cls.server_key, stat.S_IRUSR)
98+
os.chmod(cls.server_rootcert, stat.S_IRUSR)
99+
100+
os.environ['QGIS_SERVER_HOST'] = cls.hostname
101+
os.environ['QGIS_SERVER_PORT'] = str(cls.port)
102+
os.environ['QGIS_SERVER_PKI_KEY'] = cls.server_key
103+
os.environ['QGIS_SERVER_PKI_CERTIFICATE'] = cls.server_cert
104+
os.environ['QGIS_SERVER_PKI_USERNAME'] = cls.username
105+
os.environ['QGIS_SERVER_PKI_AUTHORITY'] = cls.server_rootcert
106+
107+
@classmethod
108+
def setUpClass(cls):
109+
"""Run before all tests:
110+
Creates an auth configuration"""
111+
cls.port = QGIS_SERVER_ENDPOINT_PORT
112+
# Clean env just to be sure
113+
env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
114+
for ev in env_vars:
115+
try:
116+
del os.environ[ev]
117+
except KeyError:
118+
pass
119+
cls.testdata_path = unitTestDataPath('qgis_server')
120+
cls.certsdata_path = os.path.join(unitTestDataPath('auth_system'), 'certs_keys')
121+
cls.project_path = os.path.join(cls.testdata_path, "test_project.qgs")
122+
# cls.hostname = 'localhost'
123+
cls.protocol = 'https'
124+
cls.hostname = '127.0.0.1'
125+
126+
cls.setUpAuth()
127+
128+
server_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
129+
'qgis_wrapped_server.py')
130+
cls.server = subprocess.Popen([sys.executable, server_path],
131+
env=os.environ, stdout=subprocess.PIPE)
132+
line = cls.server.stdout.readline()
133+
cls.port = int(re.findall(b':(\d+)', line)[0])
134+
assert cls.port != 0
135+
# Wait for the server process to start
136+
assert waitServer('%s://%s:%s' % (cls.protocol, cls.hostname, cls.port)), "Server is not responding! %s://%s:%s" % (cls.protocol, cls.hostname, cls.port)
137+
138+
@classmethod
139+
def tearDownClass(cls):
140+
"""Run after all tests"""
141+
cls.server.terminate()
142+
rmtree(QGIS_AUTH_DB_DIR_PATH)
143+
del cls.server
144+
145+
def setUp(self):
146+
"""Run before each test."""
147+
pass
148+
149+
def tearDown(self):
150+
"""Run after each test."""
151+
pass
152+
153+
@classmethod
154+
def _getWFSLayer(cls, type_name, layer_name=None, authcfg=None):
155+
"""
156+
WFS layer factory
157+
"""
158+
if layer_name is None:
159+
layer_name = 'wfs_' + type_name
160+
parms = {
161+
'srsname': 'EPSG:4326',
162+
'typename': type_name,
163+
'url': '%s://%s:%s/?map=%s' % (cls.protocol, cls.hostname, cls.port, cls.project_path),
164+
'version': 'auto',
165+
'table': '',
166+
}
167+
if authcfg is not None:
168+
parms.update({'authcfg': authcfg})
169+
try: # Py2
170+
uri = ' '.join([("%s='%s'" % (k, v.decode('utf-8'))) for k, v in list(parms.items())])
171+
except AttributeError: # Py3
172+
uri = ' '.join([("%s='%s'" % (k, v)) for k, v in list(parms.items())])
173+
wfs_layer = QgsVectorLayer(uri, layer_name, 'WFS')
174+
return wfs_layer
175+
176+
@classmethod
177+
def _getWMSLayer(cls, layers, layer_name=None, authcfg=None):
178+
"""
179+
WMS layer factory
180+
"""
181+
if layer_name is None:
182+
layer_name = 'wms_' + layers.replace(',', '')
183+
parms = {
184+
'crs': 'EPSG:4326',
185+
'url': '%s://%s:%s/?map=%s' % (cls.protocol, cls.hostname, cls.port, cls.project_path),
186+
'format': 'image/png',
187+
# This is needed because of a really weird implementation in QGIS Server, that
188+
# replaces _ in the the real layer name with spaces
189+
'layers': urllib.parse.quote(layers.replace('_', ' ')),
190+
'styles': '',
191+
'version': 'auto',
192+
#'sql': '',
193+
}
194+
if authcfg is not None:
195+
parms.update({'authcfg': authcfg})
196+
uri = '&'.join([("%s=%s" % (k, v.replace('=', '%3D'))) for k, v in list(parms.items())])
197+
wms_layer = QgsRasterLayer(uri, layer_name, 'wms')
198+
return wms_layer
199+
200+
def testValidAuthAccess(self):
201+
"""
202+
Access the protected layer with valid credentials
203+
Note: cannot test invalid access in a separate test because
204+
it would fail the subsequent (valid) calls due to cached connections
205+
"""
206+
wfs_layer = self._getWFSLayer('testlayer_èé', authcfg=self.auth_config.id())
207+
self.assertTrue(wfs_layer.isValid())
208+
wms_layer = self._getWMSLayer('testlayer_èé', authcfg=self.auth_config.id())
209+
self.assertTrue(wms_layer.isValid())
210+
211+
212+
if __name__ == '__main__':
213+
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICnzCCAggCCQDJz/SWI+RkwzANBgkqhkiG9w0BAQsFADCBqDELMAkGA1UEBhMC
3+
VVMxDzANBgNVBAgTBkFsYXNrYTESMBAGA1UEBxMJQW5jaG9yYWdlMRUwEwYDVQQK
4+
EwxRR0lTIFRlc3QgQ0ExHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0eTEb
5+
MBkGA1UEAxMSUUdJUyBUZXN0IFJvb3QyIENBMSAwHgYJKoZIhvcNAQkBFhF0ZXN0
6+
Y2VydEBxZ2lzLm9yZzAeFw0xNjExMDQxMDM0NDVaFw0xNjEyMDQxMDM0NDVaMH8x
7+
CzAJBgNVBAYTAkNBMQ8wDQYDVQQIDAZBbGFza2ExEjAQBgNVBAcMCUFuY2hvcmFn
8+
ZTEVMBMGA1UECgwMUUdJUyBUZXN0IENBMRIwEAYDVQQDDAkxMjcuMC4wLjExIDAe
9+
BgkqhkiG9w0BCQEWEXRlc3RjZXJ0QHFnaXMub3JnMIGfMA0GCSqGSIb3DQEBAQUA
10+
A4GNADCBiQKBgQDPPRCUxvl0kcDr6tvpFJ8LuwCAP9p9SOC7Fx1JvQfLVv/Ded7x
11+
7Tn967S57AGgVyYgA09qD68UUlGLKi2fqVIO2OsBflJ9iKyOM71UlIA7mH96+ZSZ
12+
xYjxpHoDQ8F6856+RXHBGq8JkAxUmASCMq6a5Zcw+7C7R5/4CYHFXGFgRQIDAQAB
13+
MA0GCSqGSIb3DQEBCwUAA4GBAClo2Omx26R7Av9dr51I23no4Kp3CBey81pAkn5w
14+
jGE9nuPy+ndaSVV0+8+WMyPf7eZrkVOn41DsF1Z6eiIQsQ+2JQdOc3lIXwpJKXJJ
15+
0RK1ZLLaH95II3E0U6cGatDcs/jEua26T/Th6+eg5lP+mfovUBJLerYHZW0Jsfs+
16+
GlzG
17+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIICXQIBAAKBgQDPPRCUxvl0kcDr6tvpFJ8LuwCAP9p9SOC7Fx1JvQfLVv/Ded7x
3+
7Tn967S57AGgVyYgA09qD68UUlGLKi2fqVIO2OsBflJ9iKyOM71UlIA7mH96+ZSZ
4+
xYjxpHoDQ8F6856+RXHBGq8JkAxUmASCMq6a5Zcw+7C7R5/4CYHFXGFgRQIDAQAB
5+
AoGBAMyNycAQZknZVEOJHmeCIzrA6k2suUzQkoIY3p/aJcdfqDSaJqVFMuifr1OU
6+
0EYjv0359nkJ4hZ86mAi0cW2q3aVawkc32XgK7DS/QoJ6XL8ysyWEoBifdOnDBBY
7+
wmMXgVI1SlR+6PvK12DpeoRAVS+BBlBt1J7lb36CHfl/FVxBAkEA7FZjCK12pBrT
8+
4OZm8XPtFrAvBuFptAwDxdm58gxj2EEuO7aBF3Vs5Ha63J5JCAVEQe/PJUNyWBZx
9+
I7420MI/qwJBAOB66sqV4fq5jT7W0DqvHOdBjQlvaRlo9ZNG+hZt/8QoRc02PaIa
10+
aqICt2zmxT/WCuKBw8a0JvTs/GlrLi9er88CQH+0il0FBofUa0sqlNPB1Yod97tb
11+
EHgWye8eIGkXotgXGHlxu73GWOn28jAGY+Yumlyaza8QC/hnYAl1Xj9dx3MCQFZU
12+
bz+B7OpzubJVArfO6Jq3Rvo98nlnOCpvvXYqz5Ystst49LMG3cN4r/odtfYa5wy9
13+
QwGD/wdqrJgONDDbhVkCQQCnqfTgHgK7c8z+s03htgEHEGj+tNID8Brrg8y9X3QA
14+
i4E7CnQ587WiQsq0jv5txqoksqMXGf/aQuYJEqJyELrB
15+
-----END RSA PRIVATE KEY-----

0 commit comments

Comments
 (0)