Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Password protect partnered events #407

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions coderdojochi/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import factory
from datetime import datetime
from pytz import utc

from models import Session
from models import Course
from models import Location
from models import Mentor
from models import CDCUser
from models import PartnerPasswordAccess


class CourseFactory(factory.DjangoModelFactory):
title = factory.Sequence(lambda n: 'Test Course {}'.format(n))
slug = factory.Sequence(lambda n: 'test-course-{}'.format(n))

class Meta:
model = Course


class LocationFactory(factory.DjangoModelFactory):
name = factory.Sequence(lambda n: 'Test Location {}'.format(n))
address = factory.Sequence(lambda n: '{} Street'.format(n))
city = 'Chicago'
state = 'IL'
zip = '60605'

class Meta:
model = Location


class CDCUserFactory(factory.DjangoModelFactory):
username = factory.Sequence(lambda n: 'username_{}'.format(n))

class Meta:
model = CDCUser


class MentorFactory(factory.DjangoModelFactory):
user = factory.SubFactory(CDCUserFactory)
active = True

class Meta:
model = Mentor


class SessionFactory(factory.DjangoModelFactory):
course = factory.SubFactory(CourseFactory)
location = factory.SubFactory(LocationFactory)
start_date = datetime.now(utc)
end_date = datetime.now(utc)
mentor_start_date = datetime.now(utc)
mentor_end_date = datetime.now(utc)
password = ''
teacher = factory.SubFactory(MentorFactory)

class Meta:
model = Session


class PartnerPasswordAccessFactory(factory.DjangoModelFactory):
user = factory.SubFactory(CDCUserFactory)
session = factory.SubFactory(SessionFactory)

class Meta:
model = PartnerPasswordAccess
25 changes: 25 additions & 0 deletions coderdojochi/migrations/0007_partner_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-09-20 17:46
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('coderdojochi', '0006_session_gender_limitation'),
]

operations = [
migrations.AddField(
model_name='session',
name='partner_message',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='session',
name='password',
field=models.CharField(blank=True, max_length=255),
),
]
30 changes: 30 additions & 0 deletions coderdojochi/migrations/0008_partnerpasswordaccess.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-09-20 17:56
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('coderdojochi', '0007_partner_session'),
]

operations = [
migrations.CreateModel(
name='PartnerPasswordAccess',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='coderdojochi.Session')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'partner_password_access',
'verbose_name': 'partner_password_access',
},
),
]
12 changes: 12 additions & 0 deletions coderdojochi/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ class Session(models.Model):
)
active = models.BooleanField(default=False, help_text="Session is active.")
public = models.BooleanField(default=False, help_text="Session is a public session.")
password = models.CharField(blank=True, max_length=255)
partner_message = models.TextField(blank=True)
announced_date = models.DateTimeField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Expand Down Expand Up @@ -628,3 +630,13 @@ class Meta:

def __unicode__(self):
return u'{} | ${}'.format(self.email, self.amount)


class PartnerPasswordAccess(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(CDCUser)
session = models.ForeignKey(Session)

class Meta:
verbose_name = _("partner_password_access")
db_table = _("partner_password_access")
3 changes: 2 additions & 1 deletion coderdojochi/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import sys
from coderdojochi.util import str_to_bool

BASE_DIR = os.path.dirname(os.path.dirname(__file__))
Expand All @@ -25,7 +26,7 @@
SECRET_KEY = 'e^u3u$pukt$s=6#&9oi9&jj5ow6563fuka%y9t7i*2laalk^l$'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = str_to_bool(os.environ.get('DEBUG')) or False
DEBUG = str_to_bool(os.environ.get('DEBUG')) and 'test' not in sys.argv or False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ash6851 Can you explain what this addition does?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@karbassi when I was writing tests I was trying to check response url, but the redirect was being intercepted by the redirect debug page and my response url was wrong. Let me know if you need more explanation

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ash6851 ah, yes. This needs to be set to default to false. I'll commit a fix ASAP.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ash6851 Merge in #409

DEBUG_EMAIL = str_to_bool(os.environ.get('DEBUG_EMAIL')) or False

ALLOWED_HOSTS = ['*']
Expand Down
26 changes: 26 additions & 0 deletions coderdojochi/templates/session-partner-password.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% extends "_base.html" %}

{% load i18n coderdojochi_extras %}

{% block title %}Upcoming Classes{% endblock %}

{% block extra_meta %}
<meta property="og:url" content="{{ session.get_absolute_url }}">
<meta property="og:title" content="Check out our upcoming classes!">
{% endblock %}

