Skip to content
Snippets Groups Projects
Commit fc7d532a authored by Ben Adida's avatar Ben Adida
Browse files

Merge pull request #107 from benadida/issue-106-clever-auth

Issue 106 clever auth
parents ebbb4c0a 70c946b2
Branches
No related tags found
No related merge requests found
Showing
with 234 additions and 8 deletions
...@@ -5,3 +5,4 @@ deploy-latest.sh ...@@ -5,3 +5,4 @@ deploy-latest.sh
media/* media/*
venv venv
celerybeat-* celerybeat-*
env.sh
\ No newline at end of file
...@@ -187,7 +187,7 @@ def can_create_election(request): ...@@ -187,7 +187,7 @@ def can_create_election(request):
if helios.ADMIN_ONLY: if helios.ADMIN_ONLY:
return user.admin_p return user.admin_p
else: else:
return user != None return user.can_create_election()
def user_can_feature_election(user, election): def user_can_feature_election(user, election):
if not user: if not user:
......
AUTH_SYSTEMS = {} AUTH_SYSTEMS = {}
import twitter, password, cas, facebook, google, yahoo, linkedin import twitter, password, cas, facebook, google, yahoo, linkedin, clever
AUTH_SYSTEMS['twitter'] = twitter AUTH_SYSTEMS['twitter'] = twitter
AUTH_SYSTEMS['linkedin'] = linkedin AUTH_SYSTEMS['linkedin'] = linkedin
AUTH_SYSTEMS['password'] = password AUTH_SYSTEMS['password'] = password
...@@ -9,6 +9,7 @@ AUTH_SYSTEMS['cas'] = cas ...@@ -9,6 +9,7 @@ AUTH_SYSTEMS['cas'] = cas
AUTH_SYSTEMS['facebook'] = facebook AUTH_SYSTEMS['facebook'] = facebook
AUTH_SYSTEMS['google'] = google AUTH_SYSTEMS['google'] = google
AUTH_SYSTEMS['yahoo'] = yahoo AUTH_SYSTEMS['yahoo'] = yahoo
AUTH_SYSTEMS['clever'] = clever
# not ready # not ready
#import live #import live
......
...@@ -242,3 +242,11 @@ def eligibility_category_id(constraint): ...@@ -242,3 +242,11 @@ def eligibility_category_id(constraint):
def pretty_eligibility(constraint): def pretty_eligibility(constraint):
return "Members of the Class of %s" % constraint['year'] return "Members of the Class of %s" % constraint['year']
#
# Election Creation
#
def can_create_election(user_id, user_info):
return True
"""
Clever Authentication
"""
from django.http import *
from django.core.mail import send_mail
from django.conf import settings
import httplib2,json,base64
import sys, os, cgi, urllib, urllib2, re
from oauth2client.client import OAuth2WebServerFlow, OAuth2Credentials
# some parameters to indicate that status updating is not possible
STATUS_UPDATES = False
# display tweaks
LOGIN_MESSAGE = "Log in with Clever"
def get_flow(redirect_url=None):
return OAuth2WebServerFlow(
client_id=settings.CLEVER_CLIENT_ID,
client_secret=settings.CLEVER_CLIENT_SECRET,
scope='read:students read:teachers read:user_id read:sis',
auth_uri="https://clever.com/oauth/authorize",
#token_uri="https://clever.com/oauth/tokens",
token_uri="http://requestb.in/1b18gwf1",
redirect_uri=redirect_url)
def get_auth_url(request, redirect_url):
flow = get_flow(redirect_url)
request.session['clever-redirect-url'] = redirect_url
return flow.step1_get_authorize_url()
def get_user_info_after_auth(request):
redirect_uri = request.session['clever-redirect-url']
del request.session['clever-redirect-url']
flow = get_flow(redirect_uri)
code = request.GET['code']
# do the POST manually, because OAuth2WebFlow can't do auth header for token exchange
http = httplib2.Http(".cache")
auth_header = "Basic %s" % base64.b64encode(settings.CLEVER_CLIENT_ID + ":" + settings.CLEVER_CLIENT_SECRET)
resp_headers, content = http.request("https://clever.com/oauth/tokens", "POST", urllib.urlencode({
"code" : code,
"grant_type": "authorization_code",
"redirect_uri": redirect_uri
}), headers = {
'Authorization': auth_header,
'Content-Type': "application/x-www-form-urlencoded"
})
token_response = json.loads(content)
access_token = token_response['access_token']
# package the credentials
credentials = OAuth2Credentials(access_token, settings.CLEVER_CLIENT_ID, settings.CLEVER_CLIENT_SECRET, None, None, None, None)
# get the nice name
http = credentials.authorize(http)
(resp_headers, content) = http.request("https://api.clever.com/me", "GET")
# {"type":"student","data":{"id":"563395179f7408755c0006b7","district":"5633941748c07c0100000aac","type":"student","created":"2015-10-30T16:04:39.262Z","credentials":{"district_password":"eel7Thohd","district_username":"dianes10"},"dob":"1998-11-01T00:00:00.000Z","ell_status":"Y","email":"diane.s@example.org","gender":"F","grade":"9","hispanic_ethnicity":"Y","last_modified":"2015-10-30T16:04:39.274Z","location":{"zip":"11433"},"name":{"first":"Diane","last":"Schmeler","middle":"J"},"race":"Asian","school":"5633950c62fc41c041000005","sis_id":"738733110","state_id":"114327752","student_number":"738733110"},"links":[{"rel":"self","uri":"/me"},{"rel":"canonical","uri":"/v1.1/students/563395179f7408755c0006b7"},{"rel":"district","uri":"/v1.1/districts/5633941748c07c0100000aac"}]}
response = json.loads(content)
user_id = response['data']['id']
user_name = "%s %s" % (response['data']['name']['first'], response['data']['name']['last'])
user_type = response['type']
user_district = response['data']['district']
user_grade = response['data'].get('grade', None)
print content
# watch out, response also contains email addresses, but not sure whether thsoe are verified or not
# so for email address we will only look at the id_token
return {'type' : 'clever', 'user_id': user_id, 'name': user_name , 'info': {"district": user_district, "type": user_type, "grade": user_grade}, 'token': {'access_token': access_token}}
def do_logout(user):
"""
logout of Google
"""
return None
def update_status(token, message):
"""
simple update
"""
pass
def send_message(user_id, name, user_info, subject, body):
"""
send email to google users. user_id is the email for google.
"""
pass
#
# eligibility
#
def check_constraint(constraint, user):
if not user.info.has_key('grade'):
return False
return constraint['grade'] == user.info['grade']
def generate_constraint(category, user):
"""
generate the proper basic data structure to express a constraint
based on the category string
"""
return {'grade': category}
def list_categories(user):
return [{"id": str(g), "name": "Grade %d" % g} for g in range(3,13)]
def eligibility_category_id(constraint):
return constraint['grade']
def pretty_eligibility(constraint):
return "Grade %s" % constraint['grade']
#
# Election Creation
#
def can_create_election(user_id, user_info):
"""
Teachers only for now
"""
return user_info['type'] == 'teacher'
...@@ -116,3 +116,10 @@ def eligibility_category_id(constraint): ...@@ -116,3 +116,10 @@ def eligibility_category_id(constraint):
def pretty_eligibility(constraint): def pretty_eligibility(constraint):
return "Facebook users who are members of the \"%s\" group" % constraint['group']['name'] return "Facebook users who are members of the \"%s\" group" % constraint['group']['name']
#
# Election Creation
#
def can_create_election(user_id, user_info):
return True
...@@ -82,3 +82,11 @@ def check_constraint(constraint, user_info): ...@@ -82,3 +82,11 @@ def check_constraint(constraint, user_info):
for eligibility for eligibility
""" """
pass pass
#
# Election Creation
#
def can_create_election(user_id, user_info):
return True
...@@ -89,3 +89,10 @@ def send_notification(user_id, user_info, message): ...@@ -89,3 +89,10 @@ def send_notification(user_id, user_info, message):
pass pass
#
# Election Creation
#
def can_create_election(user_id, user_info):
return True
...@@ -65,3 +65,11 @@ def update_status(user_id, user_info, token, message): ...@@ -65,3 +65,11 @@ def update_status(user_id, user_info, token, message):
def send_message(user_id, user_name, user_info, subject, body): def send_message(user_id, user_name, user_info, subject, body):
pass pass
#
# Election Creation
#
def can_create_election(user_id, user_info):
return True
...@@ -117,3 +117,11 @@ def send_message(user_id, user_name, user_info, subject, body): ...@@ -117,3 +117,11 @@ def send_message(user_id, user_name, user_info, subject, body):
email = user_id email = user_id
name = user_name or user_info.get('name', email) name = user_name or user_info.get('name', email)
send_mail(subject, body, settings.SERVER_EMAIL, ["\"%s\" <%s>" % (name, email)], fail_silently=False) send_mail(subject, body, settings.SERVER_EMAIL, ["\"%s\" <%s>" % (name, email)], fail_silently=False)
#
# Election Creation
#
def can_create_election(user_id, user_info):
return True
...@@ -118,3 +118,10 @@ def follow_view(request): ...@@ -118,3 +118,10 @@ def follow_view(request):
return HttpResponseRedirect(reverse(after_intervention)) return HttpResponseRedirect(reverse(after_intervention))
#
# Election Creation
#
def can_create_election(user_id, user_info):
return True
...@@ -52,3 +52,11 @@ def check_constraint(constraint, user_info): ...@@ -52,3 +52,11 @@ def check_constraint(constraint, user_info):
for eligibility for eligibility
""" """
pass pass
#
# Election Creation
#
def can_create_election(user_id, user_info):
return True
helios_auth/media/login-icons/clever.png

33.7 KiB

...@@ -70,6 +70,16 @@ class User(models.Model): ...@@ -70,6 +70,16 @@ class User(models.Model):
return AUTH_SYSTEMS[self.user_type].STATUS_UPDATES return AUTH_SYSTEMS[self.user_type].STATUS_UPDATES
def can_create_election(self):
"""
Certain auth systems can choose to limit election creation
to certain users.
"""
if not AUTH_SYSTEMS.has_key(self.user_type):
return False
return AUTH_SYSTEMS[self.user_type].can_create_election(self.user_id, self.info)
def update_status_template(self): def update_status_template(self):
if not self.can_update_status(): if not self.can_update_status():
return None return None
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
{% else %} {% else %}
<p> <p>
<a href="{{SECURE_URL_HOST}}{% url "helios_auth.views.start" system_name=auth_system %}?return_url={{return_url}}" style="font-size: 1.4em;"> <a href="{{SECURE_URL_HOST}}{% url "helios_auth.views.start" system_name=auth_system %}?return_url={{return_url}}" style="font-size: 1.4em;">
<img border="0" height="35" src="/static/auth/login-icons/{{auth_system}}.png" alt="{{auth_system}}" /> {{auth_system}} <img style="height: 35px; border: 0px;" src="/static/auth/login-icons/{{auth_system}}.png" alt="{{auth_system}}" /> {{auth_system}}
{% endifequal %} {% endifequal %}
</a> </a>
</p> </p>
......
...@@ -47,6 +47,16 @@ class UserModelTests(unittest.TestCase): ...@@ -47,6 +47,16 @@ class UserModelTests(unittest.TestCase):
self.assertEquals(u2.info['name'], new_name) self.assertEquals(u2.info['name'], new_name)
def test_can_create_election(self):
"""
check that auth systems have the can_create_election call and that it's true for the common ones
"""
for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems():
assert(hasattr(auth_system_module, 'can_create_election'))
if auth_system != 'clever':
assert(auth_system_module.can_create_election('foobar', {}))
def test_status_update(self): def test_status_update(self):
""" """
check that a user set up with status update ability reports it as such, check that a user set up with status update ability reports it as such,
......
...@@ -16,7 +16,6 @@ dj_database_url==0.3.0 ...@@ -16,7 +16,6 @@ dj_database_url==0.3.0
django-sslify==0.2.7 django-sslify==0.2.7
django_webtest==1.7.8 django_webtest==1.7.8
webtest==2.0.18 webtest==2.0.18
django-db-pool==0.0.10
django-secure==1.0.1 django-secure==1.0.1
bleach==1.4.1 bleach==1.4.1
boto==2.27.0 boto==2.27.0
......
import os, json import os, json
# a massive hack to see if we're testing, in which case we use different settings
import sys
TESTING = 'test' in sys.argv
# go through environment variables and override them # go through environment variables and override them
def get_from_env(var, default): def get_from_env(var, default):
if os.environ.has_key(var): if not TESTING and os.environ.has_key(var):
return os.environ[var] return os.environ[var]
else: else:
return default return default
...@@ -41,8 +45,8 @@ SOUTH_DATABASE_ADAPTERS = {'default':'south.db.postgresql_psycopg2'} ...@@ -41,8 +45,8 @@ SOUTH_DATABASE_ADAPTERS = {'default':'south.db.postgresql_psycopg2'}
if get_from_env('DATABASE_URL', None): if get_from_env('DATABASE_URL', None):
import dj_database_url import dj_database_url
DATABASES['default'] = dj_database_url.config() DATABASES['default'] = dj_database_url.config()
DATABASES['default']['ENGINE'] = 'dbpool.db.backends.postgresql_psycopg2' DATABASES['default']['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
DATABASES['default']['OPTIONS'] = {'MAX_CONNS': 1} DATABASES['default']['CONN_MAX_AGE'] = 600
# Local time zone for this installation. Choices can be found here: # Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
...@@ -234,6 +238,10 @@ CAS_PASSWORD = get_from_env('CAS_PASSWORD', "") ...@@ -234,6 +238,10 @@ CAS_PASSWORD = get_from_env('CAS_PASSWORD', "")
CAS_ELIGIBILITY_URL = get_from_env('CAS_ELIGIBILITY_URL', "") CAS_ELIGIBILITY_URL = get_from_env('CAS_ELIGIBILITY_URL', "")
CAS_ELIGIBILITY_REALM = get_from_env('CAS_ELIGIBILITY_REALM', "") CAS_ELIGIBILITY_REALM = get_from_env('CAS_ELIGIBILITY_REALM', "")
# Clever
CLEVER_CLIENT_ID = get_from_env('CLEVER_CLIENT_ID', "")
CLEVER_CLIENT_SECRET = get_from_env('CLEVER_CLIENT_SECRET', "")
# email server # email server
EMAIL_HOST = get_from_env('EMAIL_HOST', 'localhost') EMAIL_HOST = get_from_env('EMAIL_HOST', 'localhost')
EMAIL_PORT = int(get_from_env('EMAIL_PORT', "2525")) EMAIL_PORT = int(get_from_env('EMAIL_PORT', "2525"))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment