diff --git a/auth/auth_systems/__init__.py b/auth/auth_systems/__init__.py
index 39f5ef14314d57671f71fec507dd80f6508f7d42..202fe95c079bc270639b57941e33f72f53078990 100644
--- a/auth/auth_systems/__init__.py
+++ b/auth/auth_systems/__init__.py
@@ -13,3 +13,9 @@ AUTH_SYSTEMS['yahoo'] = yahoo
 # not ready
 #import live
 #AUTH_SYSTEMS['live'] = live
+
+def can_check_constraint(auth_system):
+    return hasattr(AUTH_SYSTEMS[auth_system], 'check_constraint')
+
+def can_list_categories(auth_system):
+    return hasattr(AUTH_SYSTEMS[auth_system], 'list_categories')
diff --git a/auth/auth_systems/cas.py b/auth/auth_systems/cas.py
index d5b824e86acce5bd10436c71ba8d7b32033598f5..407cccac471c8548f4af42638efe28b166538ed7 100644
--- a/auth/auth_systems/cas.py
+++ b/auth/auth_systems/cas.py
@@ -216,7 +216,7 @@ def send_message(user_id, name, user_info, subject, body):
     
   send_mail(subject, body, settings.SERVER_EMAIL, ["%s <%s>" % (name, email)], fail_silently=False)
   
-def check_constraint(constraint, user_info):
-  if not user_info.has_key('category'):
+def check_constraint(constraint, user):
+  if not user.user_info.has_key('category'):
     return False
