From 0bf6525fc63027546e5c90d423515085adbc79f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Mon, 9 Feb 2015 20:06:05 +0100 Subject: [PATCH 1/2] workflow: make it possible to sign webservice calls (#6446) The sign_* functions have been imported from cmsplugin_blurp. --- tests/test_api.py | 17 +++++++++++++++++ wcs/api.py | 29 +++++++++++++++++++++++++++++ wcs/wf/wscall.py | 16 ++++++++++++++-- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 0f86e6c..850f74c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -11,6 +11,7 @@ from wcs.users import User from wcs.formdef import FormDef from wcs.categories import Category from wcs import fields +from wcs.api import sign_url from utilities import get_app, create_temporary_pub @@ -118,6 +119,22 @@ def test_get_user_from_api_query_string_error_success_sha256(): output = get_app(pub).get('/user?%s&signature=%s' % (query, signature)) assert output.json['user_display_name'] == u'Jean Darmette' +def test_sign_url(): + signed_url = sign_url( + 'http://example.net/user?format=json&orig=coucou&email=%s' % urllib.quote(user.email), + '1234' + ) + url = signed_url[len('http://example.net'):] + output = get_app(pub).get(url) + assert output.json['user_display_name'] == u'Jean Darmette' + + signed_url = sign_url( + 'http://example.net/user?format=json&orig=coucou&email=%s' % urllib.quote(user.email), + '12345' + ) + url = signed_url[len('http://example.net'):] + output = get_app(pub).get(url, status=403) + def test_formdef_list(): FormDef.wipe() formdef = FormDef() diff --git a/wcs/api.py b/wcs/api.py index e1f157f..d1629c2 100644 --- a/wcs/api.py +++ b/wcs/api.py @@ -18,7 +18,10 @@ import base64 import hmac import hashlib import datetime +import urllib import urllib2 +import urlparse +import random from quixote import get_request, get_publisher, get_response from quixote.directory import Directory @@ -105,6 +108,32 @@ def get_user_from_api_query_string(): return user +def sign_url(url, key, algo='sha256', timestamp=None, nonce=None): + parsed = urlparse.urlparse(url) + new_query = sign_query(parsed.query, key, algo, timestamp, nonce) + return urlparse.urlunparse(parsed[:4] + (new_query,) + parsed[5:]) + +def sign_query(query, key, algo='sha256', timestamp=None, nonce=None): + if timestamp is None: + timestamp = datetime.datetime.utcnow() + timestamp = timestamp.strftime('%Y-%m-%dT%H:%M:%SZ') + if nonce is None: + nonce = hex(random.getrandbits(128))[2:-1] + new_query = query + if new_query: + new_query += '&' + new_query += urllib.urlencode(( + ('algo', algo), + ('timestamp', timestamp), + ('nonce', nonce))) + signature = base64.b64encode(sign_string(new_query, key, algo=algo)) + new_query += '&signature=' + urllib.quote(signature) + return new_query + +def sign_string(s, key, algo='sha256', timedelta=30): + digestmod = getattr(hashlib, algo) + hash = hmac.HMAC(key, digestmod=digestmod, msg=s) + return hash.digest() class ApiDirectory(Directory): _q_exports = [('reverse-geocoding', 'reverse_geocoding')] diff --git a/wcs/wf/wscall.py b/wcs/wf/wscall.py index c6f45f8..9a4fd28 100644 --- a/wcs/wf/wscall.py +++ b/wcs/wf/wscall.py @@ -18,7 +18,8 @@ import json from qommon.form import * from qommon.misc import http_get_page, http_post_request, get_variadic_url -from wcs.workflows import WorkflowStatusItem, register_item_class +from wcs.workflows import WorkflowStatusItem, register_item_class, template_on_formdata +from wcs.api import sign_url TIMEOUT = 15 @@ -30,9 +31,10 @@ class WebserviceCallStatusItem(WorkflowStatusItem): url = None varname = None post = True + request_signature_key = None def get_parameters(self): - return ('url', 'post', 'varname') + return ('url', 'post', 'varname', 'request_signature_key') def add_parameters_widgets(self, form, parameters, prefix='', formdef=None): if 'url' in parameters: @@ -46,6 +48,10 @@ class WebserviceCallStatusItem(WorkflowStatusItem): if 'varname' in parameters: form.add(VarnameWidget, '%svarname' % prefix, title=_('Variable Name'), value=self.varname) + if 'request_signature_key': + form.add(StringWidget, '%srequest_signature_key' % prefix, + title=_('Request Signature Key'), + value=self.request_signature_key) def perform(self, formdata): if not self.url: @@ -55,6 +61,12 @@ class WebserviceCallStatusItem(WorkflowStatusItem): if '[' in url: variables = get_publisher().substitutions.get_context_variables() url = get_variadic_url(url, variables) + + if self.request_signature_key: + signature_key = template_on_formdata(formdata, self.request_signature_key) + if signature_key: + url = sign_url(url, signature_key) + headers = {'Content-type': 'application/json', 'Accept': 'application/json'} if self.post: -- 2.1.4