Security

Authentication

Supported standards

  • OpenID Connect: as client, to be able to connect to an external OpenID Connect (OIDC) server.

  • TOTP: for two-factor authentication (2FA), this can be used for example with Google Authenticator.

  • OAuth2 as server: An external application can use GeoMapFish as a single sign-on (SSO) for the authentication, even if it was initially implemented to be able to connect from QGIS desktop on an application that requires two factor authentication.

The default policy

By default, c2cgeoportal applications use an auth ticket authentication policy (AuthTktAuthenticationPolicy). With this policy, the user name is obtained from the “auth ticket” cookie set in the request. The policy is created, and added to the application’s configuration, in the application’s main __init__.py file.

In the file env.project, you can configure the policy with the following variables:

AUTHTKT_TIMEOUT: Default to one day. AUTHTKT_REISSUE_TIME: Default to 2h30, recommended to be 10 times smaller than AUTHTKT_TIMEOUT. AUTHTKT_MAXAGE: Default to one day, good to have the same value as AUTHTKT_TIMEOUT. AUTHTKT_SECRET: Should be defined AUTHTKT_COOKIENAME: Should be defined AUTHTKT_HTTP_ONLY: Default to true. AUTHTKT_SECURE: Default to true. AUTHTKT_SAMESITE: Default to Lax.

Note

With the default configuration, for security reasons, the authentication will only work if the project is served on https.

See also the official documentation.

Using another policy

When using AuthTktAuthenticationPolicy, an “auth ticket” cookie should be set in the request for the user to be identified. In some applications, using a custom identification mechanism may be needed instead, for instance to use SSO. Our knowledge base has an example of how this can be achieved.

User validation

For logging in, c2cgeoportal validates the user credentials (username/password) by reading the user information from the user database table. If a c2cgeoportal application should work with another user information source, like LDAP, a custom client validation mechanism can be set up. Our knowledge base has an example of how this can be achieved.

Basic auth

To be able to access the OGC services from your desktop GIS, you should enable the basic authentication by setting BASICAUTH to True in the env.project file.

To force the application to ask for a password, you should have the attribute authentication_required in your query string.

Note

For security reasons, basic authentication and two factor authentication should not be enabled together.

Two factors authentication

GeoMapFish support TOTP (Time-Based One-Time Password Algorithm) two factors authentication (RFC 6238). To enable the two factors authentication you should set the following settings:

vars:
  authentication:
    two_factor: true
    two_factor_issuer_name: <used_issuer_name>

If a user lost his second authentication factor he can’t ask for a new one, to reset it the administrator should uncheck the ‘The user changed his password’ field on the user in the admin interface.

Note

For security reasons, basic authentication and two factor authentication should not be enabled together, you should use OAuth2 for that.

Account lockout

To lock an account after a certain number of authentication failures, set the following settings:

vars:
  authentication:
    max_consecutive_failures: 10

To unlock a user, the administrator should uncheck the ‘Deactivated’ field on the user in the admin interface.

Intranet

To configure the intranet networks fill in the configuration like:

vars:
  intranet:
    networks:
      - 192.168.1.0/24
      - 192.168.1.0/255.255.255.0
      - 192.168.1.0/0.0.0.255
      - 2001:db00::0/24
      - 2001:db00::0/ffff:ff00::

See Python documentation.

Note

Intranet detection is provided to improve usability for web site usage within the Intranet; however, please be aware that Intranet detection is not a secure mechanism. To secure access to sensitive data, do not rely on Intranet detection; for that, you must use user authentication.

A user can easily manually set the Forwarded or X-Forwarded-For header to spoof his IP.

Lost admin password

You can generate a new admin password the following command:

Reset a user password. The username is used as password if the password is not provided with the corresponding option. User can be created if it does not exist yet.

usage: docker compose exec geoportal manage-users [-h] [--password PASSWORD]
                                                  [--create]
                                                  [--rolename ROLENAME]
                                                  [--email EMAIL]
                                                  [config_uri]
                                                  [config_vars ...] user

Positional Arguments

config_uri

The URI to the configuration file.

Default: 'c2c://geoportal/development.ini'

config_vars

