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

Be able to run QGIS server in multi project mode #4116

Merged
merged 1 commit into from
Sep 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
58 changes: 42 additions & 16 deletions doc/integrator/backend_qgis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,30 @@ QGIS Desktop configuration
OWS configuration
*****************

You should setup your OWS service in the QGIS project properties in the OWS
tab.
You should setup your OWS service in the QGIS project properties in the OWS tab.

You should take care to **uncheck** the checkbox *User Layer ids as names*. If
this checkbox is enabled you will have unreliable layers name.
You should **check** *Service Capabilities* and **fill** *Online resource* to correctly generate
the URL's in the capabilities.

You should **check** the checkbox *Add geometry to feature response* in order
to make the WMS GetFeatureInfo working correctly.
In the *WMS capabilities* section, you should take care to **uncheck** the checkbox *User layer ids as
names*. If this checkbox is enabled you will have unreliable layers name.

In the *WFS capabilities* section, you should check the layers that need to be
available in the WFS service.
In the *WMS capabilities* section, you should **check** the checkbox *Add geometry to feature response* in
order to make the WMS GetFeatureInfo working correctly.

Finally, your QGIS project layers CRS should all be in the same CSR. This is subject to
change.
In the *WFS capabilities* section, you should check the layers that need to be available in the WFS service.
Also take care on the Update Insert delete column to make the layer available throw the WFS transactional
API.

Finally, your QGIS project layers CRS should all be in the same CSR. This is subject to change.

Connect to Postgres database
****************************

This section is subject to change when the QGIS plugin is available.

The way you should connect QGIS to the database is based on an external file
called ``pg_service.conf`` located in the home directory. The content of this file
is as follows:
called ``pg_service.conf`` located in the home directory. The content of this file is as follows:

.. code::

Expand All @@ -79,8 +80,7 @@ You can have several sections. A section start with a name with [] and
finish with a blank line. This file should be a unix file.

On QGIS desktop, when creating a new PostGIS connection, give it a name and use
the service name (``geomapfish`` in our example) in the connection parameters
form.
the service name (``geomapfish`` in our example) in the connection parameters form.

Copy-past this file in the server, change the parameters to fit with the server
settings and add the variable environment setting in the Apache config:
Expand All @@ -93,8 +93,8 @@ settings and add the variable environment setting in the Apache config:

Don't forget to restart Apache.

Docker
******
Run the client with Docker
**************************

Create a tunnel to the database:

Expand Down Expand Up @@ -154,3 +154,29 @@ and he should be configured as follow:
* Authentication type: ``Standard auth``
* WFS support: recommended to be ``[X]``
* Is single tile: recommended to be ``[ ]``

Access Restriction
******************

The access restriction is available only for Docker projects

We provide an Docker image named ``camptocamp/geomapfish-qgisserver`` with tag pattern:
``gmf<Major GeoMapFish version}-qgis${Major QGIS}``.

Configuration for a single project, just provide the OGC server name in the environment variable named:
``GEOMAPFISH_OGCSERVER``.

If you need to provide more than one QGIS projects you should write a config file named, e.g.
``qgisserver/accesscontrol_config.yaml``, with the content:

.. code:: yaml

map_config:
<project file path>:
ogc_server: <OGC server name>

``<project file path>`` should have exactly the same value a the ``MAP`` parameter in the ``Base URL``
vavalue of the OGC sever.

