diff --git a/README.md b/README.md index 9abc0d5..083a30d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ Jay is a simple secret voting system for Jacobs University. Just in case you wer see [doc/](doc) for minimal developer documentation +## Setup + +1. Configure database and apply migrations +2. Create a Social Application pointing to `dreamjub` + ## License Jay is © 2015-17 Leonhard Kuboschek, Tom Wiesing & Contributors. Licensed under MIT license. See [LICENSE.md](LICENSE.md) for details. diff --git a/core/views.py b/core/views.py index 1cfb20e..37d628d 100644 --- a/core/views.py +++ b/core/views.py @@ -4,6 +4,7 @@ from settings.models import VotingSystem from votes.models import Vote, Status +from jay.utils import get_user_details # Create your views here. @@ -14,8 +15,10 @@ 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)] + details = get_user_details(request.user) + + votes_shown = [v for v in votes if v.filter.matches(json.loads( + details))] ctx["vote_list_title"] = "Your votes" diff --git a/filters/models.py b/filters/models.py index dca5c8a..7c8f7c0 100644 --- a/filters/models.py +++ b/filters/models.py @@ -9,6 +9,7 @@ from settings.models import VotingSystem import filters.forest as forest +from jay import utils # Create your models here. @@ -58,8 +59,7 @@ def canEdit(self, user): """ Checks if a user can edit this UserFilter. """ - - return self.system.isAdmin(user) + return utils.is_admin_for(user, self.system) def get_absolute_url(self): from django.core.urlresolvers import reverse diff --git a/filters/views.py b/filters/views.py index deec2a2..4a16f5a 100644 --- a/filters/views.py +++ b/filters/views.py @@ -12,11 +12,12 @@ 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, \ + get_user_details + +import json FILTER_FOREST_TEMPLATE = "filters/filter_forest.html" FILTER_EDIT_TEMPLATE = "filters/filter_edit.html" @@ -27,7 +28,7 @@ @priviliged 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 @@ -40,7 +41,7 @@ def Forest(request, alert_type=None, alert_head=None, alert_text=None): {'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 @@ -79,7 +80,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 system.isAdmin(request.user): return Forest(request, alert_head="Creation failed", alert_text="Nice try. You are not allowed to edit " "this VotingSystem. ") @@ -149,7 +150,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 @@ -259,7 +260,8 @@ 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 diff --git a/jay/settings.py b/jay/settings.py index 825a9c0..0547b85 100644 --- a/jay/settings.py +++ b/jay/settings.py @@ -25,7 +25,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', @@ -65,12 +73,14 @@ 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 -LOGIN_URL = "login" +LOGIN_URL = "accounts/dreamjub/login" LOGOUT_URL = "logout" LOGIN_REDIRECT_URL = "home" @@ -91,3 +101,6 @@ ) STATIC_URL = '/static/' +SITE_ID = 1 + +ACCOUNT_EMAIL_VERIFICATION = "none" diff --git a/jay/urls.py b/jay/urls.py index 34ffe79..6e77dd9 100644 --- a/jay/urls.py +++ b/jay/urls.py @@ -49,8 +49,7 @@ 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"), diff --git a/jay/utils.py b/jay/utils.py index 698bda5..fd5e2e0 100644 --- a/jay/utils.py +++ b/jay/utils.py @@ -17,20 +17,77 @@ def superadmin(handler): """ 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 privileges. """ + """ + 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 + + +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: a not in admin_systems, VotingSystem.objects.all())) + + return admin_systems, other_systems diff --git a/requirements.txt b/requirements.txt index 8b63007..6665921 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,8 +10,12 @@ Markdown==2.6.5 # For filters PyExecJS==1.1.0 -# For OpenJUB auth backend -requests==2.6.0 - # For testing pep -pep8 >= 1.7.0 \ No newline at end of file +pep8 >= 1.7.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 \ No newline at end of file diff --git a/settings/models.py b/settings/models.py index cfeea0a..9e51e91 100644 --- a/settings/models.py +++ b/settings/models.py @@ -3,6 +3,7 @@ from django.contrib import admin from jay.restricted import is_restricted_word +from users.models import Admin class VotingSystem(models.Model): @@ -27,5 +28,4 @@ def isAdmin(self, user): """ return user.isAdminFor(self) - admin.site.register(VotingSystem) diff --git a/settings/urls.py b/settings/urls.py index db501ca..a0de000 100644 --- a/settings/urls.py +++ b/settings/urls.py @@ -1,15 +1,15 @@ from django.conf.urls import url from django.views.generic import TemplateView -from settings.views import superadmins, systems +from settings.views import systems urlpatterns = [ # Superadmin management - url(r'^$', superadmins.settings, name="settings"), - url(r'^superadmins/add$', superadmins.superadmin_add, name="add"), - url(r'^superadmins/(?P[\w-]+)/remove$', - superadmins.superadmin_remove, name="remove"), + # url(r'^$', superadmins.settings, name="settings"), + # url(r'^superadmins/add$', superadmins.superadmin_add, name="add"), + # url(r'^superadmins/(?P[\w-]+)/remove$', + # superadmins.superadmin_remove, name="remove"), # System management url(r'^systems$', systems.systems, name='systems'), diff --git a/templates/auth/login.html b/templates/auth/login.html deleted file mode 100644 index aadffe3..0000000 --- a/templates/auth/login.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends "base/base.html" %} - -{% block page_title %}Login{% endblock %} - -{% block content %} - -{% if form.errors %} - -{% endif %} - -{% if next %} - {% if user.is_authenticated %} - - {% else %} - - {% endif %} -{% endif %} - -
- -
-
- -
- -
-
- -