{% block body_class %}page-classes{% endblock %}

{% block content %}
<div class="container">
<div id="errors">
<p>Error: {{ error }}</p>
</div>
<form method="post" action=".">
{% csrf_token %}
<label for="id-password">Password:</label>
<input id="id-password" name="password" type="text"/>
<button class="btn-cdc btn-cdc-sm">Submit</button>
</form>
</div>
{% endblock %}
106 changes: 106 additions & 0 deletions coderdojochi/tests/test_password_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import mock
import sys

from django.test import TestCase
from django.test import Client
from django.test import RequestFactory
from django.core.urlresolvers import reverse
from django.contrib.auth import get_user_model
from django.http import HttpResponseRedirect

from coderdojochi.models import PartnerPasswordAccess
from coderdojochi.factories import SessionFactory
from coderdojochi.factories import PartnerPasswordAccessFactory
from coderdojochi.factories import CDCUserFactory
from coderdojochi.views import session_detail


User = get_user_model()


class TestPartnerSessionPassword(TestCase):
def setUp(self):
self.partner_session = SessionFactory.create(password='124')
self.url_kwargs = {
'year': self.partner_session.start_date.year,
'month': self.partner_session.start_date.month,
'day': self.partner_session.start_date.day,
'slug': self.partner_session.course.slug,
'session_id': self.partner_session.id
}
self.client = Client()
self.url = reverse('session_password', kwargs=self.url_kwargs)

def test_session_password_invalid_password(self):
response = self.client.post(self.url, data={'password': 'abc'})
self.assertContains(response, 'Invalid password.')

def test_session_password_no_password(self):
response = self.client.post(self.url, data={'password': ''})
self.assertContains(response, 'Must enter a password.')

def test_session_password_valid_password_unauthed(self):
response = self.client.post(self.url, data={'password': self.partner_session.password})
self.assertIsInstance(response, HttpResponseRedirect)

detail_url = reverse('session_detail', kwargs=self.url_kwargs)
self.assertEqual(response.url, detail_url)
password_access_count = PartnerPasswordAccess.objects.count()
self.assertEqual(password_access_count, 0)
authed_sessions = self.client.session['authed_partner_sessions']
self.assertFalse(str(self.partner_session.id) in authed_sessions)

def test_session_password_valid_password_authed(self):
user = User.objects.create_user('user', email='email@email.com', password='pass123')
self.assertTrue(self.client.login(email='email@email.com', password='pass123'))
response = self.client.post(self.url, data={'password': self.partner_session.password})
self.assertIsInstance(response, HttpResponseRedirect)

detail_url = reverse('session_detail', kwargs=self.url_kwargs)
self.assertEqual(response.url, detail_url)
partner_password_access = PartnerPasswordAccess.objects.get(session=self.partner_session,
user=user)
self.assertIsNotNone(partner_password_access)

authed_sessions = self.client.session['authed_partner_sessions']
self.assertTrue(str(self.partner_session.id) in authed_sessions)


class TestSessionDetail(TestCase):
def setUp(self):
super(TestSessionDetail, self).setUp()
self.client = Client()
self.partner_session = SessionFactory.create(password='124')
self.url_kwargs = {
'year': self.partner_session.start_date.year,
'month': self.partner_session.start_date.month,
'day': self.partner_session.start_date.day,
'slug': self.partner_session.course.slug,
'session_id': self.partner_session.id
}
self.url = reverse('session_detail', kwargs=self.url_kwargs)

def test_redirect_password_unauthed(self):
response = self.client.get(self.url)
self.assertIsInstance(response, HttpResponseRedirect)

detail_url = reverse('session_password', kwargs=self.url_kwargs)
self.assertEqual(response.url, detail_url)

def test_redirect_password_authed(self):
User.objects.create_user('user', email='email@email.com', password='pass123')
self.assertTrue(self.client.login(email='email@email.com', password='pass123'))
response = self.client.get(self.url)
self.assertIsInstance(response, HttpResponseRedirect)

detail_url = reverse('session_password', kwargs=self.url_kwargs)
self.assertEqual(response.url, detail_url)

