Gitlab CSE Unil

Commit f05f6ae9 authored by Julien Furrer's avatar Julien Furrer
Browse files

Updated adim.permission module, PERMISSION_CLASSES use instances instead of Class

The TTP permission classes are bulit from settings, no need for hardcoding.
parent 8a856559
...@@ -6,6 +6,7 @@ from django.http.response import HttpResponseRedirect ...@@ -6,6 +6,7 @@ from django.http.response import HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
from django.core.cache import cache from django.core.cache import cache
import sys import sys
import logging
from .models import AnObjMembership from .models import AnObjMembership
...@@ -13,10 +14,11 @@ SHARING_MODE_NONE = 0 ...@@ -13,10 +14,11 @@ SHARING_MODE_NONE = 0
SHARING_MODE_MANUAL = 1 SHARING_MODE_MANUAL = 1
SHARING_MODE_REGKEY = 4 SHARING_MODE_REGKEY = 4
SHARING_MODE_AAIRULES = 8 SHARING_MODE_AAIRULES = 8
SHARING_MODE_TTP_MOODLE = 16 # SHARING_MODE_TTP_MOODLE = 16
SHARING_MODE_TTP_TOTO = 24 # SHARING_MODE_TTP_TOTO = 24
SHARING_MODE_TTP_MY_TTP = 32 # SHARING_MODE_TTP_MY_TTP = 32
logger = logging.getLogger(__name__)
class PermissionClass(object): class PermissionClass(object):
""" """
...@@ -28,32 +30,26 @@ class PermissionClass(object): ...@@ -28,32 +30,26 @@ class PermissionClass(object):
has_interactive_registration = False has_interactive_registration = False
ttp = False ttp = False
@classmethod def get_interactive_registration_response(self, request, anobj):
def get_interactive_registration_response(cls, request, anobj):
raise NotImplementedError() raise NotImplementedError()
@classmethod def _register_user(self, user, anobj):
def _register_user(cls, user, anobj):
if not anobj.locked: if not anobj.locked:
AnObjMembership.objects.create(anobj=anobj, user=user) AnObjMembership.objects.create(anobj=anobj, user=user)
# anobj.members.add(user) # anobj.members.add(user)
@classmethod def _revoke_user(self, user, anobj):
def _revoke_user(cls, user, anobj):
if not anobj.locked: if not anobj.locked:
AnObjMembership.objects.filter(anobj=anobj, user=user).delete() AnObjMembership.objects.filter(anobj=anobj, user=user).delete()
# anobj.members.remove(user) # anobj.members.remove(user)
@classmethod def check_registration(self, request, anobj):
def check_registration(cls, request, anobj):
raise NotImplementedError() raise NotImplementedError()
@classmethod def check_revocation(self, request, anobj):
def check_revocation(cls, request, anobj):
raise NotImplementedError() raise NotImplementedError()
@classmethod def check_permission(self, request, anobj):
def check_permission(cls, request, anobj):
user = request.user user = request.user
# Anonymous users have no permissions # Anonymous users have no permissions
if user.is_anonymous(): if user.is_anonymous():
...@@ -69,19 +65,18 @@ class PermissionClass(object): ...@@ -69,19 +65,18 @@ class PermissionClass(object):
if user in members: if user in members:
if not anobj.locked: if not anobj.locked:
# Check revocation only if anobj is not locked # Check revocation only if anobj is not locked
cls.check_revocation(request, anobj) self.check_revocation(request, anobj)
else: else:
if anobj.locked: if anobj.locked:
# If anobj is locked never try to register # If anobj is locked never try to register
raise PermissionDenied() raise PermissionDenied()
cls.check_registration(request, anobj) self.check_registration(request, anobj)
return True return True
@classmethod def has_permission(self, request, anobj):
def has_permission(cls, request, anobj):
try: try:
return cls.check_permission(request, anobj) return self.check_permission(request, anobj)
except PermissionDenied: except PermissionDenied:
return False return False
...@@ -94,8 +89,7 @@ class IsMember(PermissionClass): ...@@ -94,8 +89,7 @@ class IsMember(PermissionClass):
no auto revocation -> check_revocation always pass no auto revocation -> check_revocation always pass
""" """
@classmethod def check_registration(self, request, anobj):
def check_registration(cls, request, anobj):
""" """
Check if the username is listed in the sharing options. Check if the username is listed in the sharing options.
This may occur if the username has been added as member but the This may occur if the username has been added as member but the
...@@ -107,7 +101,7 @@ class IsMember(PermissionClass): ...@@ -107,7 +101,7 @@ class IsMember(PermissionClass):
opts_members = anobj.sharing_opts.get('members', []) opts_members = anobj.sharing_opts.get('members', [])
if request.user.username in opts_members: if request.user.username in opts_members:
cls._register_user(request.user, anobj) self._register_user(request.user, anobj)
# The user is now in the anobj.members list, so we have to remove it # The user is now in the anobj.members list, so we have to remove it
# from the sharing_opts.members list # from the sharing_opts.members list
opts_members.remove(request.user.username) opts_members.remove(request.user.username)
...@@ -116,8 +110,7 @@ class IsMember(PermissionClass): ...@@ -116,8 +110,7 @@ class IsMember(PermissionClass):
else: else:
raise PermissionDenied() raise PermissionDenied()
@classmethod def check_revocation(self, request, anobj):
def check_revocation(cls, request, anobj):
return anobj return anobj
...@@ -126,20 +119,18 @@ class RegistrationKey(PermissionClass): ...@@ -126,20 +119,18 @@ class RegistrationKey(PermissionClass):
Register user if she knows the key defined in Register user if she knows the key defined in
sharing options of the anobj sharing options of the anobj
""" """
@classmethod def check_registration(self, request, anobj):
def check_registration(cls, request, anobj):
reg_key = request.POST.get('k') reg_key = request.POST.get('k')
# We need at least a key and some sharing options # We need at least a key and some sharing options
if reg_key is None or not anobj.sharing_opts: if reg_key is None or not anobj.sharing_opts:
raise PermissionDenied() raise PermissionDenied()
if anobj.sharing_opts.get('key') == reg_key: if anobj.sharing_opts.get('key') == reg_key:
cls._register_user(request.user, anobj) self._register_user(request.user, anobj)
else: else:
raise PermissionDenied() raise PermissionDenied()
@classmethod def check_revocation(self, request, anobj):
def check_revocation(cls, request, anobj):
""" """
noop, there is no revocation possible here noop, there is no revocation possible here
""" """
...@@ -147,8 +138,7 @@ class RegistrationKey(PermissionClass): ...@@ -147,8 +138,7 @@ class RegistrationKey(PermissionClass):
has_interactive_registration = True has_interactive_registration = True
@classmethod def get_interactive_registration_response(self, request, anobj):
def get_interactive_registration_response(cls, request, anobj):
errors = [] errors = []
if request.POST.get('k') is not None: if request.POST.get('k') is not None:
errors.append({'code': "invalid_key"}) errors.append({'code': "invalid_key"})
...@@ -169,63 +159,57 @@ class AAIRules(PermissionClass): ...@@ -169,63 +159,57 @@ class AAIRules(PermissionClass):
class ATTP(PermissionClass): class ATTP(PermissionClass):
has_interactive_registration = True has_interactive_registration = True
ttp = True ttp = True
ttp_id = 'moodle' ttp_id = None
@classmethod def __init__(self, ttp_id=None):
def set_attp_status(cls, request, anobj, status): self.ttp_id = ttp_id
def set_attp_status(self, request, anobj, status):
key = "attp_{ttp_id}{user_id}{uuid}".format( key = "attp_{ttp_id}{user_id}{uuid}".format(
ttp_id=cls.ttp_id, user_id=request.user.id, ttp_id=self.ttp_id, user_id=request.user.id,
uuid=anobj.uuid[:12] uuid=anobj.uuid[:12]
) )
cache.set(key, status, settings.ATTP['OPTIONS']['CACHE_TIMEOUT']) cache.set(key, status, settings.ATTP['OPTIONS']['CACHE_TIMEOUT'])
return status return status
@classmethod def get_attp_status(self, request, anobj):
def get_attp_status(cls, request, anobj):
key = "attp_{ttp_id}{user_id}{uuid}".format( key = "attp_{ttp_id}{user_id}{uuid}".format(
ttp_id=cls.ttp_id, user_id=request.user.id, ttp_id=self.ttp_id, user_id=request.user.id,
uuid=anobj.uuid[:12] uuid=anobj.uuid[:12]
) )
status = cache.get(key) status = cache.get(key)
logger.debug("ATTP status from cache: {}".format(status is not None))
return status return status
@classmethod def clear_attp_status(self, request, anobj):
def clear_attp_status(cls, request, anobj):
# key = "attp_{ttp_id}{user_id}{uuid}".format(
# ttp_id=cls.ttp_id, user_id=request.user.id,
# uuid=anobj.uuid[:12]
# )
session_key = "anobj_{}".format(anobj.uuid[:12]) session_key = "anobj_{}".format(anobj.uuid[:12])
try: try:
del(request.session[session_key]) del(request.session[session_key])
except KeyError: except KeyError:
pass pass
@classmethod def check_registration(self, request, anobj):
def check_registration(cls, request, anobj):
pass pass
@classmethod def check_revocation(self, request, anobj):
def check_revocation(cls, request, anobj):
pass pass
@classmethod def check_permission(self, request, anobj):
def check_permission(cls, request, anobj): perm_status = self.get_attp_status(request, anobj)
perm_status = cls.get_attp_status(request, anobj)
if perm_status is None: if perm_status is None:
raise PermissionDenied() raise PermissionDenied()
elif perm_status == 'denied': elif perm_status == 'denied':
if not anobj.locked: if not anobj.locked:
# Revoke only if anobj is not locked # Revoke only if anobj is not locked
cls._revoke_user(request.user, anobj) self._revoke_user(request.user, anobj)
cls.clear_attp_status(request, anobj) self.clear_attp_status(request, anobj)
raise PermissionDenied() raise PermissionDenied()
else: else:
if request.user not in anobj.members.all(): if request.user not in anobj.members.all():
cls._register_user(request.user, anobj) self._register_user(request.user, anobj)
# Check ownership # Check ownership
owners = anobj.owners.all() owners = anobj.owners.all()
...@@ -238,41 +222,34 @@ class ATTP(PermissionClass): ...@@ -238,41 +222,34 @@ class ATTP(PermissionClass):
# is not owner, but should -> add # is not owner, but should -> add
anobj.owners.add(request.user) anobj.owners.add(request.user)
# cls.clear_attp_status(request, anobj)
return True return True
@classmethod def get_interactive_registration_response(self, request, anobj):
def get_interactive_registration_response(cls, request, anobj): if self.get_attp_status(request, anobj) is None:
if cls.get_attp_status(request, anobj) is None: check_url = settings.ATTP.get(self.ttp_id, {}).get('CHECK_URL')
check_url = settings.ATTP.get(cls.ttp_id, {}).get('CHECK_URL')
return HttpResponseRedirect(check_url.format(uuid=anobj.uuid)) return HttpResponseRedirect(check_url.format(uuid=anobj.uuid))
else: else:
cls.clear_attp_status(request, anobj) self.clear_attp_status(request, anobj)
raise PermissionDenied() raise PermissionDenied()
class MoodleTTP(ATTP):
ttp_id = 'moodle'
class TotoTTP(ATTP):
ttp_id = 'toto'
class MyTTP(ATTP):
ttp_id = 'my_ttp'
PERMISSION_CLASSES = { PERMISSION_CLASSES = {
SHARING_MODE_NONE: None, SHARING_MODE_NONE: None,
SHARING_MODE_MANUAL: IsMember, SHARING_MODE_MANUAL: IsMember(),
SHARING_MODE_REGKEY: RegistrationKey, SHARING_MODE_REGKEY: RegistrationKey(),
SHARING_MODE_AAIRULES: AAIRules, SHARING_MODE_AAIRULES: AAIRules(),
SHARING_MODE_TTP_MOODLE: MoodleTTP,
SHARING_MODE_TTP_TOTO: TotoTTP,
SHARING_MODE_TTP_MY_TTP: MyTTP
} }
# Register TTP services defined in the settings
for __ttp_id, __ttp_def in settings.ATTP.items():
if __ttp_id != 'OPTIONS':
attr_name = "SHARING_MODE_TTP_{}".format(__ttp_id.upper())
mode = getattr(sys.modules[__name__], attr_name, -1)
# Mode not defined in the module, create it from settings
if mode == -1:
setattr(sys.modules[__name__], attr_name, __ttp_def['MODE_ID'])
PERMISSION_CLASSES[__ttp_def['MODE_ID']] = ATTP(__ttp_id)
def get_permission_class(sharing_mode): def get_permission_class(sharing_mode):
return PERMISSION_CLASSES.get(sharing_mode) return PERMISSION_CLASSES.get(sharing_mode)
...@@ -285,7 +262,6 @@ def get_ttp_sharing_mode(ttp_id=''): ...@@ -285,7 +262,6 @@ def get_ttp_sharing_mode(ttp_id=''):
:return: :return:
""" """
attr_name = 'SHARING_MODE_TTP_{}'.format(ttp_id.upper()) attr_name = 'SHARING_MODE_TTP_{}'.format(ttp_id.upper())
print attr_name
mode = getattr(sys.modules[__name__], attr_name, 0) mode = getattr(sys.modules[__name__], attr_name, 0)
return mode return mode
......
...@@ -159,10 +159,10 @@ def annotate(request, anobj_uuid=None): ...@@ -159,10 +159,10 @@ def annotate(request, anobj_uuid=None):
return HttpResponseRedirect(reverse('adim_app:annotate', kwargs={'anobj_uuid': anobj.uuid})) return HttpResponseRedirect(reverse('adim_app:annotate', kwargs={'anobj_uuid': anobj.uuid}))
# ----- Login check. Not using decorator so we can delegate to Trusted Third Party if needed # ----- Login check. Not using decorator so we can delegate to Trusted Third Party if needed
permission_class = get_permission_class(anobj.sharing_mode) permission = get_permission_class(anobj.sharing_mode)
if request.user.is_anonymous(): if request.user.is_anonymous():
if permission_class and permission_class.ttp: if permission and permission.ttp:
check_url = settings.ATTP.get(permission_class.ttp_id, {}).get('CHECK_URL') check_url = settings.ATTP.get(permission.ttp_id, {}).get('CHECK_URL')
return HttpResponseRedirect(check_url.format(uuid=anobj.uuid)) return HttpResponseRedirect(check_url.format(uuid=anobj.uuid))
else: else:
return redirect_to_login(resolve_url('adim_app:annotate', anobj_uuid=anobj.uuid)) return redirect_to_login(resolve_url('adim_app:annotate', anobj_uuid=anobj.uuid))
...@@ -177,22 +177,22 @@ def annotate(request, anobj_uuid=None): ...@@ -177,22 +177,22 @@ def annotate(request, anobj_uuid=None):
# ----- Detailed check for permissions # ----- Detailed check for permissions
membership = None membership = None
if is_owner and not (permission_class and permission_class.ttp): # User is owner and anobj is not shared via Trusted Third Party
# User is owner and anobj is not shared via Trusted Third Party if is_owner and not (permission and permission.ttp):
if anobj.sharing_mode != SHARING_MODE_NONE: if anobj.sharing_mode != SHARING_MODE_NONE:
membership, _ = AnObjMembership.objects.get_or_create(anobj=anobj, user=request.user) membership, _ = AnObjMembership.objects.get_or_create(anobj=anobj, user=request.user)
# User is guest or owner and anobj shared via TTP
else: else:
# User is guest or owner and anobj shared via TTP if permission is None:
if permission_class is None:
# AnObj not shared # AnObj not shared
raise Http404() raise Http404()
# raise PermissionDenied() # raise PermissionDenied()
elif not permission_class.has_permission(request, anobj): elif not permission.has_permission(request, anobj):
# AnObj shared but user has no permission yet # AnObj shared but user has no permission yet
if permission_class.has_interactive_registration: if permission.has_interactive_registration:
# Interactive registration exists, call it # Interactive registration exists, call it
return permission_class.get_interactive_registration_response(request, anobj) return permission.get_interactive_registration_response(request, anobj)
# No interactive registration for this sharing model, deny access # No interactive registration for this sharing model, deny access
raise PermissionDenied() raise PermissionDenied()
...@@ -201,7 +201,7 @@ def annotate(request, anobj_uuid=None): ...@@ -201,7 +201,7 @@ def annotate(request, anobj_uuid=None):
pass pass
# TTP permission may have changed ownership # TTP permission may have changed ownership
if permission_class.ttp: if permission.ttp:
clear_function_cache(f='adim.models.annotablesis_owned', args=(anobj, request.user.id)) clear_function_cache(f='adim.models.annotablesis_owned', args=(anobj, request.user.id))
is_owner = anobj.is_owned(request.user.id) is_owner = anobj.is_owned(request.user.id)
......
...@@ -217,18 +217,35 @@ AAI = { ...@@ -217,18 +217,35 @@ AAI = {
} }
} }
# ATTP = {
# 'OPTIONS': {
# 'CACHE_TIMEOUT': <the time in sec the ttp authorization result is kept in cache>
# },
# '<ttp_service_id>': {
# 'CHECK_URL' : <the url for authorization on the ttp>
# 'MODE_ID': <the sharing_mode value for that ttp. this should be a unique int > 15
# }
# }
#
# CAUTION: when adding a ttp service, update the template (templates/adim/aom-modal.inc.html) with MODE_ID where needed
#
ATTP = { ATTP = {
'OPTIONS': { 'OPTIONS': {
'CACHE_TIMEOUT': 20, # 30, 'CACHE_TIMEOUT': 20, # 30,
}, },
'moodle': { 'moodle': {
'CHECK_URL': "http://localhost/tests/phpupload/gv.php?a={uuid}" 'CHECK_URL': "http://localhost/tests/phpupload/gv.php?a={uuid}",
'MODE_ID': 16
}, },
'toto': { 'toto': {
'CHECK_URL': "http://localhost/tests/phpupload/toto.php?a={uuid}" 'CHECK_URL': "http://localhost/tests/phpupload/toto.php?a={uuid}",
'MODE_ID': 24
}, },
'my_ttp': { 'my_ttp': {
'CHECK_URL': "http://my-ttp:8001/ttp/check/{uuid}/" 'CHECK_URL': "http://my-ttp:8001/ttp/check/{uuid}/",
'MODE_ID': 32
} }
} }
...@@ -309,6 +326,11 @@ LOGGING = { ...@@ -309,6 +326,11 @@ LOGGING = {
'level': 'WARNING', 'level': 'WARNING',
'propagate': True, 'propagate': True,
}, },
'adim.permissions': {
'handlers': ['console'],
'level': 'DEBUG', # 'NOTSET',
'propagate': True,
},
'adim_app': { 'adim_app': {
'handlers': ['console', 'file'], 'handlers': ['console', 'file'],
'level': 'DEBUG', 'level': 'DEBUG',
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment