Statistics
| Branch: | Revision:

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