Statistics
| Branch: | Revision:

root / aai / models.py @ 97:50ea35bf5083

History | View | Annotate | Download (7.5 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
      ('ecclesiastical',  _l("Ecclesiastical schools")),
15
      ('school',  _l("Other academic institutions")),
16
      ('institute', _l("Research institutes")),
17
      ('other', _l("Other")),
18
      ('test', _l("Testing")),
19
)
20

    
21

    
22
class ShibbolethMetadata:
23
    """Basic object holding the shibboleth metadata"""
24

    
25
    def __init__(self,filename):
26
        """Initialize a ShibbolethMetadata object
27

28
        arguments:
29
            filename -- The location of the XML document containing Shibboleth metadata
30

31
        """
32

    
33
        self.metadata = parse(filename).getroot()
34

    
35
    def getIdps(self):
36
        """Returns an IdpList holding all Identity Providers found in the metadata"""
37
        def filtersso(entity):
38
            try:
39
                entity.IDPSSODescriptor
40
                return True
41
            except:
42
                return False
43

    
44
        # Create an IdpList holding all entities that contain an IDPSSODescriptor
45
        return IdpList([ IdentityProvider(kot) for kot in filter(filtersso, self.metadata.EntityDescriptor) ])
46

    
47

    
48
class IdpList(list):
49
    """Class holding a list of Shibboleth Identity Provides"""
50

    
51
    def __init__(self,idplist):
52
        """Create a new list of Identity Providers
53

54
        arguments:
55
            idplist -- a list of IdentityProvider instances
56

57
        """
58
        list.__init__(self,idplist)
59

    
60
    def __getitem__(self, key):
61
        # Allow for "idplist['a_provider_entityid'] lookups
62
        try:
63
            return filter(lambda x: x.id == key, self)[0]
64
        except:
65
            return None
66

    
67
    def getCategories(self):
68
        """Returns the list of known categories of Identity Providers"""
69

    
70
        return sorted(set(map(lambda x: x.getType(), self)))
71

    
72
    def getCategoryIdps(self, category):
73
        """Returns the list of known identity providers for a given category"""
74

    
75
        return filter(lambda x: x.getType() == category, self)
76

    
77
    def getIdpForScope(self, scope):
78
        """Returns the identity provider matching a given scope"""
79

    
80
        try:
81
            return filter(lambda x: x.matchesScope(scope), self)[0]
82
        except:
83
            return None
84

    
85
    def getIdpsByCategory(self, lang=None, exclude=None):
86
        """Returns a sequence of tuples of the form:
87
        
88
        (category, [ idp1, idp2, ...])
89
        
90
        where idpX is a dict { 'name': name, 'id': entityid }
91

92
        The list of idps is sorted by name
93

94
        """
95
        if not lang:
96
            lang = get_language()
97

    
98
        if exclude:
99
            validcats = filter(lambda x: x[0] not in exclude, institution_categories)
100
        else:
101
            validcats = institution_categories
102

    
103
        cats = map(lambda x: x[0], validcats) 
104

    
105
        categories = map(lambda x: { 'id': x[0], 'name': x[1] }, validcats)
106

    
107
        idps = []
108

    
109
        for category in cats:
110
            catidps = sorted(self.getCategoryIdps(category))
111
            idps.append(map(lambda x: {
112
                    'name': x.getName(lang),
113
                    'url': x.getURL(lang),
114
                    'id': x.id },
115
                    catidps))
116

    
117
        return zip(categories, idps)
118

    
119
class IdentityProvider:
120
    """Basic class holding a Shibboleth Identity Provider"""
121

    
122
    def __init__(self,idp):
123
        """Create a new IdentityProvider instance
124
        arguments:
125
            idp -- An lxml.objectify.Element holding an EntityDescriptor for a shibboleth IdP
126

127
        """
128

    
129
        self.idp = idp
130
        self.name = {} # Holds the institution's name in a form { language: string }
