Completed
Push — master ( 56e37b...2dcafa )
by Andrea
35s
created

Federation.get_stat_protocol()   A

Complexity

Conditions 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 3
1
#################################################################
2
# MET v2 Metadate Explorer Tool
3
#
4
# This Software is Open Source. See License: https://github.com/TERENA/met/blob/master/LICENSE.md
5
# Copyright (c) 2012, TERENA All rights reserved.
6
#
7
# This Software is based on MET v1 developed for TERENA by Yaco Sistemas, http://www.yaco.es/
8
# MET v2 was developed for TERENA by Tamim Ziai, DAASI International GmbH, http://www.daasi.de
9
# Current version of MET has been revised for performance improvements by Andrea Biancini,
10
# Consortium GARR, http://www.garr.it
11
##########################################################################
12
13
import pytz
14
import simplejson as json
15
16
from datetime import datetime, time, timedelta
17
18
from django.core.urlresolvers import reverse
19
from django.db import models
20
from django.db.models import Max
21
from django.db.models.signals import pre_save
22
from django.utils.translation import ugettext_lazy as _
23
from django.utils import timezone
24
from django.dispatch import receiver
25
from django.template.defaultfilters import slugify
26
27
from met.metadataparser.xmlparser import MetadataParser
28
29
from met.metadataparser.models.base import Base, XmlDescriptionError
30
from met.metadataparser.models.entity import Entity
31
from met.metadataparser.models.entity_type import EntityType
32
from met.metadataparser.models.entity_stat import EntityStat, stats
33
from met.metadataparser.models.entity_federations import Entity_Federations
34
35
FEDERATION_TYPES = (
36
    (None, ''),
37
    ('hub-and-spoke', 'Hub and Spoke'),
38
    ('mesh', 'Full Mesh'),
39
)
40
41
42
def update_obj(mobj, obj, attrs=None):
43
    for_attrs = attrs or getattr(mobj, 'all_attrs', [])
44
    for attrb in attrs or for_attrs:
45
        if (getattr(mobj, attrb, None) and
46
            getattr(obj, attrb, None) and
47
                getattr(mobj, attrb) != getattr(obj, attrb)):
48
            setattr(obj, attrb, getattr(mobj, attrb))
49
50
51
class Federation(Base):
52
    """
53
    Model describing an identity federation.
54
    """
55
56
    name = models.CharField(blank=False, null=False, max_length=200,
57
                            unique=True, verbose_name=_(u'Name'))
58
59
    type = models.CharField(blank=True, null=True, max_length=100,
60
                            unique=False, verbose_name=_(u'Type'), choices=FEDERATION_TYPES)
61
62
    url = models.URLField(verbose_name='Federation url',
63
                          blank=True, null=True)
64
65
    fee_schedule_url = models.URLField(verbose_name='Fee schedule url',
66
                                       max_length=150, blank=True, null=True)
67
68
    logo = models.ImageField(upload_to='federation_logo', blank=True,
69
                             null=True, verbose_name=_(u'Federation logo'))
70
71
    is_interfederation = models.BooleanField(default=False, db_index=True,
72
                                             verbose_name=_(u'Is interfederation'))
73
74
    slug = models.SlugField(max_length=200, unique=True)
75
76
    country = models.CharField(blank=True, null=True, max_length=100,
77
                               unique=False, verbose_name=_(u'Country'))
78
79
    metadata_update = models.DateField(blank=True, null=True,
80
                                       unique=False, verbose_name=_(u'Metadata update date'))
81
82
    certstats = models.CharField(blank=True, null=True, max_length=200,
83
                                 unique=False, verbose_name=_(u'Certificate Stats'))
84
85
    @property
86
    def certificates(self):
87
        return json.loads(self.certstats)
88
89
    @property
90
    def _metadata(self):
91
        if not hasattr(self, '_metadata_cache'):
92
            self._metadata_cache = self.load_file()
93
        return self._metadata_cache
94
95
    def __unicode__(self):
96
        return self.name
97
98
    def get_entity_metadata(self, entityid):
99
        return self._metadata.get_entity(entityid)
100
101
    def get_entity(self, entityid):
102
        return self.entity_set.get(entityid=entityid)
103
104
    def process_metadata(self):
105
        metadata = self.load_file()
106
107
        if self.file_id and metadata.file_id and metadata.file_id == self.file_id:
108
            return
109
        else:
110
            self.file_id = metadata.file_id
111
112
        if not metadata:
113
            return
114
        if not metadata.is_federation:
115
            raise XmlDescriptionError("XML Haven't federation form")
116
117
        update_obj(metadata.get_federation(), self)
118
        self.certstats = MetadataParser.get_certstats(metadata.rootelem)
119
120
    def _remove_deleted_entities(self, entities_from_xml):
