Skip to content

Commit

Permalink
Switch authentication to Dreamjub
Browse files Browse the repository at this point in the history
Previously, authentication worked by transmitting the password to the
legacy OpenJUB API. Furthermore user data was also received from this
API. This caused problem, as Jay was able potentially able to intercept
user passwords and also depended on a legacy system.

This commit updates the authentication to use the new dreamjub api via
OAuth. Furthermore, it updates the permission system to move away from
UserProfiles and SuperAdmin models. Instead, this commit makes use of internal
Django mechanisms and stores user data directly with the model.

See also issue #26.
  • Loading branch information
kuboschek authored and tkw1536 committed Oct 14, 2017
1 parent 9194850 commit 541dfa2
Show file tree
Hide file tree
Showing 15 changed files with 433 additions and 468 deletions.
11 changes: 9 additions & 2 deletions core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from filters.models import UserFilter
from users.models import UserProfile


# Create your views here.
def home(request):
ctx = {}
Expand All @@ -15,8 +16,14 @@ def home(request):
systems = VotingSystem.objects.all()

if request.user.is_authenticated():
details = json.loads(request.user.profile.details)
votes_shown = [v for v in votes if v.filter.matches(details)]
try:
details = request.user.socialaccount_set.get(
provider='dreamjub').extra_data
except request.user.DoesNotExist:
details = {}

votes_shown = [v for v in votes if v.filter.matches(json.loads(
details))]

ctx["vote_list_title"] = "Your votes"

Expand Down
96 changes: 48 additions & 48 deletions filters/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,56 +9,56 @@
from settings.models import VotingSystem

import filters.forest as forest
from jay import utils

# Create your models here.
class UserFilter(models.Model):
system = models.ForeignKey(VotingSystem)
name = models.CharField(max_length=255)
value = models.CharField(max_length=255)
tree = models.TextField(blank=True)

def __str__(self):
return u'%s: %s' % (self.system.machine_name, self.name)

def clean(self):
try:
self.tree = json.dumps(forest.parse_and_simplify(self.value))
except Exception as e:
self.tree = None

if self.tree == None:
raise ValidationError({
'value': ValidationError('Value for \'value\' invalid: Can not parse into a valid logical tree. ', code='invalid')
})

def matches(self, obj):
"""
Checks if this filter matches an object.
"""

try:
return forest.matches(json.loads(self.tree), obj)
except Exception as e:
import sys
sys.stderr.write(e)
return False

def map_matches(self, objs):

try:
return forest.map_match(json.loads(self.tree), objs)
except Exception as e:
return False

def canEdit(self, user):
"""
Checks if a user can edit this UserFilter.
"""

return self.system.isAdmin(user)

def get_absolute_url(self):
from django.core.urlresolvers import reverse
return reverse('filters:edit', kwargs={'filter_id':self.id})
system = models.ForeignKey(VotingSystem)
name = models.CharField(max_length=255)
value = models.CharField(max_length=255)
tree = models.TextField(blank=True)

def __str__(self):
return u'%s: %s' % (self.system.machine_name, self.name)

def clean(self):
try:
self.tree = json.dumps(forest.parse_and_simplify(self.value))
except Exception as e:
self.tree = None

if self.tree == None:
raise ValidationError({
'value': ValidationError('Value for \'value\' invalid: Can not parse into a valid logical tree. ', code='invalid')
})

def matches(self, obj):
"""
Checks if this filter matches an object.
"""

try:
return forest.matches(json.loads(self.tree), obj)
except Exception as e:
import sys
sys.stderr.write(e)
return False

def map_matches(self, objs):

try:
return forest.map_match(json.loads(self.tree), objs)
except Exception as e:
return False

def canEdit(self, user):
"""
Checks if a user can edit this UserFilter.
"""
return utils.is_admin_for(user, self.system)

def get_absolute_url(self):
from django.core.urlresolvers import reverse
return reverse('filters:edit', kwargs={'filter_id':self.id})

admin.site.register(UserFilter)
16 changes: 8 additions & 8 deletions filters/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
from filters.forms import NewFilterForm, EditFilterForm, FilterTestForm, FilterTestUserForm
import filters.forest as forest

import json


from votes.models import VotingSystem

from jay.utils import priviliged
from jay.utils import priviliged, is_elevated, get_all_systems, is_admin_for, get_user_details

import json

FILTER_FOREST_TEMPLATE = "filters/filter_forest.html"
FILTER_EDIT_TEMPLATE = "filters/filter_edit.html"
Expand All @@ -28,7 +28,7 @@
def Forest(request, alert_type=None, alert_head=None, alert_text=None):

# if the user does not have enough priviliges, throw an exception
if not request.user.profile.isElevated():
if not is_elevated(request.user):
raise PermissionDenied

# build a new context
Expand All @@ -40,7 +40,7 @@ def Forest(request, alert_type=None, alert_head=None, alert_text=None):
bc.append({'url':reverse('filters:forest'), 'text':'Filters', 'active':True})
ctx['breadcrumbs'] = bc

