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

Add URL login auth token capabilities #2812

Merged
merged 6 commits into from
Feb 23, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
/c2cgeoportal/scaffolds/create/+package+/static-ngeo/js/mobile.js_tmpl
/c2cgeoportal/scaffolds/create/+package+/static-ngeo/js/desktop.js_tmpl
/c2cgeoportal/scaffolds/create/+package+/static-ngeo/components/
/c2cgeoportal/scaffolds/update/package.json_tmpl
/c2cgeoportal/scaffolds/update/CONST_create_template/
/c2cgeoportal/version.py
/c2cgeoportal.egg-info
Expand All @@ -31,7 +32,7 @@
/doc/integrator/fulltext_search.rst
/doc/integrator/print.rst
/doc/integrator/upgrade_application.rst
/ngeo/
/dist/
/.coverage
/coverage.xml
/.tx/config
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ COPY . /tmp/
RUN \
cd /tmp && \
pip install . && \
rm -rf /tmp/*
rm --recursive --force /tmp/*

WORKDIR /src

Expand Down
20 changes: 10 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,19 @@ checks: flake8 git-attributes quote

.PHONY: clean
clean:
rm -f $(BUILD_DIR)/venv.timestamp
rm -f c2cgeoportal/version.py
rm -f c2cgeoportal/locale/*.pot
rm -f c2cgeoportal/locale/en/LC_MESSAGES/c2cgeoportal.po
rm -rf c2cgeoportal/static/build
rm -f $(MAKO_FILES:.mako=)
rm -rf ngeo
rm -f $(APPS_FILES)
rm --force $(BUILD_DIR)/venv.timestamp
rm --force c2cgeoportal/version.py
rm --force c2cgeoportal/locale/*.pot
rm --force c2cgeoportal/locale/en/LC_MESSAGES/c2cgeoportal.po
rm --recursive --force c2cgeoportal/static/build
rm --force $(MAKO_FILES:.mako=)
rm --recursive --force ngeo
rm --force $(APPS_FILES)

.PHONY: cleanall
cleanall: clean
rm -f $(PO_FILES)
rm -rf $(BUILD_DIR)
rm --force $(PO_FILES)
rm --recursive --force $(BUILD_DIR)/*

.PHONY: c2c-egg
c2c-egg: $(BUILD_DIR)/requirements.timestamp
Expand Down
2 changes: 1 addition & 1 deletion c2cgeoportal/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def _get_password(self):

def _set_password(self, password):
"""encrypts password on the fly."""
self._password = self.__encrypt_password(password)
self._password = unicode(self.__encrypt_password(password))

def set_temp_password(self, password):
"""encrypts password on the fly."""
Expand Down
34 changes: 30 additions & 4 deletions c2cgeoportal/pyramid_.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of the FreeBSD Project.

import time
import logging
import sqlalchemy
import sqlahelper
Expand All @@ -37,11 +38,13 @@
import simplejson as json
from socket import gethostbyname, gaierror
from ipcalc import IP, Network
from Crypto.Cipher import AES
import importlib

from pyramid_mako import add_mako_renderer
from pyramid.interfaces import IStaticURLInfo
from pyramid.httpexceptions import HTTPException
import pyramid.security

from papyrus.renderers import GeoJSON, XSD

Expand Down Expand Up @@ -268,15 +271,15 @@ def _match_url_start(ref, val):
return ref_parts == val_parts


def _is_valid_referer(referer, settings):
def is_valid_referer(referer, settings):
if referer:
list_ = settings.get("authorized_referers", [])
return any(_match_url_start(x, referer) for x in list_)
else:
return False


def _create_get_user_from_request(settings):
def create_get_user_from_request(settings):
def get_user_from_request(request):
""" Return the User object for the request.

Expand All @@ -287,10 +290,33 @@ def get_user_from_request(request):
"""
from c2cgeoportal.models import DBSession, User

try:
if "auth" in request.params:
auth_enc = request.params.get("auth")

if auth_enc is not None:
urllogin = request.registry.settings.get("urllogin", {})
aeskey = urllogin.get("aes_key")
if aeskey is None: # pragma: nocover
raise Exception("urllogin is not configured")
now = int(time.time())
cipher = AES.new(aeskey)
auth = json.loads(cipher.decrypt(auth_enc.decode("hex")))

if "t" in auth and "u" in auth and "p" in auth:
timestamp = int(auth["t"])
if now < timestamp and request.registry.validate_user(
request, unicode(auth["u"]), auth["p"]
):
headers = pyramid.security.remember(request, auth["u"])
request.response.headerlist.extend(headers)
except Exception as e:
log.error("URL login error: {}".format(e))

# disable the referer check for the admin interface
if not (
request.path_info_peek() == "admin" and request.referer is None or
_is_valid_referer(request.referer, settings)
is_valid_referer(request.referer, settings)
):
if request.referer is not None:
log.warning("Invalid referer for %s: %s", request.path_qs,
Expand Down Expand Up @@ -439,7 +465,7 @@ def includeme(config):

call_hook(settings, "after_settings", settings)

config.add_request_method(_create_get_user_from_request(settings),
config.add_request_method(create_get_user_from_request(settings),
name="user", property=True)

# configure 'locale' dir as the translation dir for c2cgeoportal app
Expand Down
2 changes: 1 addition & 1 deletion c2cgeoportal/scaffolds/update/CONST_Makefile_tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ CONFIG_VARS += instanceid sqlalchemy.url schema parentschema enable_admin_interf
raster shortener hide_capabilities mapserverproxy tinyowsproxy resourceproxy print_url \
tiles_url checker check_collector default_max_age jsbuild package srid \
reset_password fulltextsearch headers authorized_referers hooks stats db_chooser \
ogcproxy_enable dbsessions
ogcproxy_enable dbsessions urllogin
C2C_TEMPLATE_CMD = $(ENVIRONMENT_VARS) $(VENV_BIN)/c2c-template --vars $(VARS_FILE)
MAKE_FILES = $(shell ls -1 *.mk) CONST_Makefile

Expand Down
7 changes: 7 additions & 0 deletions c2cgeoportal/scaffolds/update/CONST_config-schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ mapping:
exportgpxkml: *header
error: *header

urllogin:
type: map
required: True
mapping:
rc4_key:
type: str

cache:
type: map
required: True
Expand Down
2 changes: 2 additions & 0 deletions c2cgeoportal/scaffolds/update/CONST_vars.yaml_tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ vars:
# architecture.
external_themes_url:

urllogin: {}

mapserverproxy:
default_ogc_server: source for image/png

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ PasteDeploy==1.5.2
PasteScript==2.0.2
polib==1.0.8
postmarkup==1.2.2
pycrypto==2.6.1
pyramid==1.8.2
pyramid-chameleon==0.3
pyramid-debugtoolbar==3.0.5
Expand Down
71 changes: 71 additions & 0 deletions c2cgeoportal/scripts/urllogin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-

# Copyright (c) 2017, Camptocamp SA
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:

# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# The views and conclusions contained in the software and documentation are those
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of the FreeBSD Project.

import argparse
import time
import json
import c2c.template
from Crypto.Cipher import AES


def create_token(aeskey, user, password, valid):
auth = {
"u": user,
"p": password,
"t": int(time.time()) + valid * 3600 * 24,
}

if aeskey is None:
print("urllogin is not configured")
exit(1)
cipher = AES.new(aeskey)
data = json.dumps(auth)
mod_len = len(data) % 16
if mod_len != 0:
data += "".join([" " for i in range(16 - mod_len)])
return cipher.encrypt(data).encode("hex")


def main():
parser = argparse.ArgumentParser(description="Generate an auth token")
parser.add_argument("user", help="The username")
parser.add_argument("password", help="The password")
parser.add_argument("valid", type=int, default=1, nargs='?', help="Is valid for, in days")

args = parser.parse_args()
config = c2c.template.get_config(".build/config.yaml")
urllogin = config.get('urllogin', {})
aeskey = urllogin.get("aes_key")
auth_enc = create_token(aeskey, args.user, args.password, args.valid)

print("Use: auth={}".format(auth_enc))


if __name__ == "__main__":
main()
34 changes: 20 additions & 14 deletions c2cgeoportal/tests/functional/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from ConfigParser import ConfigParser
from webob.acceptparse import Accept

from pyramid import testing
import c2cgeoportal
from c2cgeoportal import tests
from c2cgeoportal.lib import functionality, caching
Expand All @@ -52,9 +53,13 @@
db_url = cfg.get("test", "sqlalchemy.url")

caching.init_region({"backend": "dogpile.cache.memory"})
config = None


def set_up_common():
global config
config = testing.setUp()

c2cgeoportal.schema = "main"
c2cgeoportal.srid = 21781
functionality.FUNCTIONALITIES_TYPES = None
Expand All @@ -79,6 +84,7 @@ def set_up_common():


def tear_down_common():
testing.tearDown()

functionality.FUNCTIONALITIES_TYPES = None

Expand Down Expand Up @@ -116,10 +122,12 @@ def create_default_ogcserver():
return ogcserver, ogcserver_external


def create_dummy_request(additional_settings=None, *args, **kargs):
def create_dummy_request(additional_settings=None, authentication=True, user=None, *args, **kargs):
if additional_settings is None:
additional_settings = {}
from c2cgeoportal.pyramid_ import default_user_validator
from c2cgeoportal.pyramid_ import create_get_user_from_request
from c2cgeoportal.lib.authentication import create_authentication
request = tests.create_dummy_request({
"mapserverproxy": {
"default_ogc_server": "__test_ogc_server",
Expand All @@ -136,23 +144,21 @@ def create_dummy_request(additional_settings=None, *args, **kargs):
}, *args, **kargs)
request.accept_language = Accept("fr-CH,fr;q=0.8,en;q=0.5,en-US;q=0.3")
request.registry.settings.update(additional_settings)
request.user = None
request.referer = "http://example.com/app"
request.path_info_peek = lambda: "main"
request.interface_name = "main"
request.registry.validate_user = default_user_validator
request.client_addr = None
return request


def add_user_property(request):
"""
Add the "user" property to the given request.
Disable referer checking.
"""
from c2cgeoportal.pyramid_ import _create_get_user_from_request
request.referer = "http://example.com/app"
request.path_info_peek = lambda: "main"
if authentication:
request._get_authentication_policy = lambda: create_authentication({
"authtkt_cookie_name": "__test",
"authtkt_secret": "123",
})
elif user is not None:
config.testing_securitypolicy(user)
request.set_property(
_create_get_user_from_request({"authorized_referers": [request.referer]}),
create_get_user_from_request({"authorized_referers": [request.referer]}),
name="user",
reify=True
)
return request
17 changes: 2 additions & 15 deletions c2cgeoportal/tests/functional/test_groups_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,19 @@


from nose.plugins.attrib import attr
from pyramid import testing
from unittest import TestCase

from c2cgeoportal.tests.functional import ( # noqa
tear_down_common as tearDownModule,
set_up_common as setUpModule,
add_user_property
create_dummy_request,
)


@attr(functional=True)
class TestGroupsFinder(TestCase):

def setUp(self): # noqa
self.config = testing.setUp()

import transaction
from c2cgeoportal.models import DBSession, User, Role

Expand All @@ -60,8 +57,6 @@ def setUp(self): # noqa

@staticmethod
def tearDown(): # noqa
testing.tearDown()

import transaction
from c2cgeoportal.models import DBSession, User, Role

Expand All @@ -73,14 +68,6 @@ def tearDown(): # noqa

def test_it(self):
from c2cgeoportal.resources import defaultgroupsfinder
self.config.testing_securitypolicy(u"__test_user")
request = self._create_request()
request = create_dummy_request(authentication=False, user=u"__test_user")
roles = defaultgroupsfinder(u"__test_user", request)
self.assertEqual(roles, [u"__test_role"])

@staticmethod
def _create_request():
from pyramid.request import Request
request = Request({})
add_user_property(request)
return request