summaryrefslogtreecommitdiff
path: root/coip
diff options
context:
space:
mode:
Diffstat (limited to 'coip')
-rw-r--r--coip/apps/invitation/models.py68
-rw-r--r--coip/apps/invitation/views.py7
-rw-r--r--coip/apps/membership/models.py62
-rw-r--r--coip/apps/name/models.py10
-rw-r--r--coip/apps/resource/__init__.py1
-rw-r--r--coip/apps/resource/admin.py4
-rw-r--r--coip/apps/resource/models.py90
-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
-rw-r--r--coip/apps/userprofile/models.py8
-rw-r--r--coip/settings.py4
-rw-r--r--coip/urls.py1
14 files changed, 543 insertions, 15 deletions
diff --git a/coip/apps/invitation/models.py b/coip/apps/invitation/models.py
index fabc145..22a4492 100644
--- a/coip/apps/invitation/models.py
+++ b/coip/apps/invitation/models.py
@@ -5,10 +5,20 @@ Created on Jun 23, 2010
'''
from django.db import models
from django.contrib.auth.models import User
-from coip.apps.name.models import Name
+from coip.apps.name.models import Name, lookup
from django.core.mail import send_mail
import logging
from coip.settings import PREFIX_URL, NOREPLY
+from coip.apps.scim.schema import scim_simple_attribute, ScimAttribute,\
+ scim_reference_attribute
+from coip.apps.scim import types
+from coip.apps.resource.models import object_for_uuid
+from coip.apps.auth.utils import nonce
+from django.dispatch.dispatcher import receiver
+from django.db.models.signals import post_save
+from actstream.signals import action
+from django.core.exceptions import ObjectDoesNotExist
+from iso8601 import iso8601
class Invitation(models.Model):
'''
@@ -26,8 +36,8 @@ class Invitation(models.Model):
def __unicode__(self):
return "%s invited to %s by %s" % (self.email,self.name,self.inviter)
- def send_email(self,request):
- pinviter = request.user.get_profile()
+ def send_email(self):
+ pinviter = self.inviter.get_profile()
send_mail('Invitation to join \'%s\'' % (self.name.shortname()),
'''
%s (%s) has invited you to join \'%s\':
@@ -48,3 +58,55 @@ To view information about \'%s\' open this link in your browser:
fail_silently=False)
return
+try:
+ from coip.apps.resource.models import resources
+ resources.register(Invitation)
+except ImportError:
+ pass
+
+@receiver(post_save,sender=Invitation)
+def auto_create_resource(sender,**kwargs):
+ if kwargs['created']:
+ invitation = kwargs['instance']
+ user = invitation.inviter
+ invitation.send_email()
+ action.send(user,verb='invited',target=invitation.name,action_object=invitation)
+
+class InvitationSchema():
+ URI = "uri:mace:swami.se:scim:schema:coip:1.0"
+ ATTRIBUTES = ('mail','externalId','groupId','message','expires','userId')
+
+ externalId = scim_simple_attribute('id')
+ mail = scim_simple_attribute('email')
+ groupId = scim_reference_attribute('name')
+ message = scim_simple_attribute('message')
+ expires = scim_simple_attribute('expires')
+ userId = scim_reference_attribute('inviter')
+
+ def create(self,model,d):
+ user = None
+ if d.has_key('userId'):
+ user = object_for_uuid(d['userId'])
+ elif d.has_key('userName'):
+ try:
+ user = User.objects.get(username=d['userName'])
+ except ObjectDoesNotExist:
+ pass
+
+ if user == None:
+ raise Exception("unspecified user")
+
+ name = None
+ if d.has_key('groupId'):
+ name = object_for_uuid(d['groupId'])
+ elif d.has_key('groupName'):
+ name = lookup(d['groupName'])
+
+ if name == None:
+ raise Exception("unspecified group")
+
+ expires = iso8601.parse_date(d['expires'])
+ invitation,created = Invitation.objects.get_or_create(inviter=user,nonce=nonce(),name=name,email=d['mail'],expires=expires)
+ return invitation
+
+types.register(Invitation,"Invitations",[InvitationSchema()]) \ No newline at end of file
diff --git a/coip/apps/invitation/views.py b/coip/apps/invitation/views.py
index 8e09ddc..2d11764 100644
--- a/coip/apps/invitation/views.py
+++ b/coip/apps/invitation/views.py
@@ -28,15 +28,12 @@ def invite(request,id):
form = InvitationForm(request.POST,instance=invitation)
if form.is_valid():
invitation = form.save()
- invitation.send_email(request)
return HttpResponseRedirect("/name/id/%d" % (name.id))
else:
exp = datetime.datetime.now()+datetime.timedelta(days=1)
invitation=Invitation(message="Please consider joining my group!",expires=exp.strftime("%Y-%m-%d"))
form = InvitationForm(instance=invitation);
- action.send(user,verb='invited',target=name,action_object=invitation)
-
return respond_to(request,{'text/html': 'apps/invitation/edit.html'},{'form': form,'name': name,'formtitle': 'Invite someone to join %s' % (name.short),'submitname': 'Invite User'})
@login_required
@@ -74,6 +71,4 @@ def resend(request,id):
action.send(request.user,verb='renewed invitation to',target=name,action_object=invitation)
invitation.send_email()
- return HttpResponseRedirect("/name/id/%d" % (name.id))
-
-
+ return HttpResponseRedirect("/name/id/%d" % (name.id)) \ No newline at end of file
diff --git a/coip/apps/membership/models.py b/coip/apps/membership/models.py
index 9aec54f..6e5dbe5 100644
--- a/coip/apps/membership/models.py
+++ b/coip/apps/membership/models.py
@@ -13,6 +13,8 @@ from coip.settings import NOREPLY
from coip.extensions.templatetags.userdisplay import userdisplay
from coip.apps.userprofile.models import UserProfile
from actstream.signals import action
+from coip.apps.scim import types
+import logging
STATUS = {UserProfile.INTERNAL:'internal',
UserProfile.ENTITY:'entity',
@@ -115,4 +117,62 @@ def remove_member(name,member_name,actor=None):
def has_member(name,member_name):
return Membership.objects.filter(name=name,user=member_name)
-tagging.register(Membership) \ No newline at end of file
+tagging.register(Membership)
+
+from coip.apps.scim.schema import scim_simple_attribute, ScimAttribute
+from coip.apps.resource.models import object_for_uuid
+
+class GroupSchema():
+ URI = 'urn:scim:schemas:core:1.0'
+ ATTRIBUTES = ('externalId','displayName','members','parentId')
+
+ externalId = scim_simple_attribute('url')
+ displayName = scim_simple_attribute('display')
+
+ class MembersAttribute(ScimAttribute):
+
+ def __get__(self,o,objtype=None):
+ return [
+ {
+ 'display': userdisplay(m.user),
+ 'value': m.user.uuid
+ } for m in o.memberships.filter(hidden=False).all()
+ ]
+
+ def __set__(self,o,v):
+ o.memberships = []
+ for i in v:
+ member = object_for_uuid(v['value'])
+ add_member(o,member)
+
+ def __delete__(self,o):
+ o.memberships.clear()
+
+ def remove(self,o,v):
+ member = object_for_uuid(v['value'])
+ remove_member(o,member.username,actor="scim")
+
+ def add(self,o,v):
+ member = object_for_uuid(v['value'])
+ add_member(o,member.username,actor="scim")
+
+ class ParentAttribute(ScimAttribute):
+ def __get__(self,o,objtype=None):
+ if o.parent:
+ return o.parent.uuid
+ else:
+ return None
+
+ members = MembersAttribute()
+ parentId = ParentAttribute()
+
+class UserSchema():
+ URI = 'urn:scim:schemas:core:1.0'
+ ATTRIBUTES = ('externalId','userName')
+
+ externalId = scim_simple_attribute('username')
+ userName = scim_simple_attribute('username')
+
+
+types.register(Name, "Groups", [GroupSchema()])
+types.register(User,"Users",[UserSchema()])
diff --git a/coip/apps/name/models.py b/coip/apps/name/models.py
index 519248b..eecfa2a 100644
--- a/coip/apps/name/models.py
+++ b/coip/apps/name/models.py
@@ -10,7 +10,9 @@ from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.signals import pre_save
import logging
-from coip.settings import PREFIX_URL
+from django.conf import settings
+from coip.apps import resource
+from coip.apps.resource.models import resources
class Attribute(models.Model):
name = models.CharField(unique=True,max_length=255)
@@ -108,13 +110,13 @@ class Name(models.Model):
return str
def url(self):
- return "%s/name/%s" % (PREFIX_URL,self.display_str_url())
+ return "%s/name/%s" % (settings.PREFIX_URL,self.display_str_url())
def uri(self):
if self.mode() == FMT_URN:
return self.display
else: # implement more format as needed
- return "%s/name/%s" % (PREFIX_URL,self.display)
+ return "%s/name/%s" % (settings.PREFIX_URL,self.display)
def summary(self):
return {'name': self.display, 'url': self.url(), 'short': self.short}
@@ -298,3 +300,5 @@ def lookup(name,autocreate=False):
def attribute(a):
Attribute.objects.get_or_create(name=a)
+
+resources.register(Name) \ No newline at end of file
diff --git a/coip/apps/resource/__init__.py b/coip/apps/resource/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/coip/apps/resource/__init__.py
@@ -0,0 +1 @@
+
diff --git a/coip/apps/resource/admin.py b/coip/apps/resource/admin.py
new file mode 100644
index 0000000..69f2e01
--- /dev/null
+++ b/coip/apps/resource/admin.py
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from coip.apps.resource.models import Resource
+
+admin.site.register(Resource) \ No newline at end of file
diff --git a/coip/apps/resource/models.py b/coip/apps/resource/models.py
new file mode 100644
index 0000000..7282ae6
--- /dev/null
+++ b/coip/apps/resource/models.py
@@ -0,0 +1,90 @@
+'''
+Created on Apr 10, 2012
+
+@author: leifj
+'''
+
+from django.db import models
+from django.db.models.fields import DateTimeField
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
+from django_extensions.db.fields import UUIDField
+from django.dispatch.dispatcher import receiver
+from django.db.models.signals import post_save, pre_delete
+from django.core.exceptions import ObjectDoesNotExist
+from django.db.models.base import ModelBase
+
+class NotAModel(Exception):
+ pass
+
+class RecursiveRegistration(Exception):
+ pass
+
+class UninitializedObject(Exception):
+ pass
+
+class Resource(models.Model):
+ uuid = UUIDField(unique=True)
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ lastupdated = DateTimeField(auto_now=True)
+ timecreated = DateTimeField(auto_now_add=True)
+
+ def __unicode__(self):
+ return self.uuid
+
+def object_for_uuid(uuid):
+ try:
+ r = Resource.objects.get(uuid=uuid)
+ return r.content_object
+ except ObjectDoesNotExist:
+ return None
+
+def uuid_for_object(o):
+ if not o.id:
+ raise UninitializedObject("Can't obtain uuid for non-persisted objects")
+
+ typ = ContentType.objects.get_for_model(o)
+ r,cr = Resource.objects.get_or_create(object_id=o.id,content_type=typ)
+ return r.uuid
+
+def add_resource(o):
+ typ = ContentType.objects.get_for_model(o)
+ r,cr = Resource.objects.get_or_create(object_id=o.id,content_type=typ)
+ return r.uuid
+
+def delete_resource(o):
+ typ = ContentType.objects.get_for_model(o)
+ try:
+ r = Resource.objects.get(object_id=o.id,content_type=typ)
+ r.delete()
+ except ObjectDoesNotExist:
+ pass
+
+class ResourceClassRegistry():
+ def __init__(self):
+ self._registry = []
+
+ def register(self,model,addField=True):
+ if not isinstance(model,ModelBase):
+ raise NotAModel('%s does not appear to be a model!' % model)
+ if not model in self._registry:
+ if addField:
+ setattr(model,'uuid',property(lambda x: uuid_for_object(x)))
+ self._registry.append(model)
+
+resources = ResourceClassRegistry()
+
+def register(model):
+ resources.register(model)
+
+@receiver(post_save)
+def auto_create_resource(sender,**kwargs):
+ if sender in resources._registry and kwargs['created']:
+ add_resource(kwargs['instance'])
+
+@receiver(pre_delete)
+def auto_delete_resource(sender,**kwargs):
+ if sender in resources._registry:
+ delete_resource(kwargs['instance'])
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
diff --git a/coip/apps/userprofile/models.py b/coip/apps/userprofile/models.py
index 315097d..6d1dd7f 100644
--- a/coip/apps/userprofile/models.py
+++ b/coip/apps/userprofile/models.py
@@ -48,4 +48,10 @@ class UserProfile(models.Model):
@receiver(post_save,sender=User)
def _create_profile(sender,**kwargs):
user = kwargs['instance']
- UserProfile.objects.get_or_create(user=user) \ No newline at end of file
+ UserProfile.objects.get_or_create(user=user)
+
+try:
+ from coip.apps.resource.models import resources
+ resources.register(User)
+except ImportError:
+ pass \ No newline at end of file
diff --git a/coip/settings.py b/coip/settings.py
index a4c7bef..5bb8a99 100644
--- a/coip/settings.py
+++ b/coip/settings.py
@@ -120,7 +120,9 @@ INSTALLED_APPS = (
'actstream',
'coip.apps.opensocial',
'coip.apps.activitystreams',
- 'coip.apps.saml2'
+ 'coip.apps.saml2',
+ 'coip.apps.resource',
+ 'coip.apps.scim'
)
OAUTH_REALM_KEY_NAME = 'http://coip-test.sunet.se'
diff --git a/coip/urls.py b/coip/urls.py
index 36c65ea..59051fb 100644
--- a/coip/urls.py
+++ b/coip/urls.py
@@ -71,6 +71,7 @@ urlpatterns = patterns('',
# APIs
(r'^api/activitystreams/', include('coip.apps.activitystreams.urls')),
(r'^api/opensocial/', include('coip.apps.opensocial.urls')),
+ (r'^api/scim/v1/', include('coip.apps.scim.urls')),
(r'^api/hello/?', 'coip.apps.name.views.hello'),
(r'^oauth2/', include('django_oauth2_lite.urls')),
(r'^saml2/aa/', include('coip.apps.saml2.urls')),