Skip to content

Commit

Permalink
Add duo web support
Browse files Browse the repository at this point in the history
  • Loading branch information
fredj committed Mar 20, 2020
1 parent 7f014c4 commit 4ceabf2
Show file tree
Hide file tree
Showing 13 changed files with 713 additions and 4 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ ENV CONFIG_VARS sqlalchemy.url sqlalchemy.pool_recycle sqlalchemy.pool_size sqla
checker check_collector default_max_age package srid \
reset_password fulltextsearch global_headers headers authorized_referers hooks stats db_chooser \
dbsessions urllogin host_forward_host smtp c2c.base_path welcome_email \
lingua_extractor interfaces_config interfaces devserver_url api authentication intranet metrics
lingua_extractor interfaces_config interfaces devserver_url api authentication intranet metrics \
duo_web

COPY . /tmp/config/

Expand Down
1 change: 0 additions & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ services:
extends:
file: docker-compose-lib.yaml
service: geoportal
image: camptocamp/geomapfish-geoportal:2.5
volumes_from:
- config:ro
environment:
Expand Down
1 change: 1 addition & 0 deletions geoportal/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/geomapfish_geoportal/static-ngeo/js/apps/duo/Duo-Web-v2.js
1 change: 1 addition & 0 deletions geoportal/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
extends:
- openlayers
globals:
'Duo': false
'geomapfish': false
env:
jquery: true
Expand Down
4 changes: 3 additions & 1 deletion geoportal/geomapfish_geoportal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import distutils.core
from pyramid.config import Configurator
from c2cgeoportal_geoportal import locale_negotiator, add_interface, INTERFACE_TYPE_NGEO
from c2cgeoportal_geoportal.lib.authentication import create_authentication
from geomapfish_geoportal.resources import Root

from geomapfish_geoportal.duoweb import create_authentication

def main(global_config, **settings):
"""
Expand All @@ -24,6 +24,8 @@ def main(global_config, **settings):
config.include('c2cgeoportal_geoportal')
distutils.core._setup_stop_after = None

config.include('geomapfish_geoportal.duoweb')

config.add_translation_dirs('geomapfish_geoportal:locale/')

# Scan view decorator for adding routes
Expand Down
93 changes: 93 additions & 0 deletions geoportal/geomapfish_geoportal/duoweb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# FIXME: is_password_changed
# FIXME: consecutive_failed
# FIXME: update_last_login

import logging

from pyramid.view import view_config
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.security import remember
from pyramid.httpexceptions import HTTPBadRequest, HTTPFound, HTTPUnauthorized

from c2cgeoportal_geoportal.resources import defaultgroupsfinder

from duo_web import sign_request, verify_response


LOG = logging.getLogger(__name__)
logging.basicConfig(level=10)

def includeme(config):
config.add_route('login', '/login')
config.add_view(login, route_name='login')
config.add_route('duoweb_post_action', '/duoweb/post_action')
config.add_view(duoweb_post_action, route_name='duoweb_post_action')


def create_authentication(settings):
timeout = settings.get("authtkt_timeout")
timeout = None if timeout is None or timeout.lower() == "none" else int(timeout)
reissue_time = settings.get("authtkt_reissue_time")
reissue_time = None if reissue_time is None or reissue_time.lower() == "none" else int(reissue_time)
max_age = settings.get("authtkt_max_age")
max_age = None if max_age is None or max_age.lower() == "none" else int(max_age)
http_only = settings.get("authtkt_http_only", "True")
http_only = http_only.lower() in ("true", "yes", "1")
secure = settings.get("authtkt_secure", "True")
secure = secure.lower() in ("true", "yes", "1")
samesite = settings.get("authtkt_samesite", "Lax")
secret = settings.get("authtkt_secret")
return DuoWebAuthenticationPolicy(
secret,
callback=defaultgroupsfinder,
cookie_name=settings["authtkt_cookie_name"],
samesite=None if samesite == "" else samesite,
timeout=timeout,
max_age=timeout,
reissue_time=reissue_time,
hashalg="sha512",
http_only=http_only,
secure=secure,
)

class DuoWebAuthenticationPolicy(AuthTktAuthenticationPolicy):
def authenticated_userid(self, request):
# FIXME: necessary ?
userid = self.unauthenticated_userid(request)
LOG.info('authenticated_userid: %s' % userid)
if userid is not None:
return userid
# else:
# # back from DuoWeb, validate response
# sig_response = request.params.get('sig_response')
# if sig_response is not None:
# return verify_response(ikey, skey, akey, sig_response)

# FIXME: 'duoweb_login' instead of 'login' ?
@view_config(route_name='login', renderer='json')
def login(request):
login = request.params.get("login")
password = request.params.get("password")
if login is None or password is None:
raise HTTPBadRequest()
username = request.registry.validate_user(request, login, password)
if username is None:
raise HTTPUnauthorized()

config = request.registry.settings.get('duo_web')
return {
'sig_request': sign_request(**config, username=username),
# 'c2cjufr': sign_request(ikey, skey, akey, 'c2cjufr')
}


@view_config(route_name='duoweb_post_action', renderer='json')
def duoweb_post_action(request):
sig_response = request.params.get('sig_response')
config = request.registry.settings.get('duo_web')
authenticated_username = verify_response(**config, sig_response=sig_response)
if authenticated_username is not None:
headers = remember(request, authenticated_username)
return HTTPFound(request.route_url('loginuser'), headers=headers)
else:
raise HTTPUnauthorized()
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import geomapfishBase from '../geomapfishmodule.js';
import EPSG2056 from '@geoblocks/proj/src/EPSG_2056.js';
import EPSG21781 from '@geoblocks/proj/src/EPSG_21781.js';

import {initialize} from './duo/index.js';

if (!window.requestAnimationFrame) {
alert('Your browser is not supported, please update it or use another one. You will be redirected.\n\n'
+ 'Votre navigateur n\'est pas supporté, veuillez le mettre à jour ou en utiliser un autre. '
Expand Down Expand Up @@ -124,6 +126,15 @@ class Controller extends AbstractDesktopController {
gettextCatalog.getString('Add a theme');
gettextCatalog.getString('Add a sub theme');
gettextCatalog.getString('Add a layer');

const gmfAuthentication = $injector.get('gmfAuthenticationService');
console.log(gmfAuthentication);
}

handleLogin(resp) {
// geoportal login was successful

initialize(resp.data.sig_request);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{mainCtrl.lang}}" ng-controller="DesktopController as mainCtrl" ng-strict-di>
<html lang="{{mainCtrl.lang}}" ng-controller="DesktopController as mainCtrl">
<head>
<title ng-bind-template="{{'Desktop Application'|translate}}">GeoMapFish</title>
<meta charset="utf-8">
Expand Down Expand Up @@ -119,8 +119,10 @@
<a class="btn close" ng-click="mainCtrl.loginActive = false">&times;</a>
</div>
<gmf-authentication
gmf-authentication-handle-login="mainCtrl.handleLogin"
gmf-authentication-info-message="mainCtrl.loginInfoMessage"
></gmf-authentication>
<iframe id="duo_iframe"></iframe>
<div ng-if="mainCtrl.postLoading">
<i class="fa custom-spinner-connect fa-spin">
<%=require('gmf/icons/spinner.svg?viewbox&height=1em')%>
Expand Down
Loading

0 comments on commit 4ceabf2

Please sign in to comment.