diff --git a/app/api/admin.py b/app/api/admin.py index 5ec9e8c..6f2c53c 100644 --- a/app/api/admin.py +++ b/app/api/admin.py @@ -5,6 +5,7 @@ class AuthAPIKeyAdmin(admin.ModelAdmin): list_display = ('key', 'name', 'url', 'active') search_fields = ['name'] list_filter = ('active',) + filter_horizontal = ('permissions',) class AuthAPILogAdmin(admin.ModelAdmin): diff --git a/app/api/auth.py b/app/api/auth.py index 40b4cda..a3d2fa5 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -4,6 +4,7 @@ from datetime import datetime from django.http import HttpResponseForbidden from django.contrib.auth.models import AnonymousUser from django.utils.timezone import now +from django.core.urlresolvers import resolve from api.models import AuthAPIKey, AuthAPILog @@ -15,7 +16,7 @@ class APIKeyAuthentication(object): try: keyobj = AuthAPIKey.objects.get(key=request.GET.get('apikey', None)) except AuthAPIKey.DoesNotExist: - pass + return False else: if keyobj and keyobj.active: params = request.GET.copy() @@ -24,6 +25,8 @@ class APIKeyAuthentication(object): url = "%s?%s" % (request.path, urlencode(params)) else: url = request.path + if not keyobj.permissions.filter(key=resolve(request.path).url_name).count(): + return False AuthAPILog.objects.create(key=keyobj, access_datetime=now(), url=url) request.user = AnonymousUser() request.api_key = keyobj @@ -31,4 +34,4 @@ class APIKeyAuthentication(object): return False def challenge(self): - return HttpResponseForbidden('Access Denied, use a API Key') + return HttpResponseForbidden('Access Denied, use a valid API Key for this request.') diff --git a/app/api/migrations/0003_auto__add_authapikeypermission.py b/app/api/migrations/0003_auto__add_authapikeypermission.py new file mode 100644 index 0000000..87ab9dd --- /dev/null +++ b/app/api/migrations/0003_auto__add_authapikeypermission.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'AuthAPIKeyPermission' + db.create_table('api_authapikeypermission', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('key', self.gf('django.db.models.fields.CharField')(max_length=32)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=200)), + )) + db.send_create_signal('api', ['AuthAPIKeyPermission']) + + # Adding M2M table for field permissions on 'AuthAPIKey' + db.create_table('api_authapikey_permissions', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('authapikey', models.ForeignKey(orm['api.authapikey'], null=False)), + ('authapikeypermission', models.ForeignKey(orm['api.authapikeypermission'], null=False)) + )) + db.create_unique('api_authapikey_permissions', ['authapikey_id', 'authapikeypermission_id']) + + def backwards(self, orm): + # Deleting model 'AuthAPIKeyPermission' + db.delete_table('api_authapikeypermission') + + # Removing M2M table for field permissions on 'AuthAPIKey' + db.delete_table('api_authapikey_permissions') + + models = { + 'api.authapikey': { + 'Meta': {'object_name': 'AuthAPIKey'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'keys'", 'symmetrical': 'False', 'to': "orm['api.AuthAPIKeyPermission']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'api.authapikeypermission': { + 'Meta': {'object_name': 'AuthAPIKeyPermission'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + 'api.authapilog': { + 'Meta': {'ordering': "['access_datetime']", 'object_name': 'AuthAPILog'}, + 'access_datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['api.AuthAPIKey']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + } + } + + complete_apps = ['api'] \ No newline at end of file diff --git a/app/api/migrations/0004_api_key_permissions.py b/app/api/migrations/0004_api_key_permissions.py new file mode 100644 index 0000000..6952ac4 --- /dev/null +++ b/app/api/migrations/0004_api_key_permissions.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + from api.urls import urlpatterns as urls + + for u in urls: + if not u.name is None: + orm['api.AuthAPIKeyPermission'].objects.get_or_create(key=u.name, name=u.name) + perms = orm['api.AuthAPIKeyPermission'].objects.all() + + for key in orm['api.AuthAPIKey'].objects.all(): + key.permissions.add(*perms) + + + def backwards(self, orm): + for key in orm['api.AuthAPIKey'].objects.all(): + key.permissions.clear() + + orm['api.AuthAPIKeyPermission'].objects.all().delete() + + models = { + 'api.authapikey': { + 'Meta': {'object_name': 'AuthAPIKey'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'keys'", 'symmetrical': 'False', 'to': "orm['api.AuthAPIKeyPermission']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'api.authapikeypermission': { + 'Meta': {'object_name': 'AuthAPIKeyPermission'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + 'api.authapilog': { + 'Meta': {'ordering': "['access_datetime']", 'object_name': 'AuthAPILog'}, + 'access_datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['api.AuthAPIKey']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + } + } + + complete_apps = ['api'] + symmetrical = True diff --git a/app/api/models.py b/app/api/models.py index 75a00d3..7856110 100644 --- a/app/api/models.py +++ b/app/api/models.py @@ -2,14 +2,28 @@ import uuid from django.db import models +class AuthAPIKeyPermission(models.Model): + """ Auth API Key permissions """ + key = models.CharField("Permission Key", max_length=32) + name = models.CharField("Permission Name", max_length=200) + + def __unicode__(self): + return self.name + + class Meta: + verbose_name = 'API Key Permission' + verbose_name_plural = 'API Key Permissions' + + class AuthAPIKey(models.Model): """ Auth API Key storage model """ name = models.CharField("Service Name", max_length=200) - url = models.CharField("Service URL", max_length=200, blank=True, help_text="URL that the service is available at") + url = models.CharField("Service URL", max_length=200, blank=True, help_text="URL that the service is available at.") active = models.BooleanField(default=True) - key = models.CharField("API Key", max_length=200, blank=True, help_text="API key for the service to use") - callback = models.CharField("Callback URL", max_length=200, blank=True, help_text="URL for the callback service to connect to") + key = models.CharField("API Key", max_length=200, blank=True, help_text="API key for the service to use.") + callback = models.CharField("Callback URL", max_length=200, blank=True, help_text="URL for the callback service to connect to.") + permissions = models.ManyToManyField(AuthAPIKeyPermission, related_name='keys', help_text="API calls this API key is allowed to access.") def save(self, *args, **kwargs): if not self.key or self.key == '': diff --git a/app/api/urls.py b/app/api/urls.py index 9e7bf24..3e40106 100755 --- a/app/api/urls.py +++ b/app/api/urls.py @@ -21,15 +21,15 @@ announce_resource = Resource(handler=AnnounceHandler, **apikeyauth) edkapi_resource = Resource(handler=EDKApiHandler, **apikeyauth) urlpatterns = patterns('', - url(r'^1.0/user/$', user_resource), - url(r'^1.0/login/$', login_resource), - url(r'^1.0/eveapi/$', eveapi_resource), - url(r'^1.0/eveapi/', eveapiproxy_resource, name='api-eveapiproxy'), - url(r'^1.0/character/$', characters_resource), - url(r'^1.0/optimer/$', optimer_resource), - url(r'^1.0/blacklist/$', blacklist_resource), - url(r'^1.0/announce/$', announce_resource), - url(r'^1.0/edkapi/$', edkapi_resource), + url(r'^1.0/user/$', user_resource, name='v1-api-user'), + url(r'^1.0/login/$', login_resource, name='v1-api-login'), + url(r'^1.0/eveapi/$', eveapi_resource, name='v1-api-eveapi'), + url(r'^1.0/eveapi/', eveapiproxy_resource, name='v1-api-eveapiproxy'), + url(r'^1.0/character/$', characters_resource, name='v1-api-character'), + url(r'^1.0/optimer/$', optimer_resource, name='v1-api-optimer'), + url(r'^1.0/blacklist/$', blacklist_resource, name='v1-api-blacklist'), + url(r'^1.0/announce/$', announce_resource, name='v1-api-announce'), + url(r'^1.0/edkapi/$', edkapi_resource, name='v1-api-edkapi'), ) # v2 APIs