summaryrefslogtreecommitdiff
path: root/coip/apps/scim
diff options
context:
space:
mode:
Diffstat (limited to 'coip/apps/scim')
-rw-r--r--coip/apps/scim/__init__.py86
-rw-r--r--coip/apps/scim/schema.py57
-rw-r--r--coip/apps/scim/urls.py16
-rw-r--r--coip/apps/scim/views.py144
4 files changed, 303 insertions, 0 deletions
diff --git a/coip/apps/scim/__init__.py b/coip/apps/scim/__init__.py
new file mode 100644
index 0000000..0520e65
--- /dev/null
+++ b/coip/apps/scim/__init__.py
@@ -0,0 +1,86 @@
+import logging
+
+class NotRegistered(Exception):
+ pass
+
+class NotAvailable(Exception):
+ pass
+
+class ObjectHandler(object):
+ def __init__(self,model,schemas):
+ self.model = model
+ self.schemas = schemas
+
+ def serialize(self,o):
+ d = {'meta': {},'schemas':[]}
+ for schema in self.schemas:
+ if schema.URI == 'urn:scim:schemas:core:1.0':
+ d.update(self.as_dict(schema,o))
+ else:
+ d.update({schema.URI: self.as_dict(schema,o)})
+ d['schemas'].append(schema.URI)
+ return d
+
+ def create(self,d):
+ for schema in self.schemas:
+ if schema.URI in d['schemas']:
+ data = d
+ if schema.URI != 'urn:scim:schemas:core:1.0':
+ data = d[schema.URI]
+ try:
+ return schema.create(self.model,data)
+ except NotAvailable:
+ pass
+ raise NotAvailable("No way to create this object")
+
+ def as_dict(self,schema,o):
+ d = dict([(a,schema.__class__.__dict__[a].__get__(o)) for a in schema.ATTRIBUTES])
+ logging.debug(d)
+ return d
+
+ def update(self,o,d,replace=False):
+ meta_attributes = d['meta'].get('attributes',{})
+ for schema in self.schemas:
+ if schema in d['schemas']:
+ data = d
+ if schema.URI != 'urn:scim:schemas:core:1.0':
+ data = d[schema]
+
+ for a in schema.ATTRIBUTES:
+ v = data[a]
+ t = type(v)
+ p = getattr(schema,a)
+ if a in meta_attributes:
+ if not data.has_key(a):
+ p.__delete__(o)
+ else:
+ p.__set__(o,v) ## replace
+ else: #add / remove
+ if t is dict: #merge
+ p.__set__(o,p.__get__(o).update(v))
+ elif (t is list or t is tuple) and not t is str:
+ for i in v:
+ if type(i) is dict:
+ if i.get('operation',None) == 'delete':
+ p.remove(o,i)
+ else:
+ p.update(o,i)
+ else: # no nested lists
+ p.add(o,i)
+ else:
+ p.__set__(o,v) # simple type
+
+class ObjectTypeRegistry(object):
+ def __init__(self):
+ self._registry = {}
+
+ def register(self,model,prefix,schemas):
+ if prefix == None:
+ prefix = "%ss" % model.__name__
+ #for schema in schemas:
+ # for attr in schema.ATTRIBUTES:
+ # setattr(model,attr,schema.__dict__['externalId'])
+
+ self._registry[prefix] = ObjectHandler(model,schemas)
+
+types = ObjectTypeRegistry() \ No newline at end of file
diff --git a/coip/apps/scim/schema.py b/coip/apps/scim/schema.py
new file mode 100644
index 0000000..5290cd5
--- /dev/null
+++ b/coip/apps/scim/schema.py
@@ -0,0 +1,57 @@
+'''
+Created on Apr 12, 2012
+
+@author: leifj
+'''
+from coip.apps.scim import NotAvailable
+
+class ScimAttribute(object):
+ def __get__(self,o,objtype=None):
+ raise NotAvailable()
+
+ def __set__(self,o,v):
+ raise NotAvailable()
+
+ def __delete__(self,o):
+ raise NotAvailable()
+
+ def add(self,o,v):
+ raise NotAvailable()
+
+ def remove(self,o,v):
+ raise NotAvailable()
+
+class scim_simple_attribute(ScimAttribute):
+
+ def __init__(self,attr):
+ self._attr = attr
+
+ def __get__(self,o,objtype=None):
+ a = getattr(o,self._attr)
+ if hasattr(a,'__call__'):
+ return "%s" % a()
+ else:
+ return "%s" % a
+
+ def __set__(self,o,v):
+ a = getattr(o,self._attr)
+ if hasattr(a,'__call__'):
+ a(v)
+ else:
+ setattr(o,self._attr,v)
+
+ def __delete__(self,o):
+ a = getattr(o,self._attr)
+ if not hasattr(a,'__call__'):
+ setattr(o,self._attr,None)
+
+class scim_reference_attribute(ScimAttribute):
+ def __init__(self,attr):
+ self._attr = attr
+
+ def __get__(self,o,objtype=None):
+ a = getattr(o,self._attr)
+ if a != None:
+ return a.uuid
+ else:
+ return None \ No newline at end of file
diff --git a/coip/apps/scim/urls.py b/coip/apps/scim/urls.py
new file mode 100644
index 0000000..2802586
--- /dev/null
+++ b/coip/apps/scim/urls.py
@@ -0,0 +1,16 @@
+'''
+Created on Apr 10, 2012
+
+@author: leifj
+'''
+
+from django.conf.urls.defaults import patterns, url
+
+# my/name/scim/v1/Groups/
+
+urlpatterns = patterns('coip.apps.scim.views',
+ url(r'^(?P<prefix>[^\/]+)/?$',view='scim_v1'),
+ url(r'^(?P<prefix>[^\/]+)/?(?P<uuid>[^\/]+)/?$',view='scim_v1'),
+)
+
+#(?:^(?P<nid>[0-9]+)/)? \ No newline at end of file
diff --git a/coip/apps/scim/views.py b/coip/apps/scim/views.py
new file mode 100644
index 0000000..b299262
--- /dev/null
+++ b/coip/apps/scim/views.py
@@ -0,0 +1,144 @@
+'''
+Created on Apr 10, 2012
+
+@author: leifj
+'''
+from django.http import HttpResponseNotFound, HttpResponseBadRequest,\
+ HttpResponse
+from coip.apps.resource.models import object_for_uuid
+
+from django.utils import simplejson
+from coip.apps.name.models import Name, lookup
+from uuid import UUID
+from coip.apps.scim import types
+import logging
+
+def get_handler_for_prefix(prefix):
+ if not prefix in types._registry.keys():
+ return None
+ return types._registry[prefix]
+
+def scim_response(d,status=200):
+ content = None
+ if d != None:
+ content = simplejson.dumps(d)
+ r = HttpResponse(content=content,status=status,content_type='application/json')
+ if not d.has_key('meta'):
+ d['meta'] = {}
+ location = d['meta'].get('location',None)
+ if location is not None:
+ r['Location'] = location
+
+ return r
+
+def _resolve_name(nid):
+ if nid == None:
+ return None
+
+ if '/' in nid:
+ return lookup(nid)
+
+ try:
+ pk = int(nid)
+ return Name.objects.get(id=pk)
+ except ValueError:
+ pass
+
+ try:
+ return object_for_uuid(str(UUID(nid)))
+ except ValueError:
+ pass
+
+ return None
+
+def scim_v1_get(request,name,handler,uuid):
+ o = object_for_uuid(uuid)
+ if o == None:
+ return HttpResponseNotFound
+ if not isinstance(o, handler.model):
+ return HttpResponseBadRequest("%s does not resolve to an object of type %s" % (uuid,handler.model.__name__))
+ d = handler.serialize(o)
+ #logging.debug(d)
+ d['meta']['location'] = request.build_absolute_uri()
+ return scim_response(d)
+
+def scim_v1_post(request,name,handler):
+ # authorize request based on name
+ d = simplejson.loads(request.raw_post_data)
+
+ o = handler.create(d)
+
+ d = handler.serialize(o)
+ d['meta']['location'] = request.build_absolute_uri()
+ return scim_response(d,201)
+
+def scim_v1_delete(request,name,handler,uuid):
+ o = object_for_uuid(uuid)
+ if o == None:
+ return HttpResponseNotFound
+ if not isinstance(o, handler.model):
+ return HttpResponseBadRequest("%s does not resolve to an object of type %s" % (uuid,handler.model.__name__))
+ o.delete()
+
+ return scim_response(200)
+
+def scim_v1_put(request,name,handler,uuid):
+ o = object_for_uuid(uuid)
+ if o == None:
+ return HttpResponseNotFound
+ if not isinstance(o,handler.model):
+ return HttpResponseBadRequest("%s does not resolve to an object of type %s" % (uuid,handler.model.__name__))
+
+ d = simplejson.loads(request.raw_post_data)
+ handler.update(o,d,replace=True)
+ o.save()
+
+ d = handler.serialize(o)
+ d['meta']['location'] = request.build_absolute_uri()
+ return scim_response(d,200)
+
+def scim_v1_patch(request,name,handler,uuid):
+ o = object_for_uuid(uuid)
+ if o == None:
+ return HttpResponseNotFound()
+ if not isinstance(o, handler.model):
+ return HttpResponseBadRequest("%s does not resolve to an object of type %s" % (uuid,handler.model.__name__))
+
+ d = simplejson.loads(request.raw_post_data)
+ handler.update(o,d,replace=False)
+ o.save()
+
+ return scim_response(204)
+
+def scim_v1(request,nid=None,prefix='Groups',uuid=None):
+ name = _resolve_name(nid)
+ handler = get_handler_for_prefix(prefix)
+ if handler == None:
+ return HttpResponseNotFound("Unknown SCIM resource type %s" % prefix)
+
+ if request.method == 'GET':
+ return scim_v1_get(request,name,handler,uuid)
+
+ if request.method == 'POST':
+ #if name == None:
+ # return HttpResponseNotFound("No such name")
+ if uuid != None:
+ return HttpResponseBadRequest("POSTing to resource is not allowed. Use PUT to update a resource.")
+ return scim_v1_post(request,name,handler)
+
+ if request.method == 'DELETE':
+ if uuid == None:
+ return HttpResponseBadRequest("Missing resource")
+ return scim_v1_delete(request,name,handler,uuid)
+
+ if request.method == 'PATCH':
+ if uuid == None:
+ return HttpResponseBadRequest("Missing resource")
+ return scim_v1_patch(request,name,handler,uuid)
+
+ if request.method == 'PUT':
+ if uuid == None:
+ return HttpResponseBadRequest("Missing resource")
+ return scim_v1_put(request,name,handler,uuid)
+
+ return HttpResponseBadRequest() \ No newline at end of file