[abac] Adding of the attribute profile management.

All attributes are stored in an object profile used at the treatement of
    the ABAC permissions.

    Some of these attributes may be used in more that a single single user
    session.

    Then, a profile is recorded for each user. At the beginning of the
    permission checking, the profile is loaded or created.

    If it is loaded, all outdated attributes are removed.

    The function to load dictionnary of attributes handle the expiration
    date if it is ISO 8601 formatted:

        attr = {}
        attr['name'] = 'certificate_type'
        attr['namespace'] = 'ACS-ABAC'
        attr['values'] = ('eID',)
        attr['expiration_date'] = 2011-09-05T11:48:24
This commit is contained in:
Mikaël Ates 2011-09-05 12:17:27 +02:00
parent 1dbc34accf
commit 8ff4a7d42a
1 changed files with 261 additions and 61 deletions

View File

@ -19,6 +19,8 @@
import re
import logging
import datetime
import time
from django.db import transaction
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
@ -59,24 +61,23 @@ def check_predicate(predicate, profile):
return False
def check_predicate_comparison(predicate, profile):
'''
Grab definition of the predicate
Look in profile for a corresponding AssertionData
Then compare values
Every assertionData in the profile with a definition and a list of
sources are candidates for redicate checking.
An AssertionDefinition may indicate multiple sources
Then multiple AssertionData may correspond. It means that any of them
may satisfy the predicate
This match assertions provided by multiple source or even multiple
from the same source.
Here, we look for every assertionData in the profile
for a definition and a source
We accept provided by multiple source or even multiple from the same
source
One of them is enough to satisfy the comparison
!!! One of them is enough to satisfy the comparison. !!!
In other words the multivalue management is handled only for
multivalues of the assertion, not between multiple assertions
See function below to see how multi valued attributes are treated
'''
def check_predicate_comparison(predicate, profile):
#operand1
l_data1 = []
data1 = predicate.operand1.get_assertion_instance()
@ -86,9 +87,9 @@ def check_predicate_comparison(predicate, profile):
return False
definition = data1.attribute_definition
for source in sources:
ads = AssertionData.objects.filter(profile=profile, source=source.source)
for ad in ads:
if ad.attribute_data.definition.id == definition.id:
for ad in profile.assertions.all():
if ad.source.id == source.source.id \
and ad.attribute_data.definition.id == definition.id:
l_data1.append(ad.attribute_data)
if not l_data1:
return False
@ -103,9 +104,9 @@ def check_predicate_comparison(predicate, profile):
return False
definition = data2.attribute_definition
for source in sources:
ads = AssertionData.objects.filter(profile=profile, source=source.source)
for ad in ads:
if ad.attribute_data.definition.id == definition.id:
for ad in profile.assertions.all():
if ad.source.id == source.source.id \
and ad.attribute_data.definition.id == definition.id:
l_data2.append(ad.attribute_data)
if not l_data2:
return False
@ -470,19 +471,19 @@ def check_predicate_required(predicate, profile):
if not predicate.single_value:
logger.debug("check_predicate_required: single value not required")
for source in sources:
ads = AssertionData.objects.filter(profile=profile, source=source.source)
for ad in ads:
if ad.attribute_data.definition.id == definition.id:
for ad in profile.assertions.all():
if ad.source.id == source.source.id \
and ad.attribute_data.definition.id == definition.id:
logger.debug("check_predicate_required: satisfied")
return True
else:
logger.debug("check_predicate_required: single value required")
found = False
for source in sources:
ads = AssertionData.objects.filter(profile=profile, source=source.source)
c = 0
for ad in ads:
if ad.attribute_data.definition.id == definition.id:
for ad in profile.assertions.all():
if ad.source.id == source.source.id \
and ad.attribute_data.definition.id == definition.id:
c = c + 1
if c > 1:
logger.debug("check_predicate_required: multiple values found \
@ -564,47 +565,178 @@ def get_def_from_name_and_ns(name, namespace):
return None
def add_assertion_to_profile(profile, source, definition, values):
#@transaction.commit_manually
def add_assertion_to_profile(profile, source, definition, values,
expiration_date=None):
'''
values is a list of strings
converted to the right data type according to the datatype given by the
definition
Values is a list of strings converted to the data type given by the
definition datatype
'''
data = None
if definition.attribute_type == ACS_XACML_DATATYPE_STRING:
data = AttributeData(definition=definition)
data.save()
for value in values:
StringM(data=data, value=value).save()
elif definition.attribute_type == ACS_XACML_DATATYPE_BOOLEAN:
return None
elif definition.attribute_type == ACS_XACML_DATATYPE_INTEGER:
data = AttributeData(definition=definition)
data.save()
for value in values:
v = int(value)
IntegerM(data=data, value=v).save()
elif definition.attribute_type == ACS_XACML_DATATYPE_DOUBLE:
return None
elif definition.attribute_type == ACS_XACML_DATATYPE_TIME:
return None
elif definition.attribute_type == ACS_XACML_DATATYPE_DATE:
return None
elif definition.attribute_type == ACS_XACML_DATATYPE_DATETIME:
return None
elif definition.attribute_type == ACS_XACML_DATATYPE_RFC822NAME:
return None
elif definition.attribute_type == ACS_XACML_DATATYPE_IPADDRESS:
a = None
logger.debug('add_assertion_to_profile: Add assertion to profile %s \
- (%s, %s, %s)' % (profile, source, definition, str(values)))
try:
data = None
try:
data = AttributeData(definition=definition)
data.save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to add the assertion \
data due to %s' % err)
raise err
if definition.attribute_type == ACS_XACML_DATATYPE_STRING:
for value in values:
try:
StringM(data=data, value=value).save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to add value \
%s due to %s' % (value, err))
elif definition.attribute_type == ACS_XACML_DATATYPE_BOOLEAN:
for value in values:
try:
v = False
if value in ('True', 'true', 'Vrai', 'vrai'):
v = True
BooleanM(data=data, value=v).save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to convert in \
boolean or add value %sdue to %s' % (value, err))
elif definition.attribute_type == ACS_XACML_DATATYPE_INTEGER:
for value in values:
try:
v = int(value)
IntegerM(data=data, value=v).save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to convert in \
integer or add value %s due to %s' % (value, err))
elif definition.attribute_type == ACS_XACML_DATATYPE_DOUBLE:
for value in values:
try:
v = float(value)
IntegerM(data=data, value=v).save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to convert in \
double or add value %sdue to %s' % (value, err))
elif definition.attribute_type == ACS_XACML_DATATYPE_TIME:
for value in values:
try:
v = time.strptime(value,"%h:%m:%s") #12:15:00
TimeM(data=data, value=v).save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to convert in \
time or add value %sdue to %s' % (value, err))
elif definition.attribute_type == ACS_XACML_DATATYPE_DATE:
for value in values:
try:
v = time.strptime(value,"%d/%b/%Y") #28/01/1982
DateM(data=data, value=v).save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to convert in \
date or add value %sdue to %s' % (value, err))
elif definition.attribute_type == ACS_XACML_DATATYPE_DATETIME:
for value in values:
try:
v = time.strptime(value,"%d/%b/%Y-%h:%m:%s") #28/01/1982-03:00:00
DateTimeM(data=data, value=v).save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to convert in \
datetime or add value %sdue to %s' % (value, err))
elif definition.attribute_type == ACS_XACML_DATATYPE_RFC822NAME:
for value in values:
try:
v = None
r = re.compile('[a-zA-Z0-9+_\-\.]+@[0-9a-zA-Z][.-0-9a-zA-Z]*.[a-zA-Z]+') # email
if r.search(value):
v = value
else:
logger.error('add_assertion_to_profile: Unable to convert in \
rfc822name or add value %sdue to %s' % (value, err))
Rfc822NameM(data=data, value=v).save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to convert in \
rfc822name or add value %sdue to %s' % (value, err))
elif definition.attribute_type == ACS_XACML_DATATYPE_IPADDRESS:
for value in values:
try:
v = None
r = re.compile('(?:[\d]{1,3})\.(?:[\d]{1,3})\.(?:[\d]{1,3})\.(?:[\d]{1,3})') # x.x.x.x
if r.search(value):
v = value
else:
logger.error('add_assertion_to_profile: Unable to convert in \
ipaddress or add value %sdue to %s' % (value, err))
IpAddressM(data=data, value=v).save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to convert in \
ipaddress or add value %sdue to %s' % (value, err))
else:
logger.error('add_assertion_to_profile: Unknown definition data type \
%s' % definition.attribute_type)
raise Exception()
if expiration_date:
#expiration_date = time.strptime(expiration_date,"%d/%b/%Y-%h:%m:%s") #28/01/1982-03:00:00
logger.debug('add_assertion_to_profile: expiration date %s found \
- conversion' % expiration_date)
try:
expiration_date = iso8601_to_datetime(expiration_date)
except:
logger.error('add_assertion_to_profile: Conversion failed - \
the date is not ISO8601')
expiration_date = None
'''
Possibilities if the profile contains an assertion with the same
definition and the same source:
- append values if expiration_date are the same
- remove and the new assertion
- add a new assertion withtou checking
The third choices is made.
Then the profile may contain multiple assertion for a same
definition and source.
!!! It is enough that one of them satisfies the predicate. !!!
cf. functions check_predicate_comparison and
check_predicate_required
If it is not the behaviour expected, reduce expiration date.
'''
try:
a = AssertionData(source=source, attribute_data=data,
expiration_date=expiration_date)
a.save()
profile.assertions.add(a)
profile.save()
except Exception, err:
logger.error('add_assertion_to_profile: Unable to add the assertion \
data due to %s' % err)
raise err
except Exception, err:
# transaction.rollback()
return None
else:
return None
try:
a = AssertionData(profile=profile, source=source, attribute_data=data)
a.save()
# transaction.commit()
logger.debug('add_assertion_to_profile: everything successfully \
performed- %s added' % a)
return a
except:
return None
def iso8601_to_datetime(date_string):
'''
Convert a string formatted as an ISO8601 date into a time_t value.
This function ignores the sub-second resolution.
'''
m = re.match(r'(\d+-\d+-\d+T\d+:\d+:\d+)(?:\.\d+)?Z?$', date_string)
logger.debug('add_assertion_to_profile: m %s' % str(m))
if not m:
raise ValueError('Invalid ISO8601 date')
tm = time.strptime(m.group(1)+'Z', "%Y-%m-%dT%H:%M:%SZ")
logger.debug('add_assertion_to_profile: tm %s' % str(tm))
return datetime.datetime.fromtimestamp(time.mktime(tm))
def arrange_missing_predicates(missing_predicates, profile):
@ -706,6 +838,62 @@ def get_all_sources():
return None
@transaction.commit_manually
def load_or_create_user_profile(user, no_cleanup=False):
'''
If the user has a profile, remove assertions outdated and return
profile, else create a new one.
If cleanup: expiration_date < now() remove assertion data from profile
If no_cleanup: return profile if any without removing outdated
assertions
'''
profile = None
try:
if user:
try:
logger.info('load_or_create_user_profile: search profile for %s' \
% user)
profile = UserAttributeProfile.objects.get(user=user)
except ObjectDoesNotExist:
profile = UserAttributeProfile(user=user)
profile.save()
logger.info('load_or_create_user_profile: profile with id %s for \
user %s created' % (str(profile.id), user))
else:
if no_cleanup:
logger.info('load_or_create_user_profile: Existing user \
profile %s for %s - No cleanup' % (profile, user))
else:
for ad in profile.assertions.all():
if not ad.expiration_date \
or ad.expiration_date < datetime.datetime.now():
logger.debug('load_or_create_user_profile: \
Remove outdated assertion %s of profile %s' \
% (ad, profile))
ad.delete()
else:
logger.debug('load_or_create_user_profile: \
%s kept in profile' \
% ad)
elif not user:
profile = UserAttributeProfile()
profile.save()
logger.info('load_or_create_user_profile: profile with id %s for \
an anonymous user created' % str(profile.id))
except Exception, err:
transaction.rollback()
logger.error('load_or_create_user_profile: An error occured %s' \
% str(err))
return None
else:
transaction.commit()
logger.debug('load_or_create_user_profile: everything successfully \
performed')
return profile
def load_profile_by_dic(profile, dic):
if not profile or not dic:
logger.error('load_profile_by_dic: \
@ -721,6 +909,11 @@ def load_profile_by_dic(profile, dic):
for attr in dic[source]:
logger.debug('load_profile_by_dic: attribute %s of %s with values %s' \
% (attr['name'], attr['namespace'], str([x for x in attr['values']])))
expiration_date = None
if 'expiration_date' in attr:
logger.debug('load_profile_by_dic: expire at %s' \
% attr['expiration_date'])
expiration_date = attr['expiration_date']
d = get_def_from_name_and_ns(attr['name'], attr['namespace'])
if not d:
logger.error('load_profile_by_dic: \
@ -729,7 +922,14 @@ def load_profile_by_dic(profile, dic):
else:
logger.debug('load_profile_by_dic: \
definition %s found' % d)
add_assertion_to_profile(profile, s, d, attr['values'])
a = add_assertion_to_profile(profile, s, d, attr['values'],
expiration_date=expiration_date)
if not a:
logger.debug('load_profile_by_dic: \
error adding assertion')
else:
logger.debug('load_profile_by_dic: \
assertion %s added' % a)
else:
logger.critical('load_profile_by_dic: \
The source with name %s and attributes %s is unknown of the system'