If you lost your password, reset it in CampusNet.

- -{% endblock %} diff --git a/templates/base/base.html b/templates/base/base.html index dfa528a..7b039a0 100644 --- a/templates/base/base.html +++ b/templates/base/base.html @@ -43,11 +43,11 @@ {{ user.username }} {% else %} - Login + Login {% endif %} diff --git a/templates/vote/fragments/vote_list.html b/templates/vote/fragments/vote_list.html index f73dc3b..6c7cf1f 100644 --- a/templates/vote/fragments/vote_list.html +++ b/templates/vote/fragments/vote_list.html @@ -12,13 +12,13 @@  {{ vote.name }} - {% if vote|can_delete:request.user.profile %} + {% if vote|can_delete:request.user %} {% endif %} - {% if vote|can_edit:request.user.profile %} + {% if vote|can_edit:request.user %} diff --git a/users/models.py b/users/models.py index b8f9d93..f7e15ac 100644 --- a/users/models.py +++ b/users/models.py @@ -1,19 +1,13 @@ from django.db import models from django.contrib.auth.models import User -from django.core.exceptions import ValidationError - from django.contrib import admin -from settings.models import VotingSystem - -import json - # Create your models here. class Admin(models.Model): user = models.ForeignKey(User) - system = models.ForeignKey(VotingSystem) + system = models.ForeignKey("settings.VotingSystem") class Meta(): unique_together = (("system", "user")) @@ -22,87 +16,4 @@ def __str__(self): return u'[%s] %s' % (self.system.machine_name, self.user) -class SuperAdmin(models.Model): - user = models.ForeignKey(User) - - class Meta(): - unique_together = (("user",),) - - def __str__(self): - return u'%s' % (self.user) - - -class UserProfile(models.Model): - user = models.OneToOneField(User, related_name="profile") - details = models.TextField() - - def __str__(self): - return u'[Profile] %s' % (self.user.username) - - def clean(self): - # make sure that the details are a valid json object - try: - json.loads(self.details) - except: - raise ValidationError({ - 'details': ValidationError( - 'Details needs to be a valid JSON object', code='invalid') - }) - - def isSuperAdmin(self): - """ - Returns if this user is a SuperAdmin. - """ - return self.user.superadmin_set.count() > 0 - - def isAdminFor(self, system): - """ - Checks if this user can administer a certain voting system. - """ - return system in self.getAdministratedSystems() - - def getAdministratedSystems(self): - """ - Returns all voting systems this user can administer. - """ - # if we are a superadmin we can manage all systems - if self.isSuperAdmin(): - 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=self.user))) - - def isElevated(self): - """ - Checks if this user is an elevated user. - - (i. e. if they are a superadmin or admin for some voting system) - """ - - # thy are a superadmin - if self.isSuperAdmin(): - return True - - # they administer some voting system - return self.user.admin_set.count() > 0 - - def getSystems(self): - """ - Gets the editable filters for this user. - """ - - # get all the voting systems for this user - admin_systems = self.getAdministratedSystems() - - # and all the other ones also - other_systems = list(filter(lambda a: a not in admin_systems, - VotingSystem.objects.all())) - - return (admin_systems, other_systems) - - admin.site.register(Admin) -admin.site.register(SuperAdmin) -admin.site.register(UserProfile) diff --git a/users/ojub_auth.py b/users/ojub_auth.py index af7a211..021b178 100644 --- a/users/ojub_auth.py +++ b/users/ojub_auth.py @@ -1,81 +1,8 @@ -from django.conf import settings -from django.contrib.auth.models import User - -from users.models import UserProfile - import requests OPENJUB_BASE = "https://api.jacobs.university/" -class OjubBackend(object): - """ - Authenticates credentials against the OpenJUB database. - - The URL for the server is configured by OPENJUB_BASE in the settings. - - This class does not fill in user profiles, this has to be handled - in other places - """ - - def authenticate(self, username=None, password=None): - r = requests.post(OPENJUB_BASE + "auth/signin", - data={'username': username, 'password': password}) - - if r.status_code != requests.codes.ok: - return None - - resp = r.json() - - uname = resp['user'] - token = resp['token'] - - details = requests.get(OPENJUB_BASE + "user/me", - params={'token': token}) - - if details.status_code != requests.codes.ok: - print("Could not get user details") - return None - - try: - user = User.objects.get(username=uname) - except User.DoesNotExist: - user = User(username=uname) - - user.set_unusable_password() - - # TODO Don't hardcode this - if user.username in ["lkuboschek", "twiesing", "jinzhang", - "rdeliallis"]: - user.is_staff = True - user.is_superuser = True - - data = details.json() - - user.first_name = data['firstName'] - user.last_name = data['lastName'] - user.email = data['email'] - - user.save() - - # Make a user profile if there isn't one already - try: - profile = UserProfile.objects.get(user=user) - except UserProfile.DoesNotExist: - profile = UserProfile(user=user) - - profile.details = details.text - profile.save() - - return user - - def get_user(self, user_id): - try: - return User.objects.get(pk=user_id) - except User.DoesNotExist: - return None - - def get_all(username, password): r = requests.post(OPENJUB_BASE + "auth/signin", data={'username': username, 'password': password}) diff --git a/votes/models.py b/votes/models.py index 07c5af9..4dcd177 100644 --- a/votes/models.py +++ b/votes/models.py @@ -13,6 +13,7 @@ from users.ojub_auth import get_all from jay.restricted import is_restricted_word +from jay import utils # Create your models here. @@ -47,15 +48,15 @@ def canEdit(self, user): """ Checks if a user can edit this vote. """ - return user.isAdminFor( - self.system) and self.status.stage != Status.PUBLIC + return utils.is_admin_for(user, self.system) and \ + self.status.stage != Status.PUBLIC def canDelete(self, user): """ Check if a user can delete this vote. """ - return user.isAdminFor( - self.system) and self.status.stage == Status.INIT + return utils.is_admin_for(user, self.system) and \ + self.status.stage == Status.INIT def canBeModified(self): """ @@ -69,7 +70,7 @@ def update_eligibility(self, username, password): 'num_voters': 0, 'num_eligible': 0}) - if self.filter is not None: + if self.filter is None: raise Exception("Missing filter. ") # this will take really long diff --git a/votes/views.py b/votes/views.py index fb8971e..d5eb9f7 100644 --- a/votes/views.py +++ b/votes/views.py @@ -52,7 +52,7 @@ def system_home(request, system_name): all_votes = Vote.objects.filter(system=vs) - if request.user.is_authenticated() and vs.isAdmin(request.user.profile): + if request.user.is_authenticated() and vs.isAdmin(request.user): ctx['votes'] = all_votes ctx['results'] = Vote.objects.filter(system=vs, status__stage__in=[Status.PUBLIC, @@ -228,6 +228,7 @@ def get_vote_props(ctx, vote): def vote_edit_context(request, system_name, vote_name): + from jay import utils """ Returns context and basic parameters for vote editing. """ @@ -237,14 +238,14 @@ def vote_edit_context(request, system_name, vote_name): vote.touch() # raise an error if the user trying to access is not an admin - if not system.isAdmin(request.user.profile): + if not system.isAdmin(request.user): raise PermissionDenied # make a context ctx = {} # get all the systems this user can edit - (admin_systems, other_systems) = request.user.profile.getSystems() + (admin_systems, other_systems) = utils.get_all_systems(request.user) # add the vote to the system ctx['vote'] = vote @@ -281,7 +282,7 @@ def vote_add(request, system_name): vs = get_object_or_404(VotingSystem, machine_name=system_name) # raise an error if the user trying to access is not an admin - if not vs.isAdmin(request.user.profile): + if not vs.isAdmin(request.user): raise PermissionDenied v = Vote()