From 3f12431d04bf5b082557bd76f84558f8e260024b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Bedna=C5=99=C3=ADk?= <jan.bednarik@gmail.com>
Date: Wed, 21 Feb 2024 17:38:51 +0100
Subject: [PATCH] Pirati SSO auth system

---
 .gitignore                               |   5 +-
 helios_auth/auth_systems/__init__.py     |   3 +-
 helios_auth/auth_systems/pirati.py       | 283 +++++++++++++++++++++++
 helios_auth/media/login-icons/pirati.png | Bin 0 -> 6477 bytes
 requirements.txt                         |   3 +
 settings.py                              |  23 +-
 6 files changed, 309 insertions(+), 8 deletions(-)
 create mode 100644 helios_auth/auth_systems/pirati.py
 create mode 100755 helios_auth/media/login-icons/pirati.png

diff --git a/.gitignore b/.gitignore
index 8db764f..f81b8ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,7 @@ venv*
 celerybeat-*
 env.sh
 .cache
-.idea/
\ No newline at end of file
+.idea/
+docker-compose.yml
+.venv
+.envrc
diff --git a/helios_auth/auth_systems/__init__.py b/helios_auth/auth_systems/__init__.py
index 1818341..217e885 100644
--- a/helios_auth/auth_systems/__init__.py
+++ b/helios_auth/auth_systems/__init__.py
@@ -1,5 +1,5 @@
 from django.conf import settings
-from . import password, twitter, linkedin, cas, facebook, google, yahoo, clever, github
+from . import password, twitter, linkedin, cas, facebook, google, yahoo, clever, github, pirati
 
 AUTH_SYSTEMS = {}
 
@@ -12,6 +12,7 @@ AUTH_SYSTEMS['google'] = google
 AUTH_SYSTEMS['yahoo'] = yahoo
 AUTH_SYSTEMS['clever'] = clever
 AUTH_SYSTEMS['github'] = github
+AUTH_SYSTEMS['pirati'] = pirati
 
 # not ready
 #import live