121
        removed = 0
122
        for entity in self.entity_set.all():
123
            # Remove entity relation if does not exist in metadata
124
            if not entity.entityid in entities_from_xml:
125
                Entity_Federations.objects.filter(
126
                    federation=self, entity=entity).delete()
127
                removed += 1
128
129
        return removed
130
131
    def _update_entities(self, entities_to_update, entities_to_add):
132
        for e in entities_to_update:
133
            e.save()
134
135
        for e in entities_to_add:
136
            membership = Entity_Federations.objects.get_or_create(
137
                federation=self, entity=e)[0]
138
            membership.registration_instant = e.registration_instant.date(
139
            ) if e.registration_instant else None
140
            membership.save()
141
142
    def _add_new_entities(self, entities, entities_from_xml, request, federation_slug):
143
        db_entity_types = EntityType.objects.all()
144
        cached_entity_types = {
145
            entity_type.xmlname: entity_type for entity_type in db_entity_types}
146
147
        entities_to_add = []
148
        entities_to_update = []
149
150
        for m_id in entities_from_xml:
151
            if request and federation_slug:
152
                request.session['%s_cur_entities' % federation_slug] += 1
153
                request.session.save()
154
155
            created = False
156
            if m_id in entities:
157
                entity = entities[m_id]
158
            else:
159
                entity, created = Entity.objects.get_or_create(entityid=m_id)
160
161
            entityid = entity.entityid
162
            name = entity.name
163
            registration_authority = entity.registration_authority
164
            certstats = entity.certstats
165
            display_protocols = entity._display_protocols
166
167
            entity_from_xml = self._metadata.get_entity(m_id, False)
168
            entity.process_metadata(
169
                False, entity_from_xml, cached_entity_types)
170
171
            if created or entity.has_changed(entityid, name, registration_authority, certstats, display_protocols):
172
                entities_to_update.append(entity)
173
174
            entities_to_add.append(entity)
175
176
        self._update_entities(entities_to_update, entities_to_add)
177
        return len(entities_to_update)
178
179
    @staticmethod
180
    def _daterange(start_date, end_date):
181
        for n in range(int((end_date - start_date).days + 1)):
182
            yield start_date + timedelta(n)
183
184
    def compute_new_stats(self):
185
        if not self._metadata: return ([], [])
186
        entities_from_xml = self._metadata.get_entities()
187
188
        entities = Entity.objects.filter(entityid__in=entities_from_xml)
189
        entities = entities.prefetch_related('types')
190
        Entity_Federations.objects.filter(federation=self)
191
192
        try:
193
            first_date = EntityStat.objects.filter(
194
                federation=self).aggregate(Max('time'))['time__max']
195
            if not first_date:
196
                raise Exception('Not able to find statistical data in the DB.')
197
        except Exception:
198
            first_date = datetime(2010, 1, 1)
199
            first_date = pytz.utc.localize(first_date)
200
201
        for curtimestamp in self._daterange(first_date, timezone.now() - timedelta(1)):
202
            computed = {}
203
            not_computed = []
204
            entity_stats = []
205
            for feature in stats['features'].keys():
206
                fun = getattr(self, 'get_%s' % feature, None)
207
208
                if callable(fun):
209
                    stat = EntityStat()
210
                    stat.feature = feature
211
                    stat.time = curtimestamp
212
                    stat.federation = self
213
                    stat.value = fun(
214
                        entities, stats['features'][feature], curtimestamp)
215
                    entity_stats.append(stat)
216
                    computed[feature] = stat.value
217
                else:
218
                    not_computed.append(feature)
219
220
            from_time = datetime.combine(curtimestamp, time.min)
221
            if timezone.is_naive(from_time):
222
                from_time = pytz.utc.localize(from_time)
223
            to_time = datetime.combine(curtimestamp, time.max)
224
            if timezone.is_naive(to_time):
225
                to_time = pytz.utc.localize(to_time)
226
227
            EntityStat.objects.filter(
228
                federation=self, time__gte=from_time, time__lte=to_time).delete()
229
            EntityStat.objects.bulk_create(entity_stats)
230
231
        return (computed, not_computed)
232
233
    def process_metadata_entities(self, request=None, federation_slug=None):
234
        if not self._metadata: return
235
        entities_from_xml = self._metadata.get_entities()
236
        removed = self._remove_deleted_entities(entities_from_xml)
237
238
        entities = {}
239
        db_entities = Entity.objects.filter(entityid__in=entities_from_xml)
240
        db_entities = db_entities.prefetch_related(
241
            'types', 'entity_categories')
242
243
        for entity in db_entities.all():
244
            entities[entity.entityid] = entity
245
246
        if request and federation_slug:
247
            request.session['%s_num_entities' %
248
                            federation_slug] = len(entities_from_xml)
