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