diff --git a/helios_auth/auth_systems/pirati.py b/helios_auth/auth_systems/pirati.py
new file mode 100644
index 0000000..80fc39c
--- /dev/null
+++ b/helios_auth/auth_systems/pirati.py
@@ -0,0 +1,283 @@
+"""
+Pirati Authentication
+"""
+
+import json
+import re
+from urllib import request
+
+from celery import shared_task
+from django.core.mail import send_mail
+from django.conf import settings
+from requests_oauthlib import OAuth2Session
+from unidecode import unidecode
+
+
+# some parameters to indicate that status updating is not possible
+STATUS_UPDATES = False
+
+# display tweaks
+LOGIN_MESSAGE = "Přihlásit se pirátskou identitou"
+
+
+PIRATI_ENDPOINT_URL = f"{settings.PIRATI_REALM_URL}/protocol/openid-connect/auth"
+PIRATI_TOKEN_URL = f"{settings.PIRATI_REALM_URL}/protocol/openid-connect/token"
+PIRATI_USERINFO_URL = f"{settings.PIRATI_REALM_URL}/protocol/openid-connect/userinfo"
+
+
+###############################################################################
+# Custom helper functions
+
+
+def call_octopus_api(query, variables=None):
+    payload = json.dumps({"query": query, "variables": variables or {}}).encode("utf-8")
+    req = request.Request(settings.OCTOPUS_API_URL, method="POST")
+    req.add_header("Content-Type", "application/json; charset=utf-8")
+    req.add_header("Authorization", f"Bearer {settings.OCTOPUS_API_TOKEN}")
+    response = request.urlopen(req, payload)
+    data = json.loads(response.read())
+    if "errors" in data:
+        raise RuntimeError(
+            f"API call failed!\n - query:\n----\n{query}\n----\n - variables:\n----\n{variables}\n----\n- response:\n----\n{data}\n----\n"
+        )
+    return data
+
+
+def get_octopus_person(username):
+    query = """
+    query data ($username: String!) {
+        allPeople (first: 1, filters: {username: {iExact: $username}}) {
+            edges {
+                node {
+                    username
+                    displayName
+                    email
+                }
+            }
+        }
+    }
+    """
+    variables = {"username": username}
+    data = call_octopus_api(query, variables)
+    return data["data"]["allPeople"]["edges"][0]["node"]
+
+
+def get_octopus_groups():
+    query = """
+    query {
+        allGroups (filters: {voting: true}) {
+            edges {
+                node {
+                    id
+                    name
+                }
+            }
+        }
+    }
+    """
+    data = call_octopus_api(query)
+    return [edge["node"] for edge in data["data"]["allGroups"]["edges"]]
+
+
+def get_octopus_group(group_id):
+    query = """
+    query data ($id: GlobalID!) {
+        group (id: $id) {
+            id
+            name
+        }
+    }
+    """
+    variables = {"id": group_id}
+    data = call_octopus_api(query, variables)
+    return data["data"]["group"]
+
+
+def get_octopus_group_members(group_id):
+    query = """
+    query data ($id: GlobalID!) {
+        group (id: $id) {
+            id
+            memberships {
+                person {
+                    username
+                    displayName
+                    email
+                }
+            }
+        }
+    }
+    """
+    variables = {"id": group_id}
+    data = call_octopus_api(query, variables)
+    return [m["person"] for m in data["data"]["group"]["memberships"]]
+
+
+def group_sorter(group):
+    name = unidecode(group["name"])
+    if name == "Celostatni forum":
+        return f"0_{name}"
+
+    if re.match(r"^K[FS] ", name):
+        return f"1_{name[3:]}"
+
+    if re.match(r"^M[FS] Ch", name):
+        return f"3_{name[3:]}"
+    if re.match(r"^M[FS] [ABCDEFGH]", name):
+        return f"2_{name[3:]}"
+    if re.match(r"^M[FS] ", name):
+        return f"4_{name[3:]}"
+
+    return f"9_{name}"
+
+
+def is_old_group(group_id):
+    try:
+        int(group_id)
+        return True
+    except:
+        pass
+
+    if group_id.startswith("deadbeef-babe-"):
+        return True
+
+    return False
+
+
+def person_to_user_info(person):
+    return {
+        "type": "pirati",
+        "id": person["username"].lower(),
+        "name": person["displayName"],
+        "info": {"email": person["email"], "name": person["displayName"]},
+        "token": {},
+    }
+
+
+def old_member_to_user_info(member):
+    return {
+        "type": "pirati",
+        "id": member["username"].lower(),
+        "name": member["username"],
+        "info": {"email": member["email"], "name": member["username"]},
+        "token": {},
+    }
+
+
+###############################################################################
+# Celery tasks
+
+
+@shared_task(
+    autoretry_for=(Exception,),
+    max_retries=160,  # 3 days
+    retry_backoff=True,
+    retry_backoff_max=1800,
+)
+def send_email_task(recipient, subject, body):
+    send_mail(subject, body, settings.SERVER_EMAIL, [recipient], fail_silently=False)
+
+
+###############################################################################
+# Helios stuff
+
+can_list_category_members = True
+
+
+def get_auth_url(request, redirect_url):
+    request.session["pirate_redirect_url"] = redirect_url
+    oauth = OAuth2Session(settings.PIRATI_CLIENT_ID, redirect_uri=redirect_url)
+    url, state = oauth.authorization_url(PIRATI_ENDPOINT_URL)
+    return url
+
+
+def get_user_info_after_auth(request):
+    oauth = OAuth2Session(
+        settings.PIRATI_CLIENT_ID, redirect_uri=request.session["pirate_redirect_url"]
+    )
+    token = oauth.fetch_token(
+        PIRATI_TOKEN_URL,
+        client_secret=settings.PIRATI_CLIENT_SECRET,
+        code=request.GET["code"],
+    )
+    response = oauth.get(PIRATI_USERINFO_URL)
+    data = response.json()
+    person = get_octopus_person(data["preferred_username"])
+    info = person_to_user_info(person)
+    info["user_id"] = info.pop("id")
+    return info
+
+
+def do_logout(user):
+    return None
+
+
+def update_status(token, message):
+    pass
+
+
+def send_message(user_id, user_name, user_info, subject, body):
+    recipient = "%s <%s>" % (user_info.get("name", user_name), user_info["email"])
+    send_email_task.delay(recipient, subject, body)
+
+
+def generate_constraint(category_id, user):
+    return category_id
+
+
+def eligibility_category_id(constraint):
+    return constraint
+
+
+def check_constraint(constraint, user):
+    if is_old_group(constraint):
+        userinfo = json.load(
+            request.urlopen("https://graph.pirati.cz/user/" + user.user_id)
+        )
+        id = userinfo["id"]
+        usergroups = json.load(
+            request.urlopen("https://graph.pirati.cz/" + id + "/groups")
+        )
+        for usergroup in usergroups:
+            if usergroup["id"] == constraint:
+                return True
+    else:
+        people = get_octopus_group_members(constraint)
+        usernames = [person["username"].lower() for person in people]
+        if user.user_id in usernames:
+            return True
+    return False
+
+
+def list_categories(user):
+    return sorted(get_octopus_groups(), key=group_sorter)
+
+
+def list_category_members(category_id):
+    if is_old_group(category_id):
+        members = json.load(
+            request.urlopen("https://graph.pirati.cz/" + category_id + "/members")
+        )
+        return [old_member_to_user_info(member) for member in members]
+    else:
+        people = get_octopus_group_members(category_id)
+        return [person_to_user_info(person) for person in people]
+
+
+def pretty_eligibility(constraint):
+    if is_old_group(constraint):
+        group = json.load(request.urlopen("https://graph.pirati.cz/" + constraint))
+        name = group["username"]
+    else:
+        group = get_octopus_group(constraint)
+        name = group["name"]
+    return f"Osoby ve skupině „{name}“"
+
+
+#
+# Election Creation
+#
+
+
+def can_create_election(user_id, user_info):
+    return True
diff --git a/helios_auth/media/login-icons/pirati.png b/helios_auth/media/login-icons/pirati.png
new file mode 100755
index 0000000000000000000000000000000000000000..1c22646e755206601bf1690c9edb6d455665b688
GIT binary patch
literal 6477
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