-  return constraint['year'] == user_info['category']
+  return constraint['year'] == user.user_info['category']
diff --git a/auth/auth_systems/facebook.py b/auth/auth_systems/facebook.py
index 4dc1e7406331c224f6b422bd06bb32b9325961c0..7ace5cb0900150a47a40f99362ec0a6784483932 100644
--- a/auth/auth_systems/facebook.py
+++ b/auth/auth_systems/facebook.py
@@ -39,7 +39,7 @@ def get_auth_url(request, redirect_url):
   return facebook_url('/oauth/authorize', {
       'client_id': APP_ID,
       'redirect_uri': redirect_url,
-      'scope': 'publish_stream,email'})
+      'scope': 'publish_stream,email,user_groups'})
     
 def get_user_info_after_auth(request):
   args = facebook_get('/oauth/access_token', {
@@ -67,3 +67,45 @@ def update_status(user_id, user_info, token, message):
 def send_message(user_id, user_name, user_info, subject, body):
   if user_info.has_key('email'):
     send_mail(subject, body, settings.SERVER_EMAIL, ["%s <%s>" % (user_name, user_info['email'])], fail_silently=False)    
+
+
+##
+## eligibility checking
+##
+
+# a constraint looks like
+# {'group' : {'id': 123, 'name': 'asdfsdf'}}
+#
+# only the ID matters for checking, the name of the group is cached
+# here for ease of display so it doesn't have to be re-queried.
+
+def get_user_groups(user):
+  groups_raw = utils.from_json(facebook_get('/me/groups', {'access_token':user.token['access_token']}))
+  return groups_raw['data']    
+
+def check_constraint(constraint, user):
+  # get the groups for the user
+  groups = [group['id'] for group in get_user_groups(user.token)]
+
+  # check if one of them is the group in the constraint
+  try:
+    return constraint['group']['id'] in groups
+  except:
+    # FIXME: be more specific about exception catching
+    return False
+
+def generate_constraint(category_id, user):
+  """
+  generate the proper basic data structure to express a constraint
+  based on the category string
+  """
+  groups = get_user_groups(user)
+  the_group = [g for g in groups if g['id'] == category_id][0]
+
+  return {'group': the_group}
+
+def list_categories(user):
+  return get_user_groups(user)
+
+def pretty_eligibility(constraint):
+  return "Facebook users who are members of the \"%s\" group" % constraint['group']['name']
diff --git a/auth/models.py b/auth/models.py
index b09e873d8a166d047f73fad6884fa0b52207f32e..f511425da6498ff0e5b112069c569d4276279c88 100644
--- a/auth/models.py
+++ b/auth/models.py
@@ -12,7 +12,7 @@ from jsonfield import JSONField
 
 import datetime, logging
 
-from auth_systems import AUTH_SYSTEMS
+from auth_systems import AUTH_SYSTEMS, can_check_constraint, can_list_categories
 
 
 class User(models.Model):
@@ -111,7 +111,7 @@ class User(models.Model):
       
     for constraint in eligibility_case['constraint']:
       # do we match on this constraint?
-      if auth_system.check_constraint(constraint=constraint, user_info = self.info):
+      if auth_system.check_constraint(constraint=constraint, user = self):
         return True
   
     # no luck
diff --git a/auth/urls.py b/auth/urls.py
index cedf65e3d0d47548c898aae5ebc5cbcb23db83d5..57e0f0b87ca3bf322398f30fcaddb2c41d5eadf8 100644
--- a/auth/urls.py
+++ b/auth/urls.py
@@ -15,7 +15,8 @@ urlpatterns = patterns('',
     (r'^$', index),
     (r'^logout$', logout),
     (r'^start/(?P<system_name>.*)$', start),
-    (r'^after$', after),
+    # weird facebook constraint for trailing slash
+    (r'^after/$', after),
     (r'^after_intervention$', after_intervention),
     
     ## should make the following modular
diff --git a/helios/election_urls.py b/helios/election_urls.py
index 1b9b6aae872ff15effe6a9151c47db669c441850..31760a5944145aacc6197a05cf44e18d7229315d 100644
--- a/helios/election_urls.py
+++ b/helios/election_urls.py
@@ -71,6 +71,7 @@ urlpatterns = patterns('',
     (r'^/voters/upload$', voters_upload),
     (r'^/voters/upload-cancel$', voters_upload_cancel),
     (r'^/voters/list$', voters_list_pretty),
+    (r'^/voters/eligibility$', voters_eligibility),
     (r'^/voters/email$', voters_email),
     (r'^/voters/(?P<voter_uuid>[^/]+)$', one_voter),
     (r'^/voters/(?P<voter_uuid>[^/]+)/delete$', voter_delete),
diff --git a/helios/models.py b/helios/models.py
index 6e3f7d78ed527f0b971b37e933f42193167961cc..f0ca2468eebae6411b159cc0b929a0a8f9e43313 100644
--- a/helios/models.py
+++ b/helios/models.py
@@ -243,6 +243,27 @@ class Election(HeliosModel):
         return True
         
     return False
+
+  def eligibility_constraint_for(self, user_type):
+    if not self.eligibility:
+      return []
+
+    return [constraint['constraint'] for constraint in self.eligibility if constraint['auth_system'] == user_type][0]
+
+  @property
+  def pretty_eligibility(self):
+    if not self.eligibility:
+      return "Everyone can vote."
+    else:
+      return_val = "Only the following users can vote:<ul>"
+      
+      for constraint in self.eligibility:
+        for one_constraint in constraint['constraint']:
+          return_val += "<li>%s</li>" % AUTH_SYSTEMS[constraint['auth_system']].pretty_eligibility(one_constraint)
+
+      return_val += "</ul>"
+
+      return return_val
   
   def voting_has_started(self):
     """
diff --git a/helios/templates/voters_eligibility.html b/helios/templates/voters_eligibility.html
new file mode 100644
index 0000000000000000000000000000000000000000..eb40311348cbec764bd2bd66b0595cd0629c109a
--- /dev/null
+++ b/helios/templates/voters_eligibility.html
@@ -0,0 +1,26 @@
+{% extends TEMPLATE_BASE %}
+
+{% block title %}Voter Eligibility for {{election.name}}{% endblock %}
+{% block content %}
+  <h2 class="title">{{election.name}} &mdash; Voter Eligibility <span style="font-size:0.7em;">[<a href="{% url helios.views.voters_list_pretty election.uuid %}">back to voters</a>]</span></h2>
+
+<p>
+<em>{{election.pretty_eligibility|safe}}</em>
+</p>
+
+<p>
+You may limit eligibility of voters to one of these categories, as defined by {{user.user_type}}:
+</p>
+
+<form method="post" action="">
+<input type="hidden" name="csrf_token" value="{{csrf_token}}" />
+<select name="category_id">
+<option value="" SELECTED>(no constraint)</option>
+{% for category in categories %}
+<option value="{{category.id}}"> {{category.name}}</option>
+{% endfor %}
+</select>
+<input type="submit" value="set eligibility" />
+</form>
+</ul>
+{% endblock %}
diff --git a/helios/templates/voters_list.html b/helios/templates/voters_list.html
index 588fb046688ac28d8066135edb8860a58df2d55d..6bdd14fa7f259a6033d260a46edc49e172c68e3c 100644
--- a/helios/templates/voters_list.html
+++ b/helios/templates/voters_list.html
@@ -9,6 +9,16 @@
 {% if admin_p and not election.frozen_at %}
 {% if election.openreg %}
 [<a href="{% url helios.views.one_election_set_reg election.uuid %}?open_p=0">switch to closed</a>]
+
+{% if can_set_eligibility %}
+<p>
+<em>{{election.pretty_eligibility|safe}}</em>
+<br />
+You can <a href="{% url helios.views.voters_eligibility election.uuid %}">limit the eligibility</a> of voters based on their {{user.user_type}} credentials.
+</p>
+{% endif %}
+
+
 {% else %}
 {% if election.private_p %}
 <br />
diff --git a/helios/views.py b/helios/views.py
index 33efb07fbc7a3abf25d3a95b7e4f0536cf817357..2a82eae8de22d6dcd982774d8d2280b6134a7bd6 100644
--- a/helios/views.py
+++ b/helios/views.py
@@ -20,7 +20,10 @@ from crypto import utils as cryptoutils
 from workflows import homomorphic
 from helios import utils as helios_utils
 from view_utils import *
+
 from auth.security import *
+from auth.auth_systems import AUTH_SYSTEMS, can_list_categories
+
 from helios import security
 from auth import views as auth_views
 
@@ -1110,6 +1113,11 @@ def voters_list_pretty(request, election):
 
   user = get_user(request)
   admin_p = security.user_can_admin_election(user, election)
+
+  if admin_p and election.openreg:
+    can_set_eligibility = can_list_categories(user.user_type)
+  else:
+    can_set_eligibility = False
   
   # files being processed
   voter_files = election.voterfile_set.all()
@@ -1129,13 +1137,42 @@ def voters_list_pretty(request, election):
 
   total_voters = voter_paginator.count
     
-  return render_template(request, 'voters_list', {'election': election, 'voters_page': voters_page,
-                                                  'voters': voters_page.object_list, 'admin_p': admin_p, 
-                                                  'email_voters': helios.VOTERS_EMAIL,
-                                                  'limit': limit, 'total_voters': total_voters,
-                                                  'upload_p': helios.VOTERS_UPLOAD, 'q' : q,
-                                                  'voter_files': voter_files})
+  return render_template(request, 'voters_list', 
+                         {'election': election, 'voters_page': voters_page,
+                          'voters': voters_page.object_list, 'admin_p': admin_p, 
+                          'email_voters': helios.VOTERS_EMAIL,
+                          'limit': limit, 'total_voters': total_voters,
+                          'upload_p': helios.VOTERS_UPLOAD, 'q' : q,
+                          'voter_files': voter_files,
+                          'can_set_eligibility': can_set_eligibility})
+
+@election_admin()
+def voters_eligibility(request, election):
+  user = get_user(request)
+
+  if not can_list_categories(user.user_type):
+    return HttpResponseRedirect(reverse(voters_list_pretty, args=[election.uuid]))
 
+  if request.method == "GET":
+    categories = AUTH_SYSTEMS[user.user_type].list_categories(user)
+
+    return render_template(request, 'voters_eligibility',
+                           {'categories' : categories, 'election': election})
+
+  # now process the constraint
+  category_id = request.POST['category_id']
+  if category_id == "":
+    category_id = None
+
+  if category_id:
+    constraint = AUTH_SYSTEMS[user.user_type].generate_constraint(category_id, user)
+    election.eligibility = [{'auth_system': user.user_type, 'constraint': [constraint]}]
+  else:
+    election.eligibility = None
+  
+  election.save()
+  return HttpResponseRedirect(reverse(voters_eligibility, args=[election.uuid]))
+  
 @election_admin()
 def voters_upload(request, election):
   """