Added permission management into metaeducation (instead of it being in renkanmanager) + more logging around auth code + corrected client.py for oauth server so it sends the correct content-type when using client credentials
authordurandn
Mon, 11 Apr 2016 16:47:09 +0200
changeset 63 6bfac7c633a0
parent 62 2d3dd0824e8c
child 64 8745c7bcf096
Added permission management into metaeducation (instead of it being in renkanmanager) + more logging around auth code + corrected client.py for oauth server so it sends the correct content-type when using client credentials
.settings/org.eclipse.core.resources.prefs
oauth/client.py
server/src/metaeducation/middleware.py
server/src/metaeducation/migrations/0003_renkan_permissions.py
server/src/metaeducation/mtdc_oauth_provider/views.py
server/src/metaeducation/permissions.py
server/src/metaeducation/settings/__init__.py
server/src/metaeducation/signals.py
--- a/.settings/org.eclipse.core.resources.prefs	Thu Apr 21 16:26:30 2016 +0200
+++ b/.settings/org.eclipse.core.resources.prefs	Mon Apr 11 16:47:09 2016 +0200
@@ -1,3 +1,6 @@
 eclipse.preferences.version=1
 encoding//oauth/oauth.py=utf-8
+encoding//server/src/metaeducation/migrations/0001_initial.py=utf-8
+encoding//server/src/metaeducation/migrations/0002_user_uai.py=utf-8
+encoding//server/src/metaeducation/migrations/0003_renkan_permissions.py=utf-8
 encoding//server/src/metaeducation/settings/dev.py=utf-8
--- a/oauth/client.py	Thu Apr 21 16:26:30 2016 +0200
+++ b/oauth/client.py	Mon Apr 11 16:47:09 2016 +0200
@@ -60,13 +60,14 @@
     if 'remote_oauth_clientcredentials' in session:
         resp = requests.post(
             app.config["CREATE_RENKAN_ENDPOINT"]+"?act_as="+str(session.get("me_id", "anonymous")), 
-            {"title": "RENKAN_FROM_GED"},
+            json.dumps({"title": "RENKAN_FROM_GED"}),
             headers={
                 'Authorization': 'Bearer %s' % session['remote_oauth_clientcredentials'][0],
-                'renkan-act-as': session.get("me_id", "anonymous")
+                'renkan-act-as': session.get("me_id", "anonymous"),
+                'content-type': "application/json"
             }
         )
-        print(resp.text)
+        print("%r : %r" %(resp.status_code, json.loads(resp.text)))
     return redirect('/')
 
 @app.route('/authorized')
--- a/server/src/metaeducation/middleware.py	Thu Apr 21 16:26:30 2016 +0200
+++ b/server/src/metaeducation/middleware.py	Mon Apr 11 16:47:09 2016 +0200
@@ -11,19 +11,29 @@
 if hasattr(settings, 'OAUTH_EXEMPT_URLS'):
     EXEMPT_URLS += [compile(expr) for expr in settings.OAUTH_EXEMPT_URLS]
 
-class MtdcLoginRequiredWithContextMiddleware:
+class MtdcLoginRequiredWithContextMiddleware(object):
     """
     Middleware intended to emulate login_required decorator so we can forward the context query arg
     """
     def process_request(self, request):
+        logger.debug("REQUEST: %r ", request.user)
+        logger.debug("REQUEST: Session - %r", request.session.get("OAUTH_CONTEXT_BASE_URL", "No OAuth Context in session"))
+        cached_user = getattr(request, "_cached_user", "None")
+        logger.debug("REQUEST: Cached user - %r", cached_user)
         if not request.user.is_authenticated():
             path = request.path_info.lstrip('/')
             if not any(m.match(path) for m in EXEMPT_URLS):
                 logger.debug("LOGIN_REQUIRED: User is not logged and this request triggered Oauth redirects")
-                if request.GET.get("context", ""):
-                    context = request.GET["context"]
+                if request.GET.get("context", "") and not request.session.get("OAUTH_CONTEXT_BASE_URL", ""):
+                    logger.debug("LOGIN_REQUIRED: no context in session, storing context")
+                    logger.debug("LOGIN_REQUIRED: context queryarg is %r", request.GET.get("context", None))
+                    request.session["OAUTH_CONTEXT_BASE_URL"] = request.GET["context"]
+                context = request.session.get("OAUTH_CONTEXT_BASE_URL", "")
+                if context:
                     response = redirect(settings.LOGIN_URL)
                     logger.debug("LOGIN_REQUIRED: will redirect to %r", settings.LOGIN_URL)
                     logger.debug("LOGIN_REQUIRED: query args will be %r", {"context": context, "next": settings.BASE_URL+path})
                     response["LOCATION"] += "?"+urlencode({"context": context, "next": settings.BASE_URL+path})
                     return response