literal 0
HcmV?d00001

diff --git a/requirements.txt b/requirements.txt
index c718cf6..f5e6b05 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,3 +22,6 @@ boto==2.49.0
 django-ses==0.8.14
 oauth2client==4.1.3
 rollbar==0.14.7
+
+requests-oauthlib==1.3.1
+unidecode==1.3.6
diff --git a/settings.py b/settings.py
index 20d2330..a7f24a3 100644
--- a/settings.py
+++ b/settings.py
@@ -14,9 +14,9 @@ def get_from_env(var, default):
     else:
         return default
 
-DEBUG = (get_from_env('DEBUG', '1') == '1')
+DEBUG = (get_from_env('DEBUG', '0') == '1')
 
-# add admins of the form: 
+# add admins of the form:
 #    ('Ben Adida', 'ben@adida.net'),
 # if you want to be emailed about errors.
 admin_email = get_from_env('ADMIN_EMAIL', None)
@@ -212,7 +212,7 @@ HELP_EMAIL_ADDRESS = get_from_env('HELP_EMAIL_ADDRESS', 'help@heliosvoting.org')
 AUTH_TEMPLATE_BASE = "server_ui/templates/base.html"
 HELIOS_TEMPLATE_BASE = "server_ui/templates/base.html"
 HELIOS_ADMIN_ONLY = False
-HELIOS_VOTERS_UPLOAD = True
+HELIOS_VOTERS_UPLOAD = (get_from_env('HELIOS_VOTERS_UPLOAD', 1) == 1)
 HELIOS_VOTERS_EMAIL = True
 
 # are elections private by default?
@@ -221,9 +221,9 @@ HELIOS_PRIVATE_DEFAULT = False
 # authentication systems enabled
 # AUTH_ENABLED_SYSTEMS = ['password','facebook','twitter', 'google', 'yahoo']
 AUTH_ENABLED_SYSTEMS = get_from_env('AUTH_ENABLED_SYSTEMS',
-                                    get_from_env('AUTH_ENABLED_AUTH_SYSTEMS', 'password,google,facebook')
+                                    get_from_env('AUTH_ENABLED_AUTH_SYSTEMS', 'pirati')
                                     ).split(",")
-AUTH_DEFAULT_SYSTEM = get_from_env('AUTH_DEFAULT_SYSTEM', get_from_env('AUTH_DEFAULT_AUTH_SYSTEM', None))
+AUTH_DEFAULT_SYSTEM = get_from_env('AUTH_DEFAULT_SYSTEM', get_from_env('AUTH_DEFAULT_AUTH_SYSTEM', 'pirati'))
 
 # google
 GOOGLE_CLIENT_ID = get_from_env('GOOGLE_CLIENT_ID', '')
@@ -295,5 +295,16 @@ if ROLLBAR_ACCESS_TOKEN:
   MIDDLEWARE += ['rollbar.contrib.django.middleware.RollbarNotifierMiddleware',]
   ROLLBAR = {
     'access_token': ROLLBAR_ACCESS_TOKEN,
-    'environment': 'development' if DEBUG else 'production',  
+    'environment': 'development' if DEBUG else 'production',
   }
+
+# auth setup
+PIRATI_REALM_URL = get_from_env('PIRATI_REALM_URL', '')
+PIRATI_CLIENT_ID = get_from_env('PIRATI_CLIENT_ID', '')
+PIRATI_CLIENT_SECRET = get_from_env('PIRATI_CLIENT_SECRET', '')
+
+OCTOPUS_API_URL = get_from_env('OCTOPUS_API_URL', '')
+OCTOPUS_API_TOKEN = get_from_env('OCTOPUS_API_TOKEN', '')
+
+if DEBUG:
+    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
-- 
GitLab