Statistics
| Branch: | Revision:

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