And finally you should provide the ``GEOMAPFISH_ACCESSCONTROL_CONFIG`` to point to config file e.-g.
``/etc/qgisserver/accesscontrol_config.yaml``, and ``QGIS_PROJECT_FILE`` to be empty.
131 changes: 102 additions & 29 deletions docker/qgisserver/geomapfish_plugin/accesscontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@
"""


import c2cwsgiutils.broadcast
import geoalchemy2
import json
import os
from shapely import ops
import sqlalchemy
from sqlalchemy.orm import configure_mappers, scoped_session, sessionmaker
import sys
from threading import Lock
import traceback
import yaml
import zope.event.classhandler
from enum import Enum

from c2cgeoportal_commons.config import config
from enum import Enum
from qgis.core import QgsMessageLog, QgsDataSourceUri, QgsProject
from qgis.server import QgsAccessControlFilter

import c2cwsgiutils.broadcast
from c2cgeoportal_commons.config import config
from shapely import ops
from sqlalchemy.orm import configure_mappers, scoped_session, sessionmaker
from threading import Lock


class GMFException(Exception):
Expand All @@ -41,11 +41,102 @@ class Access(Enum):


class GeoMapFishAccessControl(QgsAccessControlFilter):

def __init__(self, server_iface):
super().__init__(server_iface)

self.server_iface = server_iface

try:
config.init(os.environ.get('GEOMAPFISH_CONFIG', '/etc/qgisserver/geomapfish.yaml'))
self.srid = config.get('srid')

c2cwsgiutils.broadcast.init(None)

configure_mappers()
engine = sqlalchemy.create_engine(config.get('sqlalchemy_slave.url'))
session_factory = sessionmaker()
session_factory.configure(bind=engine)
self.DBSession = scoped_session(session_factory)

if "GEOMAPFISH_OGCSERVER" in os.environ:
self.single = True
self.ogcserver_accesscontrol = OGCServerAccessControl(
server_iface, os.environ['GEOMAPFISH_OGCSERVER']
)

QgsMessageLog.logMessage("Use OGC server named '{}'.".format(
os.environ["GEOMAPFISH_OGCSERVER"]
))
elif "GEOMAPFISH_ACCESSCONTROL_CONFIG" in os.environ:
self.single = False
self.ogcserver_accesscontrols = {}
with open(os.environ["GEOMAPFISH_ACCESSCONTROL_CONFIG"]) as ac_config_file:
ac_config = yaml.load(ac_config_file.read())

for map, map_config in ac_config.get("map_config").items():
map_config["access_control"] = OGCServerAccessControl(
server_iface, map_config["ogc_server"]
)
self.ogcserver_accesscontrols[map] = map_config
QgsMessageLog.logMessage("Use config '{}'.".format(
os.environ["GEOMAPFISH_ACCESSCONTROL_CONFIG"]
))
else:
raise GMFException(
"The environment variable 'GEOMAPFISH_OGCSERVER' or "
"'GEOMAPFISH_ACCESSCONTROL_CONFIG' is not defined."
)

except Exception:
QgsMessageLog.logMessage(''.join(traceback.format_exception(*sys.exc_info())))
raise

server_iface.registerAccessControl(self, int(os.environ.get("GEOMAPFISH_POSITION", 100)))

def get_ogcserver_accesscontrol_config(self):
if self.single:
raise GMFException(
"The method 'get_ogcserver_accesscontrol_config' can't be called on 'single' server"
)

def get_ogcserver_accesscontrol(self):
if self.single:
return self.ogcserver_accesscontrol
else:
parameters = self.serverInterface().requestHandler().parameterMap()
return self.ogcserver_accesscontrols[parameters["MAP"]]["access_control"]

def layerFilterSubsetString(self, layer): # NOQA
""" Return an additional subset string (typically SQL) filter """
return self.get_ogcserver_accesscontrol().layerFilterSubsetString(layer)

def layerFilterExpression(self, layer): # NOQA
""" Return an additional expression filter """
return self.get_ogcserver_accesscontrol().layerFilterExpression(layer)

def layerPermissions(self, layer): # NOQA
""" Return the layer rights """
return self.get_ogcserver_accesscontrol().layerPermissions(layer)

def authorizedLayerAttributes(self, layer, attributes): # NOQA
""" Return the authorised layer attributes """
return self.get_ogcserver_accesscontrol().layauthorizedLayerAttributeserPermissions(layer, attributes)

def allowToEdit(self, layer, feature): # NOQA
""" Are we authorise to modify the following geometry """
return self.get_ogcserver_accesscontrol().allowToEdit(layer, feature)

def cacheKey(self): # NOQA
return self.get_ogcserver_accesscontrol().cacheKey()


class OGCServerAccessControl(QgsAccessControlFilter):
""" Implements GeoMapFish access restriction """

SUBSETSTRING_TYPE = ["GPKG", "PostgreSQL database with PostGIS extension"]

def __init__(self, server_iface):
def __init__(self, server_iface, ogcserver_name):
super().__init__(server_iface)

self.server_iface = server_iface
Expand All @@ -55,16 +146,6 @@ def __init__(self, server_iface):
self.project = None

try:
if "GEOMAPFISH_OGCSERVER" not in os.environ:
raise GMFException("The environment variable 'GEOMAPFISH_OGCSERVER' is not defined.")

QgsMessageLog.logMessage("Use OGC server named '{}'".format(os.environ["GEOMAPFISH_OGCSERVER"]))

config.init(os.environ.get('GEOMAPFISH_CONFIG', '/etc/qgisserver/geomapfish.yaml'))
self.srid = config.get('srid')

c2cwsgiutils.broadcast.init(None)

from c2cgeoportal_commons.models.main import InvalidateCacheEvent

@zope.event.classhandler.handler(InvalidateCacheEvent)
Expand All @@ -75,23 +156,15 @@ def handle(event: InvalidateCacheEvent):
self.layers = None

from c2cgeoportal_commons.models.main import OGCServer
configure_mappers()

engine = sqlalchemy.create_engine(config.get('sqlalchemy_slave.url'))
session_factory = sessionmaker()
session_factory.configure(bind=engine)
self.DBSession = scoped_session(session_factory)

self.ogcserver = self.DBSession.query(OGCServer) \
.filter(OGCServer.name == os.environ["GEOMAPFISH_OGCSERVER"]) \
.filter(OGCServer.name == ogcserver_name) \
.one()

except Exception as e:
except Exception:
QgsMessageLog.logMessage(''.join(traceback.format_exception(*sys.exc_info())))
raise

server_iface.registerAccessControl(self, int(os.environ.get("GEOMAPFISH_POSITION", 100)))

def get_layers(self):
with self.lock:
from c2cgeoportal_commons.models.main import LayerWMS
Expand Down Expand Up @@ -266,7 +339,7 @@ def layerFilterExpression(self, layer): # NOQA
)
QgsMessageLog.logMessage("layerFilterExpression filter: {}".format(result))
return result
except Exception as e:
except Exception:
QgsMessageLog.logMessage(''.join(traceback.format_exception(*sys.exc_info())))
raise

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,12 @@ vars:
docker_services:
qgisserver:
environment:
# Single QGIS project files
QGIS_PROJECT_FILE: /etc/qgisserver/project.qgz
GEOMAPFISH_OGCSERVER: <OGC server name>

# Multiple QGIS project files
# QGIS_PROJECT_FILE:
# GEOMAPFISH_ACCESSCONTROL_CONFIG: /etc/qgisserver/accesscontrol_config.yaml
# Checker configuration
checker:
fulltextsearch:
Expand Down