Skip to content

Commit f4af689

Browse files
committed
Add duo web support
1 parent 53c5283 commit f4af689

File tree

13 files changed

+730
-4
lines changed

13 files changed

+730
-4
lines changed

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ ENV CONFIG_VARS sqlalchemy.url sqlalchemy.pool_recycle sqlalchemy.pool_size sqla
1010
checker check_collector default_max_age package srid \
1111
reset_password fulltextsearch global_headers headers authorized_referers hooks stats db_chooser \
1212
dbsessions urllogin host_forward_host smtp c2c.base_path welcome_email \
13-
lingua_extractor interfaces_config interfaces devserver_url api authentication intranet metrics
13+
lingua_extractor interfaces_config interfaces devserver_url api authentication intranet metrics \
14+
duo_web
1415

1516
COPY . /tmp/config/
1617

docker-compose.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ services:
7171
extends:
7272
file: docker-compose-lib.yaml
7373
service: geoportal
74-
image: camptocamp/geomapfish-geoportal:2.5
7574
volumes_from:
7675
- config:ro
7776
environment:

geoportal/.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/geomapfish_geoportal/static-ngeo/js/apps/duo/Duo-Web-v2.js

geoportal/.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
extends:
22
- openlayers
33
globals:
4+
'Duo': false
45
'geomapfish': false
56
env:
67
jquery: true

geoportal/geomapfish_geoportal/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import distutils.core
44
from pyramid.config import Configurator
55
from c2cgeoportal_geoportal import locale_negotiator, add_interface, INTERFACE_TYPE_NGEO
6-
from c2cgeoportal_geoportal.lib.authentication import create_authentication
76
from geomapfish_geoportal.resources import Root
87

8+
from geomapfish_geoportal.duoweb import create_authentication
99

