diff options
Diffstat (limited to 'coip/apps/scim')
-rw-r--r-- | coip/apps/scim/__init__.py | 86 | ||||
-rw-r--r-- | coip/apps/scim/schema.py | 57 | ||||
-rw-r--r-- | coip/apps/scim/urls.py | 16 | ||||
-rw-r--r-- | coip/apps/scim/views.py | 144 |
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 |