Variables required by the config file. For example, http_port=%(http_port)s would expect http_port=8080 to be passed here.

Default: ()

user

The user

Named Arguments

--password, -p

Set password (if not set, username is used as password

--create, -c

Create user if it does not already exist

Default: False

--rolename, -r

The role name which must exist in the database

Default: 'role_admin'

--email, -e

The user email

External application

Some service of GeoMapFish has some host restriction if you mix the domain.

Application authentication

To be considered as authenticated we should have the correct Cookie header, we also check the Referer header to be sure that the user is coming from the same domain. If he is equals to the Host header, we consider that the user is coming from the same domain. If your server and client application are not on the same domain, to make the login working, you should add the client application domain name (with port) in the vars in vars/authorized_referers.

This check is also done on the came_from parameter during the login process.

Shortener

If you use the shortener service to create link on application on another domain name, you should add this domain name in the vars in vars/shortener/allowed_hosts.

Admin

We provide a view for the admin interface, to be able to clear the cache per OGC server. If for an unknown reason you have not the same host in the Host header and came_from parameter, you should add the domain of the came_from parameter in the vars in vars/admin_interface/allowed_hosts.

OpenID Connect

We can configure an OpenID connect service as an SSO (Single Sign-On) provider for our application. This allows users to log in to our application using their OpenID Connect credentials.

We use OpenID Connect Discovery 1.0 with an Authorization Code Flow from OpenID Connect Core 1.0, with PKCE (Proof Key for Code Exchange, RFC 7636).

        sequenceDiagram
    actor User
    participant Browser
    participant Geoportal
    participant IAM
    Geoportal->>IAM: discovery endpoint

    User->>+Browser: Login
    Browser->>+Geoportal: Login
    Geoportal->>-Browser: redirect
    Browser->>+IAM: authorization endpoint
    IAM->>-Browser: redirect
    Browser->>+Geoportal: callback endpoint
    Geoportal->>IAM: token endpoint
    opt on using user info instead of jwt token
    Geoportal->>IAM: userinfo endpoint
    end
    Geoportal->>-Browser: authentication data in cookie
    Browser->>-User: Reload

    Browser->>+Geoportal: any auth endpoint
    opt on token expiry
    Geoportal->>IAM: refresh token endpoint
    end
    Geoportal->>-Browser: response
    

Authentication provider

If we want to use OpenID Connect as an authentication provider, we need to set the environment variable OPENID_CONNECT_ENABLED to true, then we need to set the following configuration in our vars.yaml file:

vars:
  authentication:
    openid_connect:
      url: <the service URL>
      client_id: <the client application ID>

With that the user will be create in the database at the first login, and the access right will be set in the GeoMapFish database. The user correspondence will be done on the email field.

Authorization provider

If we want to use OpenID Connect as an authorization provider, we need to set the environment variable OPENID_CONNECT_ENABLED to true, then we need to set the following configuration in our vars.yaml file:

vars:
  authentication:
    openid_connect:
      url: <the service URL>
      client_id: <the client application ID>
      provide_roles: true
      user_info_fields:
        settings_role: settings_role
        roles: roles

With that the user will not be in the database only the roles will be set in the GeoMapFish database.

Other options

client_secret: The secret of the client.

trusted_audiences: The list of trusted audiences, if the audience provided by the id-token is not in

this list, the ID token will be rejected.

scopes: The list of scopes to request, default is [openid, profile, email].

query_user_info: If true, the OpenID Connect provider user info endpoint will be requested to

provide the user info instead of using the information provided in the ID token, default is false.

create_user: If true, a user will be create in the geomapfish database if not exists,

default is false.

match_field: The field to use to match the user in the database, can be username (default) or email.

update_fields: The fields to update in the database, default is: [], allowed values are

username, display_name and email.

user_info_fields: The mapping between the user info fields and the user fields in the GeoMapFish database,

the key is the GeoMpaFish user field and the value is the field of the user info provided by the OpenID Connect provider, default is:

username: sub
display_name: name
email: email

Example with Zitadel

vars:
  authentication:
    openid_connect:
      url: https://sso.example.com
      client_id: '***'
      query_user_info: true
      create_user: true

Hooks

If you want to redefine the user creation process, you can use the hooks get_remember_from_user_info and get_user_from_remember.

get_remember_from_user_info: This hook is called during the user is authentication. The argument are the pyramid request, the received user_info, and the remember_object dictionary to be filled and will be stored in the cookie.

get_user_from_remember: This hook is called during the user is certification. The argument are the pyramid request, the received remember_object, and the update_create_user boolean. The return value is the user object User or DynamicUsed. The update_create_user will be True only when we are in the callback endpoint.

Full signatures:

def get_remember_from_user_info(request: Request, user_info: Dict[str, Any], remember_object: OidcRememberObject) -> None:

def get_user_from_remember(request: Request, remember_object: OidcRememberObject, update_create_user: bool) -> Union[User, DynamicUsed]:

Configure the hooks in the project initialization:

def includeme(config):
    config.add_request_method(get_remember_from_user_info, name="get_remember_from_user_info")
    config.add_request_method(get_user_from_remember, name="get_user_from_remember")

QGIS with Zitadel

In Zitadel you should have a PKCS application with the following settings: Redirect URI: http://127.0.0.1:7070/.

On QGIS:

  • Add an Authentication.

  • Set a Name.

  • Set Authentication to OAuth2.

  • Set Grant flow to PKCE authentication code.

  • Set Request URL to <zitadel_base_url>/oauth/v2/authorize.

  • Set Token URL to <zitadel_base_url>/oauth/v2/token.

  • Set Client ID to <client_id>.

  • Set Scope to the openid profile email.

Implementation

When we implement OpenID Connect, we have to possibilities:

  • Implement it in the backend.

  • Implement it in the frontend, and give a token to the backend that allows to be authenticated on an other service.

In c2cgeoportal we have implemented booth method.

The backend implementation is used by ngeo an the admin interface, where se store the user information (including the access and refresh token) in an encrypted JSON as a cookie.

The frontend implementation is used by application like QGIS desktop, on every call we have to call the user info endpoint to get the user information.

OAuth2 with QGIS

In the admin interface create an ‘OAuth2 Client’ with:

On QGIS:

  • Add an Authentication

  • Set a Name

  • Set Authentication to OAuth2

  • Set Grant flow to Authentication code

  • Set Request URL to <geomapfish_base_url>/oauth/login

  • Set Token URL to <geomapfish_base_url>/oauth/token

  • Set Client ID to ‘qgis’

  • Set Client secret to the secret

Note

For security reason a user can only have one active session per client.

If you need to have more than one active session you should provide more than one client.

HTTPS

If your application is accessed in HTTPS, you have to make sure that all URLs generated by the application (CSS and JavaScript files, images, MapServer requests, etc.) use the HTTPS scheme as well. Otherwise the browser will prompt “insecure content” warnings.

There are two ways to manage this:

  • application behind a proxy

  • application and SSL certificate on the same server

Application behind a proxy

If the application is placed behind some proxy that removes the SSL encryption (plain HTTP is used between the proxy and the server), then some specific configuration is required both on the c2cgeoportal application and on the proxy:

  • The proxy should add a specific header to the requests. For example X-Https on (X-Https is the header name, and on is the header value).

In Mako templates, if you need to know what scheme is used, you may test the value of request.scheme. For example:

var WMTS_OPTIONS = {
% if request.scheme == 'https':
    url: 'https://my.wmts.server/'
% else:
    url: 'https://my.wmts.server/'
% endif
/* ... */
};

Application and SSL certificate on the same server

If the SSL certificate and the application are located on the same server, all requests will be redirected to https. So you should change the scheme to https for all url except for some cases that should always use http (typically, all requests to localhost): see url parameter in tilegeneration configuration.

If you apply ssl encryption on your application, you should take care of the tiles url to use https scheme to avoid mixing secure and insecure contents.

Finally, you should redirect all http requests to https. Depending on your environment, you may need to request this via your infrastructure support.

In case you load http external resources into your application, you should use the resourceproxy service as described below.

Loading non https external resources

If you want to load non https external resources in your https application, you should use the resourceproxy service and add the list of hosts you want to access in your project vars.yaml configuration file:

resourceproxy:
    # list of urls from which it is safe to load content
    targets:
      #exampletargetname: https://www.camptocamp.com/?param1=%s&param2=%s
      rfinfo: https://www.rfinfo.vd.ch/rfinfo.php?no_commune=%s&no_immeuble=%s

Then you can access resources by building urls using the following schema: https://<host>/resourceproxy?target=<targetname>&values=(<valueparam1>,...).

For example:

https://geoportail.camptocamp.com/main/resourceproxy?target=rfinfo&values=(175,2633)

Local certificate checks

Certain c2cgeoportal features open a http session to your c2cgeoportal services, for example the checker or the lingva_extractor. If you are running your server in https and wish to disable certificate checks in these connections, you can achieve this by adding the following configuration element to your vars file:

vars:
    http_options:
        verify: False

See other options in parameters of requests.Request

Reset password

When a user has forgotten his/her password, a new one may be sent by email if some additional GeoMapFish configuration is provided.

To ensure such an e-mail can be generated, you should add the following configuration in the vars.yaml file:

# SMTP configuration could be already there if needed by other feature
smtp:
    host: smtp.example.com:465
    ssl: true
    user: <username>
    password: <password>
    starttls: false

reset_password:
    # Used to send a confirmation email
    email_from: info@camptocamp.com
    email_subject: New password generated for GeoMapFish
    email_body: |
        Hello {user},

        You have asked for a new password,
        the newly generated password is: {password}

        Sincerely yours
        The GeoMapFish team

If the SMTP host ends with a colon (:) followed by a number, and there is no port specified, that suffix will be stripped off and the number interpreted as the port number to use.

Replace the smtp.example.com value by a working SMTP server name.

Security update

To be sure that we regularly get the security updates, every night the GeoMapFish Docker images are rebuild. And every time we do a build we pull the new images to use them.

For project on Kubernetes we also deploy fresh built images every day.

This is good for security but with that we can’t guarantee that the result of a new build works exactly as the previous one.

To avoid incidents on production service, Kubernetes will publish a service only if he start correctly. For Project on Docker Compose we have to choose ourself. The best is to build the image only on integration when the result is correct we push it on a repository, and on production we will use the images from the repository. The other solution is to use fixed tag for the base image, this imply that we should do a minor update of the application to get the security fix. To do that you should set in the project.yaml file the template parameter unsafe_long_version to True in the template_vars section.

Access to WMS GetCapability

Set hide_capabilities to true in your vars.yaml to disable the WMS GetCapability when accessing the MapServer proxy (mapserverproxy).

Default: false

Access to the admin interface

To disable the admin interface, set enable_admin_interface to false in your vars.yaml file.

Default: true

Access to services by external servers

By default, only localhost can access c2cgeoportal’s services. To permit access to a specific service by an external server, you must set CORS headers (Access-Control-Allow-Origin) in your vars.yaml file.

Add or modify the structure as follows:

headers:
    <service_name>:
        access_control_allow_origin: ["<domain1>", "<domain2>", ...]
        access_control_max_age: 3600

A "*" can be included in access_control_allow_origin to allow everybody to access, but no credentials will be passed in this case.

Available services are:

Entry:

  • index

  • config

  • api

Services:

  • themes

  • login

  • mapserver

  • print

  • profile

  • raster

  • layers

  • login

  • error

Authorized referrers

To mitigate CSRF attacks, the server validates the referrer against a list of authorized referrers.

By default, only the requests coming from the server are allowed. You can change that list by adding an vars.authorized_referers list in your vars.yaml file.

This solution is not the most secure (some people have browser extensions that reset the referrer), but that is the most consistent approach with regard to the different JS frameworks.

Allowed hosts

For security reason we check the host of the request, to be sure that the request is coming from an authorized domain.

For that we have the following configurations in the vars.yaml file:

  • vars.authentication.allowed_hosts: List of hosts that are allowed to access the oauth2 authentication service (same host allowed).

  • vars.shortener.allowed_hosts: List of hosts that are allowed to be shortened (same host allowed).

  • vars.admin_interface.allowed_hosts: List of host that are allowed to be redirected by the ogc_server_clear_cache (same host allowed).

  • vars.allowed_hosts: List of hosts that are allowed to access the application (theme and dynamic.json).