def test_redirect_password_partner_password_access(self):
user = User.objects.create_user('user', email='email@email.com', password='pass123')
self.assertTrue(self.client.login(email='email@email.com', password='pass123'))
PartnerPasswordAccessFactory.create(user=user, session=self.partner_session)
response = self.client.get(self.url)
detail_url = reverse('session_password', kwargs=self.url_kwargs)
# don't care what its doing as long as its not redirecting to the password url.
self.assertNotEqual(response.url, detail_url)
1 change: 1 addition & 0 deletions coderdojochi/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
url(r'^class/(?P<year>[\d]+)/(?P<month>[\d]+)/(?P<day>[\d]+)/(?P<slug>[-\w]+)/(?P<session_id>[\d]+)/enroll/$', coderdojochi_views.session_detail_enroll, name='session_detail_enroll'),
url(r'^class/(?P<year>[\d]+)/(?P<month>[\d]+)/(?P<day>[\d]+)/(?P<slug>[-\w]+)/(?P<session_id>[\d]+)/calendar/$', coderdojochi_views.session_ics, name='session_ics'),
url(r'^class/(?P<year>[\d]+)/(?P<month>[\d]+)/(?P<day>[\d]+)/(?P<slug>[-\w]+)/(?P<session_id>[\d]+)$', coderdojochi_views.session_detail, name='session_detail'),
url(r'^class/(?P<year>[\d]+)/(?P<month>[\d]+)/(?P<day>[\d]+)/(?P<slug>[-\w]+)/(?P<session_id>[\d]+)/password/$', coderdojochi_views.PasswordSessionView.as_view(), name='session_password'),
url(r'^class/(?P<session_id>[\d]+)/announce/$', coderdojochi_views.session_announce, name='session_announce'),

url(r'^meeting/(?P<year>[\d]+)/(?P<month>[\d]+)/(?P<day>[\d]+)/(?P<slug>[-\w]+)/(?P<meeting_id>[\d]+)/sign-up/$', coderdojochi_views.meeting_sign_up, name='meeting_sign_up'),
Expand Down
59 changes: 58 additions & 1 deletion coderdojochi/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
from django.utils.html import strip_tags
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView

from coderdojochi.util import local_to_utc
from coderdojochi.models import (Mentor, Guardian, Student, Session, Order, MentorOrder,
Meeting, MeetingOrder, Donation, CDCUser, EquipmentType, Equipment)
Meeting, MeetingOrder, Donation, CDCUser, EquipmentType,
Equipment, PartnerPasswordAccess)
from coderdojochi.forms import CDCModelForm, MentorForm, GuardianForm, StudentForm, ContactForm

# this will assign User to our custom CDCUser
Expand Down Expand Up @@ -240,8 +242,37 @@ def session_detail_enroll(request, year, month, day, slug, session_id, template_
return session_detail(request, year, month, day, slug, session_id, template_name, enroll=True)


def validate_partner_session_access(request, session_id):
authed_sessions = request.session.get('authed_partner_sessions')
if authed_sessions and session_id in authed_sessions:
return True

if request.user.is_authenticated():
try:
PartnerPasswordAccess.objects.get(session_id=session_id,
user_id=request.user.id)
except PartnerPasswordAccess.DoesNotExist:
return False
else:
return True
else:
return False


def session_detail(request, year, month, day, slug, session_id, template_name="session-detail.html", enroll=False):
session_obj = get_object_or_404(Session, id=session_id)
if session_obj.password:
if not validate_partner_session_access(request, session_id):
view_kwargs = {
'year': year,
'month': month,
'day': day,
'slug': slug,
'session_id': session_id
}
url = reverse('session_password', kwargs=view_kwargs)
return HttpResponseRedirect(url)

mentor_signed_up = False
account = False
students = False
Expand Down Expand Up @@ -1716,3 +1747,29 @@ def sendSystemEmail(request, subject, template_name, merge_vars, email=False, bc
print >>sys.stderr, u'user: {}, {}'.format(user.email, response['reject_reason'])

raise e


class PasswordSessionView(TemplateView):
template_name = 'session-partner-password.html'

def post(self, request, *args, **kwargs):
session_id = kwargs.get('session_id')
session_obj = get_object_or_404(Session, id=session_id)
password_input = request.POST.get('password')
if not password_input:
return self.render_to_response({'error': 'Must enter a password.'})
if session_obj.password != password_input:
return self.render_to_response({'error': 'Invalid password.'})

authed_partner_sessions = request.session.get('authed_partner_sessions') or set()
authed_partner_sessions.update(session_id)
request.session['authed_partner_sessions'] = authed_partner_sessions

if request.user.is_authenticated():
try:
PartnerPasswordAccess.objects.get(session=session_obj, user=request.user)
except PartnerPasswordAccess.DoesNotExist:
PartnerPasswordAccess.objects.create(session=session_obj, user=request.user)

url = reverse('session_detail', kwargs=kwargs)
return HttpResponseRedirect(url)