131
        self.url = {}
132
        self.id = self.idp.get('entityID')
133

    
134
        # Initialize the contact details
135
        self.contact = { 'givenName': '', 'surName': '', 'company': '', 'email': '', 'telephone': '', 'url': '' }
136

    
137
        # Dictionary to hold all SingleSignOnService definitions, by Binding
138
        self.sso = {}
139

    
140
        # Get the institution's name
141
        for name in self.idp.Organization.OrganizationDisplayName:
142
            self.name[name.get('{http://www.w3.org/XML/1998/namespace}lang')] = name.text
143

    
144
        for url in self.idp.Organization.OrganizationURL:
145
            self.url[url.get('{http://www.w3.org/XML/1998/namespace}lang')] = url.text
146

    
147
        # Fill in the contact details
148
        for contact in self.idp.ContactPerson:
149
            if contact.get('contactType') == "support":
150
                # We're not sure these details even exists, but since that would
151
                # require a set of nested checks, exception catching is more
152
                # clean.
153
                try:
154
                    self.contact['email'] = contact.EmailAddress.text
155
                except:
156
                    pass
157

    
158
                try:
159
                    self.contact['telephone'] = contact.TelephoneNumber.text
160
                except:
161
                    pass
162

    
163
        # Get all single-sign-on service descriptions
164
        for entry in self.idp.IDPSSODescriptor.SingleSignOnService:
165
            self.sso[entry.get('Binding')] = entry.get('Location')
166

    
167
    def __cmp__ (self,other):
168
        # Alphabetic sorting by name
169
        return cmp(self.getName(get_language()), other.getName(get_language()))
170

    
171
    def __repr__(self):
172
        return "IDP: \"" + self.name['en'] + '"'
173

    
174
    def getName(self,lang=None):
175
        if not lang:
176
            lang = get_language()
177

    
178
        try:
179
            return self.name[lang]
180
        except:
181
            return self.name['en']
182

    
183
    def getURL(self,lang=None):
184
        if not lang:
185
            lang = get_language()
186

    
187
        try:
188
            return self.url[lang]
189
        except:
190
            pass
191

    
192
        try:
193
            return self.url['en']
194
        except:
195
            pass
196

    
197
        return None
198

    
199
    def getType(self):
200
        """Returns the type (category) of the current IdP"""
201

    
202
        # Some heuristics to determine the IdP type, based on the 
203
        # institution's name in english.
204
        if self.name['en'].lower().find('university') >= 0:
205
            return "university"
206

    
207
        elif self.name['en'].lower().find('school of fine arts') >= 0:
208
            return "university"
209

    
210
        elif self.name['en'].lower().find('technological educational') >= 0:
211
            return "tei"
212

    
213
        if self.name['en'].lower().find('ecclesiastical') >= 0:
214
            return "ecclesiastical"
215

    
216
        elif re.findall(r'(school|academy)', self.name['en'].lower()):
217
            return "school"
218

    
219
        elif re.findall(r'(institute|cent(er|re))', self.name['en'].lower()):
220
            return "institute"
221

    
222
        if self.name['en'].lower().find('test') >= 0:
223
            return "test"
224

    
225
        else:
226
            return "other"
227
    
228
    def getScope(self):
229
        """Returns the scope of the current IdP"""
230

    
231
        scopes = filter(lambda x: x.tag == "{urn:mace:shibboleth:metadata:1.0}Scope", self.idp.IDPSSODescriptor.Extensions.getchildren())
232
        return scopes[0].text
233

    
234
    def matchesScope(self,scope):
235
        """Checks wheter the current IdPs scope matches the given string"""
236

    
237
        myscope = self.getScope()
238
        
239
        # Append a trailing '$', to align the regexp with the string end
240
        if myscope[-1] != '$':
241
            myscope += '$'
242

    
243
        if re.search(myscope, scope):
244
            return True
245

    
246
        return False