Skip to content

Commit bf45c28

Browse files
committedFeb 22, 2017
[Server] add unit tests for sql injection
1 parent 4021490 commit bf45c28

File tree

4 files changed

+841
-0
lines changed

4 files changed

+841
-0
lines changed
 

‎tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ IF (WITH_SERVER)
183183
ADD_PYTHON_TEST(PyQgsServer test_qgsserver.py)
184184
ADD_PYTHON_TEST(PyQgsServerSettings test_qgsserver_settings.py)
185185
ADD_PYTHON_TEST(PyQgsServerProjectUtils test_qgsserver_projectutils.py)
186+
ADD_PYTHON_TEST(PyQgsServerSecurity test_qgsserver_security.py)
186187
ADD_PYTHON_TEST(PyQgsServerAccessControl test_qgsserver_accesscontrol.py)
187188
ADD_PYTHON_TEST(PyQgsServerWFST test_qgsserver_wfst.py)
188189
ADD_PYTHON_TEST(PyQgsOfflineEditingWFS test_offline_editing_wfs.py)
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for server security.
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
9+
"""
10+
__author__ = 'Paul Blottiere'
11+
__date__ = '31/01/2017'
12+
__copyright__ = 'Copyright 2017, The QGIS Project'
13+
# This will get replaced with a git SHA1 when you do a git archive
14+
__revision__ = '$Format:%H$'
15+
16+
from qgis.utils import spatialite_connect
17+
import os
18+
import time
19+
import urllib.parse
20+
from shutil import copyfile
21+
from qgis.core import QgsApplication
22+
from qgis.server import QgsServer
23+
from qgis.testing import unittest
24+
from utilities import unitTestDataPath
25+
26+
27+
class TestQgsServerSecurity(unittest.TestCase):
28+
29+
@classmethod
30+
def setUpClass(cls):
31+
cls.testdatapath = unitTestDataPath('qgis_server_security') + '/'
32+
cls.db = os.path.join(cls.testdatapath, 'db.sqlite')
33+
cls.db_clone = os.path.join(cls.testdatapath, 'db_clone.sqlite')
34+
cls.project = os.path.join(cls.testdatapath, 'project.qgs')
35+
cls.app = QgsApplication([], False)
36+
37+
@classmethod
38+
def tearDownClass(cls):
39+
cls.app.exitQgis()
40+
try:
41+
os.remove(cls.db_clone)
42+
except OSError:
43+
pass
44+
45+
def setUp(self):
46+
self.server = QgsServer()
47+
copyfile(self.db, self.db_clone)
48+
49+
def test_wms_getfeatureinfo_filter_and_based_blind(self):
50+
"""
51+
And-based blind attack to check the kind of database currently used (if
52+
the result is valid for the point nammed 'b', then sqlite_version()
53+
function exist).
54+
55+
But does not work because of the whitelist.
56+
57+
If you remove the safety check, this is a valid injection.
58+
"""
59+
60+
filter_sql = "point:\"name\" = 'b'"
61+
injection_sql = ") and (select sqlite_version()"
62+
63+
query = "{0} {1}".format(filter_sql, injection_sql)
64+
d, h = self.handle_request_wms_getfeatureinfo(query)
65+
66+
self.assertFalse(b"name = 'b'" in d)
67+
68+
def test_wms_getfeatureinfo_filter_time_based_blind(self):
69+
"""
70+
Time-based blind to check the current version of database. If the
71+
server is too long to respond, then we have the answer!
72+
73+
But it does not work because of the whitelist.
74+
75+
If you remove the safety check, this is a valid injection.
76+
"""
77+
78+
# first step, retrieve the version of sqlite by a regular way
79+
conn = spatialite_connect(self.db_clone)
80+
cur = conn.cursor()
81+
sql = "select sqlite_version()"
82+
sqlite_version = ''
83+
for row in cur.execute(sql):
84+
sqlite_version = row[0]
85+
conn.close()
86+
87+
# second step, check the time of response for an invalid version
88+
filter_sql = "point:\"name\" = 'b'"
89+
injection_sql = ") and (select case sqlite_version() when '0.0.0' then substr(upper(hex(randomblob(99999999))),0,1) end)--"
90+
91+
query = "{0} {1}".format(filter_sql, injection_sql)
92+
start = time.time()
93+
d, h = self.handle_request_wms_getfeatureinfo(query)
94+
duration_invalid_version = time.time() - start
95+
96+
# third step, check the time of response for a valid version
97+
# maximum: several seconds
98+
injection_sql = ") and (select case sqlite_version() when '{0}' then substr(upper(hex(randomblob(99999999))),0,1) end)--".format(sqlite_version)
99+
100+
query = "{0} {1}".format(filter_sql, injection_sql)
101+
start = time.time()
102+
d, h = self.handle_request_wms_getfeatureinfo(query)
103+
duration_valid_version = time.time() - start
104+
105+
# compare duration. On my computer when safety check is deactivated:
106+
# duration_invalid_version: 0.012360334396362305
107+
# duration_valid_version: 2.8810460567474365
108+
self.assertAlmostEqual(duration_valid_version, duration_invalid_version, delta=0.5)
109+
110+
def test_wms_getfeatureinfo_filter_stacked(self):
111+
"""
112+
The aim is to execute some staked queries. Here the 'drop' function is
113+
used but it could be done with create, insert, ...
114+
115+
But the filter string is split thanks to the semicolon so it seems
116+
totally ignored whatever the query is (even without the safety check).
117+
"""
118+
119+
filter_sql = "point:\"name\" = 'fake'"
120+
injection_sql = "); drop table point"
121+
122+
query = "{0} {1}".format(filter_sql, injection_sql)
123+
d, h = self.handle_request_wms_getfeatureinfo(query)
124+
125+
self.assertTrue(self.is_point_table_still_exist())
126+
127+
def test_wms_getfeatureinfo_filter_union_0(self):
128+
"""
129+
The aim is to retrieve name of tables within the database (like
130+
'SpatialIndex').
131+
132+
But the whitelist blocks this request because of invalid tokens.
133+
134+
If you remove the safety check, this is a valid injection.
135+
"""
136+
137+
filter_sql = "point:\"name\" = 'fake'"
138+
injection_sql = ") union select 1,1,name,1,1 from sqlite_master where type = \"table\" order by name--"
139+
140+
query = "{0} {1}".format(filter_sql, injection_sql)
141+
d, h = self.handle_request_wms_getfeatureinfo(query)
142+
143+
self.assertFalse(b'SpatialIndex' in d)
144+
145+
def test_wms_getfeatureinfo_filter_union_1(self):
146+
"""
147+
The aim is to retrieve data from an excluded layer.
148+
149+
But the whitelist blocks this request because of invalid tokens.
150+
151+
If you remove the safety check, this is a valid injection.
152+
"""
153+
154+
filter_sql = "point:\"name\" = 'fake'"
155+
injection_sql = ") union select 1,1,* from aoi--"
156+
157+
query = "{0} {1}".format(filter_sql, injection_sql)
158+
d, h = self.handle_request_wms_getfeatureinfo(query)
159+
160+
self.assertFalse(b'private_value' in d)
161+
162+
def test_wms_getfeatureinfo_filter_unicode(self):
163+
"""
164+
The aim is to send some invalid token in unicode to bypass the
165+
whitelist.
166+
167+
But unicode is interpreted and checked by the safety function.
168+
"""
169+
170+
# %3B -> semicolon
171+
filter_sql = "point:\"name\" = 'fake %3B'"
172+
d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
173+
self.assertTrue(self.check_service_exception_report(d))
174+
175+
def test_wms_getfeatureinfo_filter_whitelist(self):
176+
"""
177+
The aim is to check that some tokens cannot pass the safety check
178+
whatever their positions in the filter string.
179+
"""
180+
181+
# create
182+
filter_sql = "point:\"name\" = 'a'create"
183+
d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
184+
self.assertTrue(self.check_service_exception_report(d))
185+
186+
filter_sql = "point:\"name\" = 'a' create"
187+
d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
188+
self.assertTrue(self.check_service_exception_report(d))
189+
190+
# invalid token and escape single quote
191+
filter_sql = "point:\"name\" = 'a\\'create"
192+
d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
193+
self.assertTrue(self.check_service_exception_report(d))
194+
195+
# drop
196+
filter_sql = "point:\"name\" = 'a' drop"
197+
d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
198+
self.assertTrue(self.check_service_exception_report(d))
199+
200+
# select
201+
filter_sql = "point:\"name\" = 'a' select"
202+
d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
203+
self.assertTrue(self.check_service_exception_report(d))
204+
205+
# comments
206+
filter_sql = "point:\"name\" = 'a' #"
207+
d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
208+
self.assertTrue(self.check_service_exception_report(d))
209+
210+
filter_sql = "point:\"name\" = 'a' -"
211+
d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
212+
self.assertTrue(self.check_service_exception_report(d))
213+
214+
def test_wfs_getfeature_filter_stacked(self):
215+
"""
216+
The aim is to execute some staked queries within the 'Literal'
217+
and 'PropertyName' field. Here the 'drop' function is used but it
218+
could be done with create, insert, ...
219+
220+
But due to the implementation, these filters are not resolved on
221+
database side but in server side with QgsExpression. So, there's no
222+
'WHERE' clause and the query never reach the database. By the way,
223+
it's exactly the same thing whatever the kind of attacks and for
224+
the EXP_FILTER parameter too (filter described with QgsExpression).
225+
226+
It's typically the kind of SQL injection which has been fixed in
227+
mapserver several years ago:
228+
https://trac.osgeo.org/mapserver/ticket/3874
229+
"""
230+
231+
# ogc:Literal / ogc:PropertyIsEqualTo
232+
literal = "4')); drop table point --"
233+
filter_xml = "<ogc:Filter%20xmlns:ogc=\"http://www.opengis.net/ogc\"><ogc:PropertyIsEqualTo><ogc:PropertyName>pkuid</ogc:PropertyName><ogc:Literal>{0}</ogc:Literal></ogc:PropertyIsEqualTo></ogc:Filter>".format(literal)
234+
self.handle_request_wfs_getfeature_filter(filter_xml)
235+
self.assertTrue(self.is_point_table_still_exist())
236+
237+
# ogc:Literal / ogc:PropertyIsLike
238+
literal = "4')); drop table point --"
239+
filter_xml = "<ogc:Filter%20xmlns:ogc=\"http://www.opengis.net/ogc\"><ogc:PropertyIsLike><ogc:PropertyName>pkuid</ogc:PropertyName><ogc:Literal>{0}</ogc:Literal></ogc:PropertyIsLike></ogc:Filter>".format(literal)
240+
self.handle_request_wfs_getfeature_filter(filter_xml)
241+
self.assertTrue(self.is_point_table_still_exist())
242+
243+
# ogc:PropertyName / ogc:PropertyIsLike
244+
propname = "name = 'a')); drop table point --"
245+
filter_xml = "<ogc:Filter%20xmlns:ogc=\"http://www.opengis.net/ogc\"><ogc:PropertyIsLike><ogc:PropertyName>{0}</ogc:PropertyName><ogc:Literal>4</ogc:Literal></ogc:PropertyIsLike></ogc:Filter>".format(propname)
246+
self.handle_request_wfs_getfeature_filter(filter_xml)
247+
self.assertTrue(self.is_point_table_still_exist())
248+
249+
def test_wms_getmap_sld_stacked(self):
250+
"""
251+
The aim is to execute some staked queries within the 'Literal'
252+
and 'PropertyName' field. Here the 'drop' function is used but it
253+
could be done with create, insert, ...
254+
255+
However it's not working because special characters are duplicated. For
256+
example, with 'Literal' as "4')); drop table point --", the underlying
257+
query is:
258+
SELECT .... AND (("pkuid" = '4'')); drop table point --'))
259+
"""
260+
261+
literal = "4')); drop table point --"
262+
sld = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:ogc=\"http://www.opengis.net/ogc\" xsi:schemaLocation=\"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd\" version=\"1.1.0\" xmlns:se=\"http://www.opengis.net/se\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"> <NamedLayer> <se:Name>point</se:Name> <UserStyle> <se:Name>point</se:Name> <se:FeatureTypeStyle> <se:Rule> <se:Name>Single symbol</se:Name> <ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\"> <ogc:PropertyIsEqualTo> <ogc:PropertyName>pkuid</ogc:PropertyName> <ogc:Literal>{0}</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <se:PointSymbolizer> <se:Graphic> <se:Mark> <se:WellKnownName>circle</se:WellKnownName> <se:Fill><se:SvgParameter name=\"fill\">5e86a1</se:SvgParameter></se:Fill><se:Stroke><se:SvgParameter name=\"stroke\">000000</se:SvgParameter></se:Stroke></se:Mark><se:Size>7</se:Size></se:Graphic></se:PointSymbolizer></se:Rule></se:FeatureTypeStyle></UserStyle></NamedLayer></StyledLayerDescriptor>".format(literal)
263+
self.handle_request_wms_getmap(sld)
264+
self.assertTrue(self.is_point_table_still_exist())
265+
266+
def check_service_exception_report(self, d):
267+
"""
268+
Return True if a ServiceExceptionReport is raised, False otherwise
269+
"""
270+
271+
if b'<ServiceExceptionReport' in d:
272+
return True
273+
else:
274+
return False
275+
276+
def handle_request_wfs_getfeature_filter(self, filter_xml):
277+
qs = "&".join(["%s=%s" % i for i in list({
278+
"MAP": urllib.parse.quote(self.project),
279+
"SERVICE": "WFS",
280+
"VERSION": "1.1.1",
281+
"REQUEST": "GetFeature",
282+
"TYPENAME": "point",
283+
"STYLES": "",
284+
"CRS": "EPSG:32613",
285+
"FILTER": filter_xml}.items())])
286+
287+
return self.server.handleRequest(qs)
288+
289+
def handle_request_wms_getfeatureinfo(self, filter_sql):
290+
qs = "&".join(["%s=%s" % i for i in list({
291+
"MAP": urllib.parse.quote(self.project),
292+
"SERVICE": "WMS",
293+
"VERSION": "1.1.1",
294+
"REQUEST": "GetFeatureInfo",
295+
"QUERY_LAYERS": "point",
296+
"LAYERS": "point",
297+
"STYLES": "",
298+
"FORMAT": "image/png",
299+
"HEIGHT": "500",
300+
"WIDTH": "500",
301+
"BBOX": "606171,4822867,612834,4827375",
302+
"CRS": "EPSG:32613",
303+
"FILTER": filter_sql}.items())])
304+
305+
return self._result(self.server.handleRequest(qs))
306+
307+
def handle_request_wms_getmap(self, sld):
308+
qs = "&".join(["%s=%s" % i for i in list({
309+
"MAP": urllib.parse.quote(self.project),
310+
"SERVICE": "WMS",
311+
"VERSION": "1.0.0",
312+
"REQUEST": "GetMap",
313+
"QUERY_LAYERS": "point",
314+
"LAYERS": "point",
315+
"STYLES": "",
316+
"FORMAT": "image/png",
317+
"HEIGHT": "500",
318+
"WIDTH": "500",
319+
"BBOX": "606171,4822867,612834,4827375",
320+
"CRS": "EPSG:32613",
321+
"SLD": sld}.items())])
322+
323+
return self._result(self.server.handleRequest(qs))
324+
325+
def is_point_table_still_exist(self):
326+
conn = spatialite_connect(self.db_clone)
327+
cur = conn.cursor()
328+
sql = "select * from point"
329+
point_table_exist = True
330+
try:
331+
cur.execute(sql)
332+
except:
333+
point_table_exist = False
334+
conn.close()
335+
336+
return point_table_exist
337+
338+
def _result(self, data):
339+
headers = {}
340+
for line in data[0].decode('UTF-8').split("\n"):
341+
if line != "":
342+
header = line.split(":")
343+
self.assertEqual(len(header), 2, line)
344+
headers[str(header[0])] = str(header[1]).strip()
345+
346+
return data[1], headers
347+
348+
349+
if __name__ == '__main__':
350+
unittest.main()
259 KB
Binary file not shown.
Lines changed: 490 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,490 @@
1+
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
2+
<qgis projectname="" version="2.99.0-Master">
3+
<title></title>
4+
<autotransaction active="0"/>
5+
<evaluateDefaultValues active="0"/>
6+
<layer-tree-group checked="Qt::Checked" expanded="1" name="">
7+
<customproperties/>
8+
<layer-tree-layer checked="Qt::Checked" id="point20170206172502668" expanded="1" name="point">
9+
<customproperties/>
10+
</layer-tree-layer>
11+
<layer-tree-layer checked="Qt::Checked" id="aoi20170206172502660" expanded="1" name="aoi">
12+
<customproperties/>
13+
</layer-tree-layer>
14+
<layer-tree-layer checked="Qt::Checked" id="background20170206172502667" expanded="1" name="background">
15+
<customproperties/>
16+
</layer-tree-layer>
17+
</layer-tree-group>
18+
<snapping-settings mode="2" enabled="0" intersection-snapping="0" type="1" unit="2" tolerance="0">
19+
<individual-layer-settings>
20+
<layer-setting enabled="0" id="aoi20170206172502660" type="1" tolerance="0" units="2"/>
21+
<layer-setting enabled="0" id="point20170206172502668" type="1" tolerance="0" units="2"/>
22+
<layer-setting enabled="0" id="background20170206172502667" type="1" tolerance="0" units="2"/>
23+
</individual-layer-settings>
24+
</snapping-settings>
25+
<relations/>
26+
<mapcanvas>
27+
<units>meters</units>
28+
<extent>
29+
<xmin>605206.40265044115949422</xmin>
30+
<ymin>4822097.18003295548260212</ymin>
31+
<xmax>613514.99562231602612883</xmax>
32+
<ymax>4827725.58172358199954033</ymax>
33+
</extent>
34+
<rotation>0</rotation>
35+
<projections>0</projections>
36+
<destinationsrs>
37+
<spatialrefsys>
38+
<proj4>+proj=utm +zone=13 +datum=WGS84 +units=m +no_defs</proj4>
39+
<srsid>3097</srsid>
40+
<srid>32613</srid>
41+
<authid>EPSG:32613</authid>
42+
<description>WGS 84 / UTM zone 13N</description>
43+
<projectionacronym>utm</projectionacronym>
44+
<ellipsoidacronym>WGS84</ellipsoidacronym>
45+
<geographicflag>false</geographicflag>
46+
</spatialrefsys>
47+
</destinationsrs>
48+
<rendermaptile>0</rendermaptile>
49+
<layer_coordinate_transform_info/>
50+
</mapcanvas>
51+
<layer-tree-canvas>
52+
<custom-order enabled="0">
53+
<item>aoi20170206172502660</item>
54+
<item>background20170206172502667</item>
55+
<item>point20170206172502668</item>
56+
</custom-order>
57+
</layer-tree-canvas>
58+
<legend updateDrawingOrder="true">
59+
<legendlayer drawingOrder="-1" showFeatureCount="0" checked="Qt::Checked" open="true" name="point">
60+
<filegroup hidden="false" open="true">
61+
<legendlayerfile visible="1" layerid="point20170206172502668" isInOverview="0"/>
62+
</filegroup>
63+
</legendlayer>
64+
<legendlayer drawingOrder="-1" showFeatureCount="0" checked="Qt::Checked" open="true" name="aoi">
65+
<filegroup hidden="false" open="true">
66+
<legendlayerfile visible="1" layerid="aoi20170206172502660" isInOverview="0"/>
67+
</filegroup>
68+
</legendlayer>
69+
<legendlayer drawingOrder="-1" showFeatureCount="0" checked="Qt::Checked" open="true" name="background">
70+
<filegroup hidden="false" open="true">
71+
<legendlayerfile visible="1" layerid="background20170206172502667" isInOverview="0"/>
72+
</filegroup>
73+
</legendlayer>
74+
</legend>
75+
<projectlayers>
76+
<maplayer geometry="Polygon" simplifyAlgorithm="0" maximumScale="1e+08" hasScaleBasedVisibilityFlag="0" type="vector" minimumScale="0" simplifyMaxScale="1" simplifyLocal="1" simplifyDrawingHints="1" simplifyDrawingTol="1" readOnly="0">
77+
<extent>
78+
<xmin>606510</xmin>
79+
<ymin>4823130</ymin>
80+
<xmax>612510</xmax>
81+
<ymax>4827130</ymax>
82+
</extent>
83+
<id>aoi20170206172502660</id>
84+
<datasource>dbname='./db_clone.sqlite' table="aoi" (geometry) sql=</datasource>
85+
<keywordList>
86+
<value></value>
87+
</keywordList>
88+
<layername>aoi</layername>
89+
<srs>
90+
<spatialrefsys>
91+
<proj4>+proj=utm +zone=13 +datum=WGS84 +units=m +no_defs</proj4>
92+
<srsid>3097</srsid>
93+
<srid>32613</srid>
94+
<authid>EPSG:32613</authid>
95+
<description>WGS 84 / UTM zone 13N</description>
96+
<projectionacronym>utm</projectionacronym>
97+
<ellipsoidacronym>WGS84</ellipsoidacronym>
98+
<geographicflag>false</geographicflag>
99+
</spatialrefsys>
100+
</srs>
101+
<provider encoding="UTF-8">spatialite</provider>
102+
<vectorjoins/>
103+
<layerDependencies/>
104+
<defaults>
105+
<default field="pkuid" expression=""/>
106+
<default field="ftype" expression=""/>
107+
</defaults>
108+
<constraints>
109+
<constraint field="pkuid" unique_strength="1" exp_strength="0" constraints="3" notnull_strength="1"/>
110+
<constraint field="ftype" unique_strength="0" exp_strength="0" constraints="0" notnull_strength="0"/>
111+
</constraints>
112+
<constraintExpressions>
113+
<constraint exp="" field="pkuid" desc=""/>
114+
<constraint exp="" field="ftype" desc=""/>
115+
</constraintExpressions>
116+
<dataDependencies/>
117+
<expressionfields/>
118+
<map-layer-style-manager current="">
119+
<map-layer-style name=""/>
120+
</map-layer-style-manager>
121+
<edittypes/>
122+
<renderer-v2 forceraster="0" symbollevels="0" type="singleSymbol" enableorderby="0">
123+
<symbols>
124+
<symbol type="fill" alpha="1" clip_to_extent="1" name="0">
125+
<layer locked="0" enabled="1" class="SimpleFill" pass="0">
126+
<prop k="border_width_map_unit_scale" v="0,0,0,0,0,0"/>
127+
<prop k="color" v="105,193,210,255"/>
128+
<prop k="joinstyle" v="bevel"/>
129+
<prop k="offset" v="0,0"/>
130+
<prop k="offset_map_unit_scale" v="0,0,0,0,0,0"/>
131+
<prop k="offset_unit" v="MM"/>
132+
<prop k="outline_color" v="0,0,0,255"/>
133+
<prop k="outline_style" v="solid"/>
134+
<prop k="outline_width" v="0.26"/>
135+
<prop k="outline_width_unit" v="MM"/>
136+
<prop k="style" v="solid"/>
137+
</layer>
138+
</symbol>
139+
</symbols>
140+
<rotation/>
141+
<sizescale/>
142+
</renderer-v2>
143+
<labeling type="simple"/>
144+
<customproperties/>
145+
<blendMode>0</blendMode>
146+
<featureBlendMode>0</featureBlendMode>
147+
<layerTransparency>0</layerTransparency>
148+
<annotationform>.</annotationform>
149+
<aliases>
150+
<alias field="pkuid" index="0" name=""/>
151+
<alias field="ftype" index="1" name=""/>
152+
</aliases>
153+
<excludeAttributesWMS/>
154+
<excludeAttributesWFS/>
155+
<attributeactions>
156+
<defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/>
157+
</attributeactions>
158+
<attributetableconfig actionWidgetStyle="dropDown" sortExpression="" sortOrder="0">
159+
<columns>
160+
<column hidden="0" type="field" width="-1" name="pkuid"/>
161+
<column hidden="0" type="field" width="-1" name="ftype"/>
162+
<column hidden="1" type="actions" width="-1"/>
163+
</columns>
164+
</attributetableconfig>
165+
<editform>.</editform>
166+
<editforminit/>
167+
<editforminitcodesource>0</editforminitcodesource>
168+
<editforminitfilepath>.</editforminitfilepath>
169+
<editforminitcode><![CDATA[]]></editforminitcode>
170+
<featformsuppress>0</featformsuppress>
171+
<editorlayout>generatedlayout</editorlayout>
172+
<widgets/>
173+
<conditionalstyles>
174+
<rowstyles/>
175+
<fieldstyles/>
176+
</conditionalstyles>
177+
<expressionfields/>
178+
<previewExpression>"pkuid"</previewExpression>
179+
<mapTip></mapTip>
180+
</maplayer>
181+
<maplayer geometry="Polygon" simplifyAlgorithm="0" maximumScale="1e+08" hasScaleBasedVisibilityFlag="0" type="vector" minimumScale="0" simplifyMaxScale="1" simplifyLocal="1" simplifyDrawingHints="1" simplifyDrawingTol="1" readOnly="0">
182+
<extent>
183+
<xmin>606410</xmin>
184+
<ymin>4823030</ymin>
185+
<xmax>612610</xmax>
186+
<ymax>4827230</ymax>
187+
</extent>
188+
<id>background20170206172502667</id>
189+
<datasource>dbname='./db_clone.sqlite' table="background" (geometry) sql=</datasource>
190+
<keywordList>
191+
<value></value>
192+
</keywordList>
193+
<layername>background</layername>
194+
<srs>
195+
<spatialrefsys>
196+
<proj4>+proj=utm +zone=13 +datum=WGS84 +units=m +no_defs</proj4>
197+
<srsid>3097</srsid>
198+
<srid>32613</srid>
199+
<authid>EPSG:32613</authid>
200+
<description>WGS 84 / UTM zone 13N</description>
201+
<projectionacronym>utm</projectionacronym>
202+
<ellipsoidacronym>WGS84</ellipsoidacronym>
203+
<geographicflag>false</geographicflag>
204+
</spatialrefsys>
205+
</srs>
206+
<provider encoding="UTF-8">spatialite</provider>
207+
<vectorjoins/>
208+
<layerDependencies/>
209+
<defaults>
210+
<default field="pkuid" expression=""/>
211+
<default field="text" expression=""/>
212+
</defaults>
213+
<constraints>
214+
<constraint field="pkuid" unique_strength="1" exp_strength="0" constraints="3" notnull_strength="1"/>
215+
<constraint field="text" unique_strength="0" exp_strength="0" constraints="0" notnull_strength="0"/>
216+
</constraints>
217+
<constraintExpressions>
218+
<constraint exp="" field="pkuid" desc=""/>
219+
<constraint exp="" field="text" desc=""/>
220+
</constraintExpressions>
221+
<dataDependencies/>
222+
<expressionfields/>
223+
<map-layer-style-manager current="">
224+
<map-layer-style name=""/>
225+
</map-layer-style-manager>
226+
<edittypes/>
227+
<renderer-v2 forceraster="0" symbollevels="0" type="singleSymbol" enableorderby="0">
228+
<symbols>
229+
<symbol type="fill" alpha="1" clip_to_extent="1" name="0">
230+
<layer locked="0" enabled="1" class="SimpleFill" pass="0">
231+
<prop k="border_width_map_unit_scale" v="0,0,0,0,0,0"/>
232+
<prop k="color" v="141,51,82,255"/>
233+
<prop k="joinstyle" v="bevel"/>
234+
<prop k="offset" v="0,0"/>
235+
<prop k="offset_map_unit_scale" v="0,0,0,0,0,0"/>
236+
<prop k="offset_unit" v="MM"/>
237+
<prop k="outline_color" v="0,0,0,255"/>
238+
<prop k="outline_style" v="solid"/>
239+
<prop k="outline_width" v="0.26"/>
240+
<prop k="outline_width_unit" v="MM"/>
241+
<prop k="style" v="solid"/>
242+
</layer>
243+
</symbol>
244+
</symbols>
245+
<rotation/>
246+
<sizescale/>
247+
</renderer-v2>
248+
<labeling type="simple"/>
249+
<customproperties/>
250+
<blendMode>0</blendMode>
251+
<featureBlendMode>0</featureBlendMode>
252+
<layerTransparency>0</layerTransparency>
253+
<annotationform>.</annotationform>
254+
<aliases>
255+
<alias field="pkuid" index="0" name=""/>
256+
<alias field="text" index="1" name=""/>
257+
</aliases>
258+
<excludeAttributesWMS/>
259+
<excludeAttributesWFS/>
260+
<attributeactions>
261+
<defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/>
262+
</attributeactions>
263+
<attributetableconfig actionWidgetStyle="dropDown" sortExpression="" sortOrder="0">
264+
<columns>
265+
<column hidden="0" type="field" width="-1" name="pkuid"/>
266+
<column hidden="0" type="field" width="-1" name="text"/>
267+
<column hidden="1" type="actions" width="-1"/>
268+
</columns>
269+
</attributetableconfig>
270+
<editform>.</editform>
271+
<editforminit/>
272+
<editforminitcodesource>0</editforminitcodesource>
273+
<editforminitfilepath>.</editforminitfilepath>
274+
<editforminitcode><![CDATA[]]></editforminitcode>
275+
<featformsuppress>0</featformsuppress>
276+
<editorlayout>generatedlayout</editorlayout>
277+
<widgets/>
278+
<conditionalstyles>
279+
<rowstyles/>
280+
<fieldstyles/>
281+
</conditionalstyles>
282+
<expressionfields/>
283+
<previewExpression>"pkuid"</previewExpression>
284+
<mapTip></mapTip>
285+
</maplayer>
286+
<maplayer geometry="Point" simplifyAlgorithm="0" maximumScale="1e+08" hasScaleBasedVisibilityFlag="0" type="vector" minimumScale="0" simplifyMaxScale="1" simplifyLocal="1" simplifyDrawingHints="1" simplifyDrawingTol="1" readOnly="0">
287+
<extent>
288+
<xmin>609510</xmin>
289+
<ymin>4825130</ymin>
290+
<xmax>609510</xmax>
291+
<ymax>4825130</ymax>
292+
</extent>
293+
<id>point20170206172502668</id>
294+
<datasource>dbname='./db_clone.sqlite' table="point" (geometry) sql=</datasource>
295+
<keywordList>
296+
<value></value>
297+
</keywordList>
298+
<layername>point</layername>
299+
<srs>
300+
<spatialrefsys>
301+
<proj4>+proj=utm +zone=13 +datum=WGS84 +units=m +no_defs</proj4>
302+
<srsid>3097</srsid>
303+
<srid>32613</srid>
304+
<authid>EPSG:32613</authid>
305+
<description>WGS 84 / UTM zone 13N</description>
306+
<projectionacronym>utm</projectionacronym>
307+
<ellipsoidacronym>WGS84</ellipsoidacronym>
308+
<geographicflag>false</geographicflag>
309+
</spatialrefsys>
310+
</srs>
311+
<provider encoding="UTF-8">spatialite</provider>
312+
<vectorjoins/>
313+
<layerDependencies/>
314+
<defaults>
315+
<default field="pkuid" expression=""/>
316+
<default field="text" expression=""/>
317+
<default field="name" expression=""/>
318+
</defaults>
319+
<constraints>
320+
<constraint field="pkuid" unique_strength="1" exp_strength="0" constraints="3" notnull_strength="1"/>
321+
<constraint field="text" unique_strength="0" exp_strength="0" constraints="0" notnull_strength="0"/>
322+
<constraint field="name" unique_strength="0" exp_strength="0" constraints="0" notnull_strength="0"/>
323+
</constraints>
324+
<constraintExpressions>
325+
<constraint exp="" field="pkuid" desc=""/>
326+
<constraint exp="" field="text" desc=""/>
327+
<constraint exp="" field="name" desc=""/>
328+
</constraintExpressions>
329+
<dataDependencies/>
330+
<expressionfields/>
331+
<map-layer-style-manager current="">
332+
<map-layer-style name=""/>
333+
</map-layer-style-manager>
334+
<edittypes/>
335+
<renderer-v2 forceraster="0" symbollevels="0" type="singleSymbol" enableorderby="0">
336+
<symbols>
337+
<symbol type="marker" alpha="1" clip_to_extent="1" name="0">
338+
<layer locked="0" enabled="1" class="SimpleMarker" pass="0">
339+
<prop k="angle" v="0"/>
340+
<prop k="color" v="85,91,195,255"/>
341+
<prop k="horizontal_anchor_point" v="1"/>
342+
<prop k="joinstyle" v="bevel"/>
343+
<prop k="name" v="circle"/>
344+
<prop k="offset" v="0,0"/>
345+
<prop k="offset_map_unit_scale" v="0,0,0,0,0,0"/>
346+
<prop k="offset_unit" v="MM"/>
347+
<prop k="outline_color" v="0,0,0,255"/>
348+
<prop k="outline_style" v="solid"/>
349+
<prop k="outline_width" v="0"/>
350+
<prop k="outline_width_map_unit_scale" v="0,0,0,0,0,0"/>
351+
<prop k="outline_width_unit" v="MM"/>
352+
<prop k="scale_method" v="diameter"/>
353+
<prop k="size" v="2"/>
354+
<prop k="size_map_unit_scale" v="0,0,0,0,0,0"/>
355+
<prop k="size_unit" v="MM"/>
356+
<prop k="vertical_anchor_point" v="1"/>
357+
</layer>
358+
</symbol>
359+
</symbols>
360+
<rotation/>
361+
<sizescale/>
362+
</renderer-v2>
363+
<labeling type="simple"/>
364+
<customproperties/>
365+
<blendMode>0</blendMode>
366+
<featureBlendMode>0</featureBlendMode>
367+
<layerTransparency>0</layerTransparency>
368+
<annotationform>.</annotationform>
369+
<aliases>
370+
<alias field="pkuid" index="0" name=""/>
371+
<alias field="text" index="1" name=""/>
372+
<alias field="name" index="2" name=""/>
373+
</aliases>
374+
<excludeAttributesWMS/>
375+
<excludeAttributesWFS/>
376+
<attributeactions>
377+
<defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/>
378+
</attributeactions>
379+
<attributetableconfig actionWidgetStyle="dropDown" sortExpression="" sortOrder="0">
380+
<columns>
381+
<column hidden="0" type="field" width="-1" name="pkuid"/>
382+
<column hidden="0" type="field" width="-1" name="text"/>
383+
<column hidden="1" type="actions" width="-1"/>
384+
</columns>
385+
</attributetableconfig>
386+
<editform>.</editform>
387+
<editforminit/>
388+
<editforminitcodesource>0</editforminitcodesource>
389+
<editforminitfilepath>.</editforminitfilepath>
390+
<editforminitcode><![CDATA[]]></editforminitcode>
391+
<featformsuppress>0</featformsuppress>
392+
<editorlayout>generatedlayout</editorlayout>
393+
<widgets/>
394+
<conditionalstyles>
395+
<rowstyles/>
396+
<fieldstyles/>
397+
</conditionalstyles>
398+
<expressionfields/>
399+
<previewExpression>"pkuid"</previewExpression>
400+
<mapTip></mapTip>
401+
</maplayer>
402+
</projectlayers>
403+
<properties>
404+
<Macros>
405+
<pythonCode type="QString"></pythonCode>
406+
</Macros>
407+
<WMSOnlineResource type="QString"></WMSOnlineResource>
408+
<WMSKeywordList type="QStringList">
409+
<value></value>
410+
</WMSKeywordList>
411+
<Identify>
412+
<disabledLayers type="QStringList"/>
413+
</Identify>
414+
<WMSContactOrganization type="QString"></WMSContactOrganization>
415+
<Measurement>
416+
<AreaUnits type="QString">m2</AreaUnits>
417+
<DistanceUnits type="QString">meters</DistanceUnits>
418+
</Measurement>
419+
<WMSContactMail type="QString"></WMSContactMail>
420+
<WFSLayersPrecision>
421+
<point20170206172502668 type="int">8</point20170206172502668>
422+
</WFSLayersPrecision>
423+
<WMSContactPerson type="QString"></WMSContactPerson>
424+
<DefaultStyles>
425+
<AlphaInt type="int">255</AlphaInt>
426+
<ColorRamp type="QString"></ColorRamp>
427+
<Line type="QString"></Line>
428+
<RandomColors type="bool">true</RandomColors>
429+
<Fill type="QString"></Fill>
430+
<Marker type="QString"></Marker>
431+
</DefaultStyles>
432+
<Legend>
433+
<filterByMap type="bool">false</filterByMap>
434+
</Legend>
435+
<Paths>
436+
<Absolute type="bool">false</Absolute>
437+
</Paths>
438+
<WMSFees type="QString">conditions unknown</WMSFees>
439+
<WFSLayers type="QStringList">
440+
<value>point20170206172502668</value>
441+
</WFSLayers>
442+
<Gui>
443+
<CanvasColorBluePart type="int">255</CanvasColorBluePart>
444+
<CanvasColorRedPart type="int">255</CanvasColorRedPart>
445+
<SelectionColorRedPart type="int">255</SelectionColorRedPart>
446+
<SelectionColorBluePart type="int">0</SelectionColorBluePart>
447+
<SelectionColorAlphaPart type="int">255</SelectionColorAlphaPart>
448+
<CanvasColorGreenPart type="int">255</CanvasColorGreenPart>
449+
<SelectionColorGreenPart type="int">255</SelectionColorGreenPart>
450+
</Gui>
451+
<WMSAccessConstraints type="QString">None</WMSAccessConstraints>
452+
<WMSRestrictedLayers type="QStringList">
453+
<value>aoi</value>
454+
</WMSRestrictedLayers>
455+
<WMSContactPosition type="QString"></WMSContactPosition>
456+
<WMSImageQuality type="int">90</WMSImageQuality>
457+
<WMSRequestDefinedDataSources type="bool">false</WMSRequestDefinedDataSources>
458+
<WMSSegmentizeFeatureInfoGeometry type="bool">false</WMSSegmentizeFeatureInfoGeometry>
459+
<Measure>
460+
<Ellipsoid type="QString">NONE</Ellipsoid>
461+
</Measure>
462+
<WMSAddWktGeometry type="bool">false</WMSAddWktGeometry>
463+
<PositionPrecision>
464+
<DegreeFormat type="QString">MU</DegreeFormat>
465+
<Automatic type="bool">true</Automatic>
466+
<DecimalPlaces type="int">2</DecimalPlaces>
467+
</PositionPrecision>
468+
<WMSServiceAbstract type="QString"></WMSServiceAbstract>
469+
<WMSUseLayerIDs type="bool">false</WMSUseLayerIDs>
470+
<WFSTLayers>
471+
<Delete type="QStringList"/>
472+
<Update type="QStringList"/>
473+
<Insert type="QStringList"/>
474+
</WFSTLayers>
475+
<SpatialRefSys>
476+
<ProjectCRSID type="int">3097</ProjectCRSID>
477+
<ProjectCRSProj4String type="QString">+proj=utm +zone=13 +datum=WGS84 +units=m +no_defs</ProjectCRSProj4String>
478+
<ProjectCrs type="QString">EPSG:32613</ProjectCrs>
479+
</SpatialRefSys>
480+
<WMSServiceTitle type="QString"></WMSServiceTitle>
481+
<WMSPrecision type="QString">8</WMSPrecision>
482+
<WCSUrl type="QString"></WCSUrl>
483+
<WCSLayers type="QStringList"/>
484+
<WMSContactPhone type="QString"></WMSContactPhone>
485+
<WFSUrl type="QString"></WFSUrl>
486+
<WMSServiceCapabilities type="bool">true</WMSServiceCapabilities>
487+
<WMSUrl type="QString"></WMSUrl>
488+
</properties>
489+
<visibility-presets/>
490+
</qgis>

0 commit comments

Comments
 (0)
Please sign in to comment.