1010
def main(global_config, **settings):
1111
"""
@@ -24,6 +24,8 @@ def main(global_config, **settings):
2424
config.include('c2cgeoportal_geoportal')
2525
distutils.core._setup_stop_after = None
2626

27+
config.include('geomapfish_geoportal.duoweb')
28+
2729
config.add_translation_dirs('geomapfish_geoportal:locale/')
2830

2931
# Scan view decorator for adding routes
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# FIXME: is_password_changed
2+
# FIXME: consecutive_failed
3+
# FIXME: update_last_login
4+
5+
import logging
6+
from json import loads
7+
8+
from pyramid.view import view_config
9+
from pyramid.authentication import AuthTktAuthenticationPolicy
10+
from pyramid.security import remember
11+
from pyramid.httpexceptions import HTTPBadRequest, HTTPFound, HTTPUnauthorized
12+
13+
from c2cgeoportal_geoportal.resources import defaultgroupsfinder
14+
15+
from duo_web import sign_request, verify_response
16+
17+
18+
LOG = logging.getLogger(__name__)
19+
logging.basicConfig(level=10)
20+
21+
def includeme(config):
22+
config.add_route('login', '/login')
23+
config.add_view(login, route_name='login')
24+
config.add_route('duoweb_post_action', '/duoweb/post_action')
25+
config.add_view(duoweb_post_action, route_name='duoweb_post_action')
26+
27+
28+
def create_authentication(settings):
29+
timeout = settings.get("authtkt_timeout")
30+
timeout = None if timeout is None or timeout.lower() == "none" else int(timeout)
31+
reissue_time = settings.get("authtkt_reissue_time")
32+
reissue_time = None if reissue_time is None or reissue_time.lower() == "none" else int(reissue_time)
33+
max_age = settings.get("authtkt_max_age")
34+
max_age = None if max_age is None or max_age.lower() == "none" else int(max_age)
35+
http_only = settings.get("authtkt_http_only", "True")
36+
http_only = http_only.lower() in ("true", "yes", "1")
37+
secure = settings.get("authtkt_secure", "True")
38+
secure = secure.lower() in ("true", "yes", "1")
39+
samesite = settings.get("authtkt_samesite", "Lax")
40+
secret = settings.get("authtkt_secret")
41+
return DuoWebAuthenticationPolicy(
42+
secret,
43+
callback=defaultgroupsfinder,
44+
cookie_name=settings["authtkt_cookie_name"],
45+
samesite=None if samesite == "" else samesite,
46+
timeout=timeout,
47+
max_age=timeout,
48+
reissue_time=reissue_time,
49+
hashalg="sha512",
50+
http_only=http_only,
51+
secure=secure,
52+
)
53+
54+
class DuoWebAuthenticationPolicy(AuthTktAuthenticationPolicy):
55+
def authenticated_userid(self, request):
56+
# FIXME: necessary ?
57+
userid = self.unauthenticated_userid(request)
58+
LOG.info('authenticated_userid: %s' % userid)
59+
if userid is not None:
60+
return userid
61+
# else:
62+
# # back from DuoWeb, validate response
63+
# sig_response = request.params.get('sig_response')
64+
# if sig_response is not None:
65+
# return verify_response(ikey, skey, akey, sig_response)
66+
67+
# FIXME: 'duoweb_login' instead of 'login' ?
68+
@view_config(route_name='login', renderer='json')
69+
def login(request):
70+
login = request.params.get("login")
71+
password = request.params.get("password")
72+
if login is None or password is None:
73+
raise HTTPBadRequest()
74+
username = request.registry.validate_user(request, login, password)
75+
if username is None:
76+
raise HTTPUnauthorized()
77+
78+
config = request.registry.settings.get('duo_web')
79+
return {
80+
'sig_request': sign_request(**config, username=username),
81+
# 'c2cjufr': sign_request(ikey, skey, akey, 'c2cjufr')
82+
}
83+
84+
85+
@view_config(route_name='duoweb_post_action', renderer='json')
86+
def duoweb_post_action(request):
87+
body = loads(request.body, encoding=request.charset)
88+
sig_response = body.get('sig_response')
89+
config = request.registry.settings.get('duo_web')
90+
authenticated_username = verify_response(**config, sig_response=sig_response)
91+
if authenticated_username is not None:
92+
headers = remember(request, authenticated_username)
93+
return HTTPFound(request.route_url('loginuser'), headers=headers)
94+
else:
95+
raise HTTPUnauthorized()

geoportal/geomapfish_geoportal/static-ngeo/js/apps/Controllerdesktop.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import geomapfishBase from '../geomapfishmodule.js';
3737
import EPSG2056 from '@geoblocks/proj/src/EPSG_2056.js';
3838
import EPSG21781 from '@geoblocks/proj/src/EPSG_21781.js';
3939

40+
import {initialize} from './duo/index.js';
41+
4042
if (!window.requestAnimationFrame) {
4143
alert('Your browser is not supported, please update it or use another one. You will be redirected.\n\n'
4244
+ 'Votre navigateur n\'est pas supporté, veuillez le mettre à jour ou en utiliser un autre. '
@@ -125,6 +127,11 @@ class Controller extends AbstractDesktopController {
125127
gettextCatalog.getString('Add a sub theme');
126128
gettextCatalog.getString('Add a layer');
127129
}
130+
131+
handleDuoWebLogin(resp) {
132+
// geoportal login was successful
133+
initialize(resp.data.sig_request);
134+
}
128135
}
129136

130137
/**

geoportal/geomapfish_geoportal/static-ngeo/js/apps/desktop.html.ejs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!DOCTYPE html>
2-
<html lang="{{mainCtrl.lang}}" ng-controller="DesktopController as mainCtrl" ng-strict-di>
2+
<html lang="{{mainCtrl.lang}}" ng-controller="DesktopController as mainCtrl">
33
<head>
44
<title ng-bind-template="{{'Desktop Application'|translate}}">GeoMapFish</title>
55
<meta charset="utf-8">
@@ -119,8 +119,10 @@
119119
<a class="btn close" ng-click="mainCtrl.loginActive = false">&times;</a>
120120
</div>
121121
<gmf-authentication
122+
gmf-authentication-on-successful-login="mainCtrl.handleDuoWebLogin"
122123
gmf-authentication-info-message="mainCtrl.loginInfoMessage"
123124
></gmf-authentication>
125+
<iframe id="duo_iframe"></iframe>
124126
<div ng-if="mainCtrl.postLoading">
125127
<i class="fa custom-spinner-connect fa-spin">
126128
<%=require('gmf/icons/spinner.svg?viewbox&height=1em')%>

0 commit comments

Comments
 (0)