+                else:
+                    logger.debug("LOGIN REQUIRED: User not authenticated, no context in session or queryarg, aborting")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/metaeducation/migrations/0003_renkan_permissions.py	Mon Apr 11 16:47:09 2016 +0200
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.1 on 2016-04-11 13:24
+from __future__ import unicode_literals
+
+from django.db import migrations
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Permission
+
+def fix_renkan_permissions(apps, schema_editor):
+    User = get_user_model()
+    for user in User.objects.all():
+        for perm in ["view_renkan", "add_renkan", "change_renkan", "delete_renkan", "add_revision", "delete_revision"]:
+            if not user.has_perm(perm):
+                perm_obj = Permission.objects.get(codename=perm)
+                user.user_permissions.add(perm_obj)
+        user.save()
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('metaeducation', '0002_user_uai'),
+    ]
+
+    operations = [
+        migrations.RunPython(fix_renkan_permissions)
+    ]
--- a/server/src/metaeducation/mtdc_oauth_provider/views.py	Thu Apr 21 16:26:30 2016 +0200
+++ b/server/src/metaeducation/mtdc_oauth_provider/views.py	Mon Apr 11 16:47:09 2016 +0200
@@ -57,7 +57,7 @@
             logger.debug("AUTHORIZATION CODE AUTH: login almost complete, checking if user %r exists", sociallogin.account.extra_data.get('external_id', 'NO_ID'))
             user = get_user_model().objects.get(external_id=sociallogin.account.extra_data.get('external_id', ''))  # if user exists, connect the account to the existing account and login
             logger.debug("AUTHORIZATION CODE AUTH: user %r exists, connecting to existing account", sociallogin.account.extra_data.get('external_id', 'NO_ID'))
-            sociallogin.state['process'] = 'connect'             
+            sociallogin.state['process'] = 'connect'            
             perform_login(request, user, 'none')
         except get_user_model().DoesNotExist:
             logger.debug("AUTHORIZATION CODE AUTH: user %r does not exist", sociallogin.account.extra_data.get('external_id', 'NO_ID'))
@@ -66,7 +66,7 @@
         return super(MtdcOAuth2Adapter, self).get_login_redirect_url(self, request)
     
     def new_user(self, request, sociallogin):
-        if 'username' in sociallogin.account.extra_data:
+        if 'external_id' in sociallogin.account.extra_data:
             logger.debug("AUTHORIZATION CODE AUTH: checking if user %r exists to populate sociallogin", sociallogin.account.extra_data.get('external_id', 'NO_ID'))
             user_queryset = get_user_model().objects.filter(external_id=sociallogin.account.extra_data.get('external_id', ''), username=sociallogin.account.extra_data['username'])
             if user_queryset.exists():
@@ -80,7 +80,7 @@
                 user.uai = sociallogin.account.extra_data.get('uai', '')
             return user
         else:
