root / aai / models.py @ 108:34870696ae9f
History | View | Annotate | Download (7.3 kB)
1 |
# -*- coding: utf-8 -*-
|
---|---|
2 |
from lxml.objectify import parse |
3 |
from django.utils.translation import ugettext as _ |
4 |
from django.utils.translation import ugettext_lazy as _l |
5 |
from django.utils.translation import get_language |
6 |
import re |
7 |
|
8 |
|
9 |
# A catalog of the institution categories. The order in which they appear
|
10 |
# here is the same as they will appear on the web.
|
11 |
institution_categories = ( |
12 |
('university', _l("Universities")), |
13 |
('tei', _l("Technological educational institutes")), |
14 |
('school', _l("Other academic institutions")), |
15 |
('institute', _l("Research institutes")), |
16 |
('other', _l("Other")), |
17 |
('test', _l("Testing")), |
18 |
) |
19 |
|
20 |
|
21 |
class ShibbolethMetadata: |
22 |
"""Basic object holding the shibboleth metadata"""
|
23 |
|
24 |
def __init__(self,filename): |
25 |
"""Initialize a ShibbolethMetadata object
|
26 |
|
27 |
arguments:
|
28 |
filename -- The location of the XML document containing Shibboleth metadata
|
29 |
|
30 |
"""
|
31 |
|
32 |
self.metadata = parse(filename).getroot()
|
33 |
|
34 |
def getIdps(self): |
35 |
"""Returns an IdpList holding all Identity Providers found in the metadata"""
|
36 |
def filtersso(entity): |
37 |
try:
|
38 |
entity.IDPSSODescriptor |
39 |
return True |
40 |
except:
|
41 |
return False |
42 |
|
43 |
# Create an IdpList holding all entities that contain an IDPSSODescriptor
|
44 |
return IdpList([ IdentityProvider(kot) for kot in filter(filtersso, self.metadata.EntityDescriptor) ]) |
45 |
|
46 |
|
47 |
class IdpList(list): |
48 |
"""Class holding a list of Shibboleth Identity Provides"""
|
49 |
|
50 |
def __init__(self,idplist): |
51 |
"""Create a new list of Identity Providers
|
52 |
|
53 |
arguments:
|
54 |
idplist -- a list of IdentityProvider instances
|
55 |
|
56 |
"""
|
57 |
list.__init__(self,idplist) |
58 |
|
59 |
def __getitem__(self, key): |
60 |
# Allow for "idplist['a_provider_entityid'] lookups
|
61 |
try:
|
62 |
return filter(lambda x: x.id == key, self)[0] |
63 |
except:
|
64 |
return None |
65 |
|
66 |
def getCategories(self): |
67 |
"""Returns the list of known categories of Identity Providers"""
|
68 |
|
69 |
return sorted(set(map(lambda x: x.getType(), self))) |
70 |
|
71 |
def getCategoryIdps(self, category): |
72 |
"""Returns the list of known identity providers for a given category"""
|
73 |
|
74 |
return filter(lambda x: x.getType() == category, self) |
75 |
|
76 |
def getIdpForScope(self, scope): |
77 |
"""Returns the identity provider matching a given scope"""
|
78 |
|
79 |
try:
|
80 |
return filter(lambda x: x.matchesScope(scope), self)[0] |
81 |
except:
|
82 |
return None |
83 |
|
84 |
def getIdpsByCategory(self, lang=None, exclude=None): |
85 |
"""Returns a sequence of tuples of the form:
|
86 |
|
87 |
(category, [ idp1, idp2, ...])
|
88 |
|
89 |
where idpX is a dict { 'name': name, 'id': entityid }
|
90 |
|
91 |
The list of idps is sorted by name
|
92 |
|
93 |
"""
|
94 |
if not lang: |
95 |
lang = get_language() |
96 |
|
97 |
if exclude:
|
98 |
validcats = filter(lambda x: x[0] not in exclude, institution_categories) |
99 |
else:
|
100 |
validcats = institution_categories |
101 |
|
102 |
cats = map(lambda x: x[0], validcats) |
103 |
|
104 |
categories = map(lambda x: { 'id': x[0], 'name': x[1] }, validcats) |
105 |
|
106 |
idps = [] |
107 |
|
108 |
for category in cats: |
109 |
catidps = sorted(self.getCategoryIdps(category)) |
110 |
idps.append(map(lambda x: { |
111 |
'name': x.getName(lang),
|
112 |
'url': x.getURL(lang),
|
113 |
'id': x.id },
|
114 |
catidps)) |
115 |
|
116 |
return zip(categories, idps) |
117 |
|
118 |
class IdentityProvider: |
119 |
"""Basic class holding a Shibboleth Identity Provider"""
|
120 |
|
121 |
def __init__(self,idp): |
122 |
"""Create a new IdentityProvider instance
|
123 |
arguments:
|
124 |
idp -- An lxml.objectify.Element holding an EntityDescriptor for a shibboleth IdP
|
125 |
|
126 |
"""
|
127 |
|
128 |
self.idp = idp
|
129 |
self.name = {} # Holds the institution's name in a form { language: string } |
130 |
self.url = {}
|
131 |
self.id = self.idp.get('entityID') |
132 |
|
133 |
# Initialize the contact details
|
134 |
self.contact = { 'givenName': '', 'surName': '', 'company': '', 'email': '', 'telephone': '', 'url': '' } |
135 |
|
136 |
# Dictionary to hold all SingleSignOnService definitions, by Binding
|
137 |
self.sso = {}
|
138 |
|
139 |
# Get the institution's name
|
140 |
for name in self.idp.Organization.OrganizationDisplayName: |
141 |
self.name[name.get('{http://www.w3.org/XML/1998/namespace}lang')] = name.text |
142 |
|
143 |
for url in self.idp.Organization.OrganizationURL: |
144 |
self.url[url.get('{http://www.w3.org/XML/1998/namespace}lang')] = url.text |
145 |
|
146 |
# Fill in the contact details
|
147 |
for contact in self.idp.ContactPerson: |
148 |
if contact.get('contactType') == "support": |
149 |
# We're not sure these details even exists, but since that would
|
150 |
# require a set of nested checks, exception catching is more
|
151 |
# clean.
|
152 |
try:
|
153 |
self.contact['email'] = contact.EmailAddress.text |
154 |
except:
|
155 |
pass
|
156 |
|
157 |
try:
|
158 |
self.contact['telephone'] = contact.TelephoneNumber.text |
159 |
except:
|
160 |
pass
|
161 |
|
162 |
# Get all single-sign-on service descriptions
|
163 |
for entry in self.idp.IDPSSODescriptor.SingleSignOnService: |
164 |
self.sso[entry.get('Binding')] = entry.get('Location') |
165 |
|
166 |
def __cmp__ (self,other): |
167 |
# Alphabetic sorting by name
|
168 |
return cmp(self.getName(get_language()), other.getName(get_language())) |
169 |
|
170 |
def __repr__(self): |
171 |
return "IDP: \"" + self.name['en'] + '"' |
172 |
|
173 |
def getName(self,lang=None): |
174 |
if not lang: |
175 |
lang = get_language() |
176 |
|
177 |
try:
|
178 |
return self.name[lang] |
179 |
except:
|
180 |
return self.name['en'] |
181 |
|
182 |
def getURL(self,lang=None): |
183 |
if not lang: |
184 |
lang = get_language() |
185 |
|
186 |
try:
|
187 |
return self.url[lang] |
188 |
except:
|
189 |
pass
|
190 |
|
191 |
try:
|
192 |
return self.url['en'] |
193 |
except:
|
194 |
pass
|
195 |
|
196 |
return None |
197 |
|
198 |
def getType(self): |
199 |
"""Returns the type (category) of the current IdP"""
|
200 |
|
201 |
# Some heuristics to determine the IdP type, based on the
|
202 |
# institution's name in english.
|
203 |
if self.name['en'].lower().find('test') >= 0: |
204 |
return "test" |
205 |
elif self.name['en'].lower().find('university') >= 0: |
206 |
return "university" |
207 |
elif self.name['en'].lower().find('school of fine arts') >= 0: |
208 |
return "university" |
209 |
elif self.name['en'].lower().find('technological') >= 0: |
210 |
return "tei" |
211 |
elif re.findall(r'(ecclesiastical|school|academy)', self.name['en'].lower()): |
212 |
return "school" |
213 |
elif re.findall(r'(institute|cent(er|re)|ncsr)', self.name['en'].lower()): |
214 |
return "institute" |
215 |
else:
|
216 |
return "other" |
217 |
|
218 |
def getScope(self): |
219 |
"""Returns the scope of the current IdP"""
|
220 |
|
221 |
scopes = filter(lambda x: x.tag == "{urn:mace:shibboleth:metadata:1.0}Scope", self.idp.IDPSSODescriptor.Extensions.getchildren()) |
222 |
return scopes[0].text |
223 |
|
224 |
def matchesScope(self,scope): |
225 |
"""Checks wheter the current IdPs scope matches the given string"""
|
226 |
|
227 |
myscope = self.getScope()
|
228 |
|
229 |
# Append a trailing '$', to align the regexp with the string end
|
230 |
if myscope[-1] != '$': |
231 |
myscope += '$'
|
232 |
|
233 |
if re.search(myscope, scope):
|
234 |
return True |
235 |
|
236 |
return False |