From e909f73d03e8e46dfc4e7471f13c040d0f84b6ff Mon Sep 17 00:00:00 2001 From: "neil.chen.nj@gmail.com" Date: Mon, 8 Jun 2015 14:52:29 +0800 Subject: [PATCH 1/5] modify javascript test code to support auth post --- example/test/test.js | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/example/test/test.js b/example/test/test.js index 0186521..891675c 100644 --- a/example/test/test.js +++ b/example/test/test.js @@ -15,50 +15,55 @@ function APIClient(API_KEY, SEC_KEY, host, port) { this._port = port; } -APIClient.prototype.send_request = function (url, data, callback) { +APIClient.prototype.send_request = function(url, method, data, callback) { var opts = { hostname: this._host, port: this._port, - path: this.sign_url(url), - method: 'GET' + path: this.sign_url(url, data), + method: method || 'GET' }; - var req = http.request(opts, function (res) { - res.on('data', function (chunk) { + var req = http.request(opts, function(res) { + res.on('data', function(chunk) { console.log('URL:' + url + ' BODY: ' + chunk); typeof callback === 'function' && callback(chunk); }); }); - req.on('error', function (e) { + req.on('error', function(e) { console.log('problem with request: ' + e.message); }); - req.write(querystring.stringify(data)); + req.write(querystring.stringify(data)+ "\n"); req.end(); }; -APIClient.prototype.sign_msg = function (_msg) { +APIClient.prototype.sign_msg = function(_msg) { return crypto.createHmac('sha256', this._sk).update(_msg).digest('base64'); }; -APIClient.prototype.sign_url = function (_url) { +APIClient.prototype.sign_url = function(_url, data) { var url = (_url.indexOf('/') === 0 ? _url : '/' + _url); url += '?'; url += ('timestamp=' + new Date().getTime() / 1000); url += ('&apikey=' + this._ak); - url += ('&signature=' + this.sign_msg(url)); + url += ('&signature=' + this.sign_msg(url + (data? querystring.stringify(data): ""))); return url; }; apiclient = new APIClient(API_KEY, SEC_KEY, host, port); -console.log("send to /api/hello/"); -apiclient.send_request("/api/hello/"); +console.log("1. send GET to /api/hello/"); +apiclient.send_request("/api/hello/", 'GET'); -console.log("send to /api/goodbye/"); -apiclient.send_request("/api/goodbye/"); +console.log("2.1 send GET to /api/goodbye/"); +apiclient.send_request("/api/goodbye/", 'GET'); +console.log("2.2 send POST to /api/goodbye/"); +apiclient.send_request("/api/goodbye/", 'POST', 'world'); -console.log("send to /api/classbased1/"); -apiclient.send_request("/api/classbased1/"); \ No newline at end of file +console.log("3.1 send to /api/classbased1/"); +apiclient.send_request("/api/classbased1/", 'GET'); + +console.log("3.2 send to /api/classbased1/"); +apiclient.send_request("/api/classbased1/", 'POST', 'world'); From cbaa1736b8dc693ed15f43218e315ae6d25ffee4 Mon Sep 17 00:00:00 2001 From: FEI FAN Date: Mon, 15 Jun 2015 16:27:24 -0700 Subject: [PATCH 2/5] add command: repairapikeys --- README.rst | 15 +++++++++++- .../management/commands/repairapikeys.py | 24 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 djapiauth/management/commands/repairapikeys.py diff --git a/README.rst b/README.rst index 90f19ab..630ad55 100644 --- a/README.rst +++ b/README.rst @@ -100,7 +100,20 @@ and add URL mapping in ``urls.py`` Scan API ------------------- -we have a Django command ``reloadentrypoints`` to help you to collect and save all auth-required APIs to database. +There's a Django command ``reloadentrypoints`` to help you to collect and save all auth-required APIs to database. + + +API Key and associate API set +------------------------------- +since version ``0.8``, I start using a tree-alike structure to speed up matching incoming URLs. + +when you upgrade from previous versions, you should run following commands to expand database fields and build matching tree for each API key. + +.. code-block:: + + python manage.py migrate + python manage.py repairapikeys + Error messages diff --git a/djapiauth/management/commands/repairapikeys.py b/djapiauth/management/commands/repairapikeys.py new file mode 100644 index 0000000..0f8ceb7 --- /dev/null +++ b/djapiauth/management/commands/repairapikeys.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +import cPickle +from django.core.management.base import BaseCommand, CommandError +# from django.utils.module_loading import import_by_path +# from django.conf import settings + +from ...models import APIKeys, APITree +# from ...utility import is_protected_api, traverse_urls + +# rooturl = import_by_path(settings.ROOT_URLCONF+".urlpatterns") + + +class Command(BaseCommand): + args = '' + help = 'repair API key matching data' + + def handle(self, *args, **options): + for ak in APIKeys.objects.all(): + tree = APITree() + for api in ak.apis.all(): + srelist = cPickle.loads(api.pattern.encode("ascii")) + tree.add(srelist) + ak.apitree = cPickle.dumps(tree) + ak.save(update_fields=["apitree"]) \ No newline at end of file From d79116145697dd6d88caf68510f5fc7fb7f2dd17 Mon Sep 17 00:00:00 2001 From: FEI FAN Date: Sat, 1 Aug 2015 14:42:03 -0700 Subject: [PATCH 3/5] increase pattern length --- .../migrations/0003_auto_20150801_1439.py | 20 +++++++++++++++++++ djapiauth/models.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 djapiauth/migrations/0003_auto_20150801_1439.py diff --git a/djapiauth/migrations/0003_auto_20150801_1439.py b/djapiauth/migrations/0003_auto_20150801_1439.py new file mode 100644 index 0000000..114e68d --- /dev/null +++ b/djapiauth/migrations/0003_auto_20150801_1439.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('djapiauth', '0002_apikeys_apitree'), + ] + + operations = [ + migrations.AlterField( + model_name='apientrypoint', + name='pattern', + field=models.CharField(max_length=300), + preserve_default=True, + ), + ] diff --git a/djapiauth/models.py b/djapiauth/models.py index 2e5e1e9..589ac9d 100644 --- a/djapiauth/models.py +++ b/djapiauth/models.py @@ -49,7 +49,7 @@ class APIEntryPoint(models.Model): class Meta: verbose_name = "Entry point" name = models.CharField(max_length=100, unique=True) - pattern = models.CharField(max_length=100) # cPickle.dumps() + pattern = models.CharField(max_length=300) # cPickle.dumps() def __unicode__(self): return unicode(self.name) From 0d4900ebbe399011460c897fa3611e4a2baa577a Mon Sep 17 00:00:00 2001 From: FEI FAN Date: Mon, 10 Aug 2015 17:38:18 -0700 Subject: [PATCH 4/5] add version desp file --- VERSION.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 VERSION.txt diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 0000000..1367f6e --- /dev/null +++ b/VERSION.txt @@ -0,0 +1,3 @@ +v0.7 +- add match tree to speed up API permission check +- increase pattern DB lenght from 100 to 300 \ No newline at end of file From 6ab160c19de10c00151e23c2568434edec2ddf28 Mon Sep 17 00:00:00 2001 From: FEI FAN Date: Wed, 30 Sep 2015 14:03:18 -0700 Subject: [PATCH 5/5] bug fix --- djapiauth/models.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/djapiauth/models.py b/djapiauth/models.py index 589ac9d..c5ec191 100644 --- a/djapiauth/models.py +++ b/djapiauth/models.py @@ -1,10 +1,13 @@ import uuid import re import cPickle +import pprint + from django.db import models from django.conf import settings from django.contrib.auth.models import AnonymousUser -from django.db.models.signals import m2m_changed +from django.db.models.signals import m2m_changed, pre_delete +from django.dispatch import receiver class APITree(object): @@ -19,9 +22,8 @@ def add(self, srelist): p[pr] = (sre, {}) p = p[pr][1] - # def pprint(self): - # import pprint - # pprint.pprint(self._tree) + def pprint(self): + pprint.pprint(self._tree) def match(self, url): path = url @@ -94,6 +96,7 @@ def permission_check(apikey, endpoint): return None, None def _api_set_changed(sender, instance, action, **kwargs): + # removed/add an API from an API key tree = APITree() if action == "post_clear": instance.apitree = cPickle.dumps(tree) @@ -106,3 +109,19 @@ def _api_set_changed(sender, instance, action, **kwargs): instance.save(update_fields=["apitree"]) m2m_changed.connect(_api_set_changed, sender=APIKeys.apis.through) + +@receiver(pre_delete, sender=APIEntryPoint) +def _api_entry_deleted(sender, instance, using, *args, **kwargs): + # when an api entry is deleted, + # the entry will be removed automatically from api key + # but we need to refresh the apitree field which stores the data strucuture for fast-matching + for apikey in instance.apikeys_set.all(): + tree = APITree() + + for api in apikey.apis.all(): + if api.id == instance.id: + continue + srelist = cPickle.loads(api.pattern.encode("ascii")) + tree.add(srelist) + apikey.apitree = cPickle.dumps(tree) + apikey.save(update_fields=["apitree"])