-            logger.debug("AUTHORIZATION CODE AUTH: no username in extra data")
+            logger.debug("AUTHORIZATION CODE AUTH: no external_id in extra data")
             return get_user_model()()
         
     def populate_user(self,
@@ -91,8 +91,11 @@
         user = sociallogin.user
         user.username = username
         user.save()
+        view_permission = Permission.objects.get(codename="view_renkan")
         add_permission = Permission.objects.get(codename="add_renkan")
-        user.user_permissions.add(add_permission)
+        change_permission = Permission.objects.get(codename="change_renkan")
+        delete_permission = Permission.objects.get(codename="delete_renkan")
+        user.user_permissions.add(view_permission, add_permission, change_permission, delete_permission)
         return user
     
     def complete_login(self, request, app, token, **kwargs):
@@ -102,8 +105,6 @@
         extra_data = resp.json()
         
         logger.debug("AUTHORIZATION CODE AUTH: response extra_data: %r ", extra_data)
-        if request.session.get("OAUTH_CONTEXT_BASE_URL", None) is not None:
-            del request.session["OAUTH_CONTEXT_BASE_URL"]
         return self.get_provider().sociallogin_from_response(request,
                                                              extra_data)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/metaeducation/permissions.py	Mon Apr 11 16:47:09 2016 +0200
@@ -0,0 +1,15 @@
+from rest_framework import permissions
+
+class MtdcObjectPermissions(permissions.DjangoObjectPermissions):
+    """
+    Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
+    """
+    perms_map = {
+        'GET': ['%(app_label)s.view_%(model_name)s'],
+        'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
+        'HEAD': ['%(app_label)s.view_%(model_name)s'],
+        'POST': ['%(app_label)s.add_%(model_name)s'],
+        'PUT': ['%(app_label)s.change_%(model_name)s'],
+        'PATCH': ['%(app_label)s.change_%(model_name)s'],
+        'DELETE': ['%(app_label)s.delete_%(model_name)s'],
+    }
\ No newline at end of file
--- a/server/src/metaeducation/settings/__init__.py	Thu Apr 21 16:26:30 2016 +0200
+++ b/server/src/metaeducation/settings/__init__.py	Mon Apr 11 16:47:09 2016 +0200
@@ -52,7 +52,15 @@
         'metaeducation.auth.MtdcOAuth2ClientCredentialsAuthentication',
         'rest_framework.authentication.BasicAuthentication',
         'renkanmanager.auth.CsrfExemptSessionAuthentication'
-    )
+    ),
+    'DEFAULT_PERMISSION_CLASSES': (
+        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
+        'metaeducation.permissions.MtdcObjectPermissions',
+    ),
+    'DEFAULT_PARSER_CLASSES': (
+        'rest_framework.parsers.JSONParser',
+    ),               
+    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'
 }
 
 MIDDLEWARE_CLASSES = (
@@ -119,6 +127,7 @@
 DEFAULT_RENKAN_ICON = ""
 RENKAN_USER_DISPLAY_FIELD = 'external_id'
 
+SOCIALACCOUNT_STORE_TOKENS = False
 SOCIALACCOUNT_ADAPTER = "metaeducation.mtdc_oauth_provider.views.MtdcOAuth2Adapter"
 SOCIALACCOUNT_PROVIDERS = {
     'mtdc': {
--- a/server/src/metaeducation/signals.py	Thu Apr 21 16:26:30 2016 +0200
+++ b/server/src/metaeducation/signals.py	Mon Apr 11 16:47:09 2016 +0200
@@ -1,9 +1,13 @@
+import requests, json, sys, logging
+
+from django.conf import settings
+from django.contrib.auth import get_user_model
+from django.contrib.auth.signals import user_logged_in, user_logged_out
+from django.core.urlresolvers import reverse
 from django.db.models.signals import post_save
 from django.dispatch import receiver
-from django.contrib.auth import get_user_model
-from django.conf import settings
-from django.core.urlresolvers import reverse
-import requests, json, sys, logging
+from guardian.shortcuts import assign_perm
+
 
 logger = logging.getLogger(__name__)
 
@@ -45,6 +49,41 @@
             )
             logger.debug("REFERENCING RENKAN: response is %r", reference_response.status_code)
 
+def assign_permission(sender, instance, created, **kwargs):
+    from renkanmanager.models import Renkan, Revision, Workspace
+    if sender == Renkan and created:
+        creator = instance.creator
+        logger.debug("SIGNALS: RENKAN WAS CREATED, ASSIGNING PERMS TO USER %r FOR RENKAN %r", instance.creator, instance.renkan_guid)
+        assign_perm('renkanmanager.view_renkan', creator, instance)
+        assign_perm('renkanamanger.change_renkan', creator, instance)
+        assign_perm('renkanmanager.delete_renkan', creator, instance)
+    if sender == Revision and created:
+        creator = instance.creator
+        logger.debug("SIGNALS: REVISION WAS CREATED, ASSIGNING PERMS TO USER %r", instance.creator)
+        assign_perm('renkanmanager.view_revision', creator, instance)
+        assign_perm('renkanmanager.delete_revision', creator, instance)
+    if sender == Workspace and created:
+        creator = instance.creator
+        logger.debug("SIGNALS: WORKSPACE WAS CREATED, ASSIGNING PERMS TO USER %r", instance.creator)
+        assign_perm('renkanmanager.view_workspace', creator, instance)
+        assign_perm('renkanmanager.change_workspace', creator, instance)
+        assign_perm('renkanmanager.delete_workspace', creator, instance)
 
+def log_user_logged_in(sender, request, user, **kwargs):
+    logger.debug("LOGGING IN: user logged in: %r", user)
+
+def log_user_logged_out(sender, request, user, **kwargs):
+    logger.debug("LOGGING OUT: DJANGO: user logged out: %r", user)
+
+def token_updated(sender, instance, created, **kwargs):
+    from allauth.socialaccount.models import SocialToken
+    if sender == SocialToken:
+        logger.debug("TOKEN: SocialToken saved")
+        logger.debug("TOKEN: Token created: %r", created)
+    
 if not 'test' in sys.argv:
     post_save.connect(reference_created_renkan)
+post_save.connect(token_updated)
+post_save.connect(assign_permission)
+user_logged_in.connect(log_user_logged_in)
+user_logged_out.connect(log_user_logged_out)
\ No newline at end of file