(admin_systems, other_systems) = request.user.profile.getSystems()
(admin_systems, other_systems) = get_all_systems(request.user)

# give those to the view
ctx['admin_systems'] = admin_systems
Expand Down Expand Up @@ -77,7 +77,7 @@ def FilterNew(request):

# check if the user can edit it.
# if not, go back to the overview
if not system.isAdmin(request.user.profile):
if not is_admin_for(request.user, system):
return Forest(request, alert_head="Creation failed", alert_text="Nice try. You are not allowed to edit this VotingSystem. ")

# create a new filter
Expand Down Expand Up @@ -136,7 +136,7 @@ def FilterEdit(request, filter_id):
ctx["filter"] = filter

# check if the user can edit it
if not filter.canEdit(request.user.profile):
if not filter.canEdit(request.user):
raise PermissionDenied

# Set up the breadcrumbs
Expand Down Expand Up @@ -244,7 +244,7 @@ def FilterTestUser(request, filter_id):
form = FilterTestUserForm(request.POST)
if form.is_valid():
obj = form.cleaned_data["user"]
obj = User.objects.filter(username=obj)[0].profile.details
obj = json.dumps(get_user_details(User.objects.filter(username=obj)[0]))
except Exception as e:
print(e)
pass
Expand Down
17 changes: 15 additions & 2 deletions jay/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',

'django_forms_bootstrap',

'allauth',
'allauth.account',
'allauth.socialaccount',
'dreamjub.providers.oauth',

'filters',
'settings',
'users',
Expand Down Expand Up @@ -66,8 +74,10 @@
WSGI_APPLICATION = 'jay.wsgi.application'

# OpenJUB auth
AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend',
'users.ojub_auth.OjubBackend')
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)

# Default after login redirect
# These are named URL routes
Expand All @@ -93,3 +103,6 @@
)

STATIC_URL = '/static/'
SITE_ID = 1

ACCOUNT_EMAIL_VERIFICATION = "none"
2 changes: 1 addition & 1 deletion jay/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
url(r'^help/filters/$', TemplateView.as_view(template_name="filters/filter_help.html"), name="filter_help"),

# Authentication
url(r'^login/', auth_views.login, {'template_name': 'auth/login.html'}, name="login"),
url(r'^accounts/', include('allauth.urls'), name='login'),
url(r'^logout/', auth_views.logout, {'template_name': 'auth/logout.html', 'next_page':'home'}, name="logout"),

# Sub-projects
Expand Down
78 changes: 69 additions & 9 deletions jay/utils.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,94 @@
from django.core.exceptions import PermissionDenied


def memoize(f):
memo = {}

def helper(*x):
key = str(x)
if key not in memo:
memo[key] = f(*x)
return memo[key]

return helper


def superadmin(handler):
"""
Checks if a user is a super admin.
Checks if a user is a super admin.
"""

def helper(request, *args, **kwargs):
if not request.user.profile.isSuperAdmin():
if not request.user.is_superuser:
raise PermissionDenied
return handler(request, *args, **kwargs)

return helper


def is_elevated(user):
if not user.is_superuser:
if not user.admin_set.count() > 0:
return False
return True


def priviliged(handler):
"""
Checks that a user has elevated priviliges.
Checks that a user has elevated priviliges.
"""

def helper(request, *args, **kwargs):
if not request.user.profile.isElevated():
if not is_elevated(request.user):
raise PermissionDenied

return handler(request, *args, **kwargs)

return helper

return helper


def get_user_details(user):
import json

try:
data = user.socialaccount_set.get(provider="dreamjub").extra_data
return data
except:
return {}


def is_admin_for(user, system):
"""
Checks if this user can administer a certain voting system.
"""
return system in get_administrated_systems(user)


def get_administrated_systems(user):
from settings.models import VotingSystem, Admin
"""
Returns all voting systems this user can administer.
"""
# if we are a superadmin we can manage all systems
if user.is_superuser:
return VotingSystem.objects.all()

# else return only the systems we are an admin for.
else:
return list(map(lambda x: x.system, Admin.objects.filter(
user=user)))


def get_all_systems(user):
from settings.models import VotingSystem
"""
Gets the editable filters for this user.
"""

# get all the voting systems for this user
admin_systems = get_administrated_systems(user)

# and all the other ones also
other_systems = list(filter(lambda a: not a in admin_systems, VotingSystem.objects.all()))

return admin_systems, other_systems
8 changes: 6 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@ Markdown==2.6.5
# For filters
PyExecJS==1.1.0

# For OpenJUB auth backend
requests==2.6.0
# For reaching out to OpenJUB
requests==2.12.3

# For authing against dreamjub
django-allauth==0.29.0
django-allauth-dreamjub==0.1.3
Loading

0 comments on commit 541dfa2

Please sign in to comment.