249
            request.session['%s_cur_entities' % federation_slug] = 0
250
            request.session['%s_process_done' % federation_slug] = False
251
            request.session.save()
252
253
        updated = self._add_new_entities(
254
            entities, entities_from_xml, request, federation_slug)
255
256
        if request and federation_slug:
257
            request.session['%s_process_done' % federation_slug] = True
258
            request.session.save()
259
260
        return removed, updated
261
262
    def get_absolute_url(self):
263
        return reverse('federation_view', args=[self.slug])
264
265
    @classmethod
266
    def get_sp(cls, entities, xml_name, ref_date=None):
267
        if ref_date and ref_date < pytz.utc.localize(datetime.now() - timedelta(days=1)):
268
            selected = entities.filter(
269
                types__xmlname=xml_name, entity_federations__registration_instant__lt=ref_date)
270
        else:
271
            selected = entities.filter(types__xmlname=xml_name)
272
        return len(selected)
273
274
    @classmethod
275
    def get_idp(cls, entities, xml_name, ref_date=None):
276
        if ref_date and ref_date < pytz.utc.localize(datetime.now() - timedelta(days=1)):
277
            selected = entities.filter(
278
                types__xmlname=xml_name, entity_federations__registration_instant__lt=ref_date)
279
        else:
280
            selected = entities.filter(types__xmlname=xml_name)
281
        return len(selected)
282
283
    @classmethod
284
    def get_aa(cls, entities, xml_name, ref_date=None):
285
        if ref_date and ref_date < pytz.utc.localize(datetime.now() - timedelta(days=1)):
286
            selected = entities.filter(
287
                types__xmlname=xml_name, entity_federations__registration_instant__lt=ref_date)
288
        else:
289
            selected = entities.filter(types__xmlname=xml_name)
290
        return len(selected)
291
292
    def get_sp_saml1(self, entities, xml_name, ref_date=None):
293
        return self.get_stat_protocol(entities, xml_name, 'SPSSODescriptor', ref_date)
294
295
    def get_sp_saml2(self, entities, xml_name, ref_date=None):
296
        return self.get_stat_protocol(entities, xml_name, 'SPSSODescriptor', ref_date)
297
298
    def get_sp_shib1(self, entities, xml_name, ref_date=None):
299
        return self.get_stat_protocol(entities, xml_name, 'SPSSODescriptor', ref_date)
300
301
    def get_idp_saml1(self, entities, xml_name, ref_date=None):
302
        return self.get_stat_protocol(entities, xml_name, 'IDPSSODescriptor', ref_date)
303
304
    def get_idp_saml2(self, entities, xml_name, ref_date=None):
305
        return self.get_stat_protocol(entities, xml_name, 'IDPSSODescriptor', ref_date)
306
307
    def get_idp_shib1(self, entities, xml_name, ref_date=None):
308
        return self.get_stat_protocol(entities, xml_name, 'IDPSSODescriptor', ref_date)
309
310
    def get_stat_protocol(self, entities, xml_name, service_type, ref_date):
311
        if ref_date and ref_date < pytz.utc.localize(datetime.now() - timedelta(days=1)):
312
            selected = entities.filter(types__xmlname=service_type,
313
                                       _display_protocols__contains=xml_name,
314
                                       entity_federations__registration_instant__lt=ref_date)
315
        else:
316
            selected = entities.filter(types__xmlname=service_type,
317
                                       _display_protocols__contains=xml_name)
318
319
        return len(selected)
320
321
    def can_edit(self, user, delete):
322
        if user.is_superuser:
323
            return True
324
325
        permission = 'delete_federation' if delete else 'change_federation'
326
        if user.has_perm('metadataparser.%s' % permission) and user in self.editor_users.all():
327
            return True
328
        return False
329
330
331
@receiver(pre_save, sender=Federation, dispatch_uid='federation_pre_save')
332
def federation_pre_save(sender, instance, **kwargs):
333
    # Skip pre_save if only file name is saved
334
    if kwargs.has_key('update_fields') and kwargs['update_fields'] == set(['file']):
335
        return
336
337
    #slug = slugify(unicode(instance.name))[:200]
338
    # if instance.file_url and instance.file_url != '':
339
    #    try:
340
    #        instance.fetch_metadata_file(slug)
341
    #    except Exception, e:
342
    #        pass
343
344
    if instance.name:
345
        instance.slug = slugify(unicode(instance))[:200]
346
347
348
@receiver(pre_save, sender=Entity, dispatch_uid='entity_pre_save')
349
def entity_pre_save(sender, instance, **kwargs):
350
    # if refetch and instance.file_url:
351
    #    slug = slugify(unicode(instance.name))[:200]
352
    #    instance.fetch_metadata_file(slug)
353
    #    instance.process_metadata()
354
    pass
355