Skip to content
Snippets Groups Projects
Commit ff1904b3 authored by Sébastien DA ROCHA's avatar Sébastien DA ROCHA :bicyclist: Committed by m431m
Browse files

Feature/tokens

parent 0d8dd867
No related branches found
No related tags found
No related merge requests found
Showing
with 453 additions and 1 deletion
...@@ -113,3 +113,13 @@ dmypy.json ...@@ -113,3 +113,13 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/
Pipfile*
django_config/
manage.py
# vim
*swp
# reports for SonarQube
xunit-reports/
coverage-reports/
image:
name: python:3.5
variables:
SONAR_TOKEN: "5b1ac2b019175e23aeb4b5618e50893d0db78f1f"
SONAR_PROJECTKEY: "ideo-bfc/idgo_ows_accounts_manager"
SONAR_HOST_URL: "https://sonarqube.neogeo.fr"
GIT_DEPTH: 0
stages:
- test1
- test2
test_unit:
stage: test1
before_script:
- pip install -r requirements.txt
- pip install 'django<2.0.0'
- pip install pytest pytest-django pytest-cov
script:
- pytest --junitxml=xunit-reports/xunit-result-pytest.xml --cov-report xml:coverage-reports/coverage-pytest.xml --cov idgo_ows_account_manager
artifacts:
paths:
- coverage-reports/
- xunit-reports/
expire_in: 1 week
sonarqube-check:
image:
name: sonarsource/sonar-scanner-cli:latest
entrypoint: [""]
stage: test2
script:
- sonar-scanner -Dsonar.qualitygate.wait=true -Dsonar.projectKey=$CI_PROJECT_NAME -Dsonar.projectName=$CI_PROJECT_NAME -Dsonar.projectVersion=$CI_COMMIT_BRANCH
allow_failure: true
dependencies:
- test_unit
only:
- develop
- master
# CHANGELOG
Tous les changements notables apportés à ce projet seront documentés dans ce dossier.
---
## TABLE DES MATIÈRES
- [Reste à réaliser](#Reste-à-réaliser)
- [Versions](#Versions)
- [[0.1.0] - Version initiale](#[0.1.0]---Version-initiale)
---
## Reste à réaliser
## Versions
### [0.1.0] - Version initiale
* Ajout script OGC compatible tokens
* Ajout d'une option de configuration dans le profile utilisateur pour créer un token personnalisé.
# IDGO WMS Token # IDGO WMS Token
Gestion des token pour l'accès à Mapserver en utilisant le protocole WMS Gestion des token pour l'accès à Mapserver en utilisant le protocole WMS
\ No newline at end of file
---
## TABLE DES MATIÈRES
- [TABLE DES MATIÈRES](#TABLE-DES-MATIÈRES)
- [Installation](#Installation)
- [Configuration](#Configuration)
- [Utilisation](#Utilisation)
- [Démarrage des dockers](#Démarrage-des-dockers)
- [Arrêt des dockers](#Arrêt-des-dockers)
- [Versions](#Versions)
- [Auteurs](#Auteurs)
---
## Installation
C'est un plugin pour idgo, donc installer idgo avant !
```
pip install git+https://@gitlab.neogeo.fr/ideo-bfc/idgo_ows_accounts_manager.git@master#egg=idgo_ows_accounts_manager
```
## Configuration
Pour activer l'application django, et activer le `context_processor` chargeant tous les templates de modification de profil utilisateur :
Dans le settings.py:
```
ACCOUNT_MANAGER_APPS = [
'idgo_ows_account_manager',
]
INSTALLED_APPS = [
[...]
] + RESOURCE_APPS + ACCOUNT_MANAGER_APPS
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
[...]
'idgo_ows_account_manager.context_processors.list_account_manager_extensions',
],
},
},
]
DISABLE_ACCOUNT_PASSWORD = True
```
`DISABLE_ACCOUNT_PASSWORD` permet de ne pas afficher le mot de passe utilisateur
Pour charger les 3 vues (pour creer, supprimer, changer le mot de passe) dans urls.py:
```
for app in settings.RESOURCE_APPS + settings.ACCOUNT_MANAGER_APPS:
# on insert apres idgo_admin
urlpatterns.insert(2, url(r'^', include('{}.urls'.format(app), namespace=app)),)
```
Pour que le script OGC utilise en basic auth le username en nom d'utilisateur et le token en mot de passe.
## Utilisation
### Configurer son token
Dans la nouvelle section "Compte WMS" du profil utilisateur, choisir "Créer un compte WMS", le login / mot de passe apparaitra, ainsi que les boutons pour supprimer le compte et regéner le mot de passe.
### Utiliser son token
Dans QGis, rensignier le mot de passe et le mot de passe dans la connexion WMS/WMTS/WFS, l'URL à utiliser est https://[qualif-]/[preprod-]/[]ogc.ternum-bfc.fr/
## Versions
Voir le fichier [CHANGELOG](CHANGELOG.md)
## Auteurs
* Néogéo Technologies
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class IdgoAdminMockConfig(AppConfig):
name = 'idgo_admin_mock'
from django.db import models
# Create your models here.
from django.test import TestCase
# Create your tests here.
from django.conf.urls import url
from idgo_admin_mock import views
urlpatterns = [
url(r'^fake/', views.mock_view, name="update_account"),
]
from django.http import HttpResponse
def mock_view(request):
return HttpResponse("OK")
from django.contrib import admin
from idgo_ows_account_manager import models
# Register your models here.
admin.site.register(models.WMSToken)
from django.apps import AppConfig
class IdgoOwsAccountManagerConfig(AppConfig):
name = 'idgo_ows_account_manager'
#!/WEBS/ternum/idgo.ternum.fr/docs/neogeo-idgo/bin/python3
# Copyright (c) 2017-2020 Neogeo-Technologies.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import os
import sys
from urllib.parse import parse_qs
from urllib.parse import urlparse
python_home = "/WEBS/ternum/idgo.ternum.fr/docs/neogeo-idgo"
sys.path.append(python_home)
# Obliqé pour WSGIAuthUserScript
activate_this = python_home + '/bin/activate_this.py'
with open(activate_this) as venv:
exec(venv.read(), {'__file__': activate_this})
from wl_proxy import load_proxy_environment
load_proxy_environment()
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_config.settings')
import django # noqa: E402
django.setup()
from django.contrib.auth.models import User # noqa: E402
from django.db.models import Q # noqa: E402
from functools import reduce # noqa: E402
from idgo_admin.models import Dataset # noqa: E402
from idgo_admin.models import Organisation # noqa: E402
from idgo_admin.models import Resource # noqa: E402
from operator import ior # noqa: E402
from django.conf import settings
if settings.DEBUG:
AUTH_SCRIPT_LOGFILE = '/FILER/idgo.ternum.fr/media/logos/auth-ogc.ternum.fr_error.log'
OTHER_LOGFILE = '/FILER/idgo.ternum.fr/media/logos/default.ternum.fr_error.log'
logger = logging.getLogger('')
stream_handler = logging.FileHandler(OTHER_LOGFILE)
logger.addHandler(stream_handler)
logger.setLevel(logging.DEBUG)
logger = logging.getLogger('auth_ogc')
stream_handler = logging.FileHandler(AUTH_SCRIPT_LOGFILE)
logger.addHandler(stream_handler)
logger.setLevel(logging.DEBUG)
try:
if os.path.exists(OTHER_LOGFILE):
os.chmod(OTHER_LOGFILE, 0o666)
if os.path.exists(AUTH_SCRIPT_LOGFILE):
os.chmod(AUTH_SCRIPT_LOGFILE, 0o666)
except:
pass
else:
logger = logging.getLogger('auth_ogc')
logger.setLevel(logging.WARN)
AUTHORIZED_PREFIX = ['/maps/', '/wfs/', '/wms/', '/wxs/']
# used for parsing address when basic auth is provided
PRIVATE_AUTHORIZED_PREFIX = ["/private{prefix}".format(prefix=p)
for p in AUTHORIZED_PREFIX]
def retrieve_resources_through_ows_url(url):
parsed_url = urlparse(url.lower())
qs = parse_qs(parsed_url.query)
if 'layers' in qs:
layers = qs.get('layers')[-1]
elif 'typename' in qs:
layers = qs.get('typename')[-1]
elif 'typenames' in qs:
layers = qs.get('typenames')[-1]
else:
layers = None
if not layers:
return None
layers = set(layers.replace(' ', '').split(','))
layers = [layer.split(':')[-1] for layer in layers]
datasets_filters = [
Q(slug__in=layers),
Q(organisation__in=Organisation.objects.filter(slug__in=layers).distinct()),
]
datasets = Dataset.objects.filter(reduce(ior, datasets_filters)).distinct()
resources_filters = [
Q(dataset__in=datasets),
Q(layer__name__in=layers),
]
resources = Resource.objects.filter(reduce(ior, resources_filters)).distinct()
return resources
def check_password(environ, user, password):
url = environ['REQUEST_URI']
logger.debug('Checking user %s rights to url %s', user, url)
# check path is authorized
is_path_authorized = False
for prefix in AUTHORIZED_PREFIX + PRIVATE_AUTHORIZED_PREFIX:
if url.startswith(prefix):
is_path_authorized = True
if not is_path_authorized:
logger.error("path '%s' is unauthorized", url)
return False
# Get Capabilities and metadata are always athorized
qs = parse_qs(urlparse(url.lower()).query)
request = qs.get('request')
logger.debug(qs)
public_requests = [
"getcapabilities",
"getmetadata",
"getlegendgraphic",
"describefeaturetype",
"describelayer",
"getstyles",
]
if request[-1] in public_requests:
logger.debug("URL request is public")
return True
try:
user = User.objects.get(username=user, is_active=True)
except User.DoesNotExist:
logger.debug("User %s does not exist (or is not active)" % user)
else:
if str(user.wmstoken.token) != password:
logger.error("User %s provided bad password", user)
#logger.debug(" '%s' instead of '%s'", password, user.wmstoken.token)
return False
resources = retrieve_resources_through_ows_url(url)
if not resources:
logger.error("Unable to get resources")
return False
# Refuse query if one of the resources is not available/authorized
for resource in resources:
if resource.anonymous_access:
continue
if not resource.is_profile_authorized(user):
logger.error(
"Resource '{resource}' is not authorized to user '{user}'.".format(
resource=resource.pk, user=user.username))
return False
return True
if __name__ == '__main__':
while True:
try:
line = sys.stdin.readline().strip()
logger.debug("REMAP ogc auth: %s" % line)
headers = {"REQUEST_URI": line}
# Remove querystring (handled by apache)
path = line.split("?")[0]
# if ressource is accessible by anonymous => public,
# otherwise check password (=> private)
if check_password(headers, "", ""):
response = "http://localhost:8001/public{uri}".format(uri=path)
else:
response = "http://localhost:8001/private{uri}".format(uri=path)
logger.debug("response : %s" % response)
sys.stdout.write(response + '\n')
sys.stdout.flush()
except Exception as e:
logger.error(e)
sys.stdout.write('NULL\n')
sys.stdout.flush()
from django.conf import settings
def list_account_manager_extensions(request):
"""
En considerant une liste regroupant les extensions de compte utilisateur:
ACCOUNT_MANAGER_APPS = ['idgo_ows_account_manager', ...]
INSTALLED_APPS = CORE_APPS + ... + ACCOUNT_MANAGER_APPS
A charger dans les settings:
TEMPLATES = [
{
# ...
'OPTIONS': {
'context_processors': [
# ...
'idgo_ows_account_manager.context_processors.list_account_manager_extensions'
],
},
},
]
Permet d'avoir la variable 'ACCOUNT_MANAGER_APPS' accessible depuis tous les
templates.
"""
return {
'ACCOUNT_MANAGER_APPS': [
{
'name': app,
'template': '{}/account_manager_extent.html'.format(app)
} for app in getattr(settings, 'ACCOUNT_MANAGER_APPS', [])
]
}
# Generated by Django 3.0 on 2020-03-12 15:45
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='WMSToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.UUIDField(default=uuid.uuid4)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
import uuid
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class WMSToken(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
token = models.UUIDField(default=uuid.uuid4)
def __str__(self):
return self.user.username
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment