Skip to content
Snippets Groups Projects
Verified Commit 5a5b1724 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Add login flow for Nextcloud Talk

parent 46bb7663
No related branches found
No related tags found
No related merge requests found
...@@ -132,3 +132,7 @@ class EditTermForm(forms.ModelForm): ...@@ -132,3 +132,7 @@ class EditTermForm(forms.ModelForm):
class Meta: class Meta:
model = SchoolTerm model = SchoolTerm
fields = ["caption", "date_start", "date_end"] fields = ["caption", "date_start", "date_end"]
class NextcloudServerForm(forms.Form):
url = forms.URLField(required=True, label=_("URL of your Nextcloud instance"))
...@@ -87,6 +87,15 @@ MENUS = { ...@@ -87,6 +87,15 @@ MENUS = {
"menu_generator.validators.is_superuser", "menu_generator.validators.is_superuser",
], ],
}, },
{
"name": _("Third-party services"),
"url": "third_party_services",
"icon": "share",
"validators": [
"menu_generator.validators.is_authenticated",
"menu_generator.validators.is_superuser",
],
},
{ {
"name": _("Backend Admin"), "name": _("Backend Admin"),
"url": "admin:index", "url": "admin:index",
......
...@@ -371,6 +371,9 @@ CONSTANCE_CONFIG = { ...@@ -371,6 +371,9 @@ CONSTANCE_CONFIG = {
"IMPRINT_URL": ("", _("Link to imprint"), "url_field"), "IMPRINT_URL": ("", _("Link to imprint"), "url_field"),
"ADRESSING_NAME_FORMAT": ("german", _("Name format of adresses"), "adressing-select"), "ADRESSING_NAME_FORMAT": ("german", _("Name format of adresses"), "adressing-select"),
"NOTIFICATION_CHANNELS": (["email"], _("Channels to allow for notifications"), "notifications-select"), "NOTIFICATION_CHANNELS": (["email"], _("Channels to allow for notifications"), "notifications-select"),
"NEXTCLOUD_TALK_SERVER": ("", _("Nextcloud Talk server configured by connection service"), "url_field"),
"NEXTCLOUD_TALK_LOGIN_NAME": ("", _("Nextcloud Talk server configured by connection service"), str),
"NEXTCLOUD_TALK_APP_PASSWORD": ("", _("Nextcloud Talk server configured by connection service"), str),
} }
CONSTANCE_CONFIG_FIELDSETS = { CONSTANCE_CONFIG_FIELDSETS = {
"General settings": ("SITE_TITLE",), "General settings": ("SITE_TITLE",),
......
var POLL_INTERVAL = 500;
var w = null;
function poll() {
$.ajax({
url: Urls.pollNextcloudTalk(),
}).done(function (data) {
if (data.done) {
console.log("Polling done");
// Redirect to third-party services home view
window.location.href = Urls.thirdPartyServices();
w.close();
} else {
window.setTimeout(poll, POLL_INTERVAL);
}
}).fail(function () {
window.setTimeout(poll, POLL_INTERVAL);
})
}
$(document).ready(function () {
// Open login URL
var loginData = getJSONScript("login_data");
w = window.open(loginData.login);
console.log("Start polling");
poll();
});
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load i18n material_form static %}
{% block browser_title %}{% blocktrans %}Connect to Nextcloud Talk{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}Connect to Nextcloud Talk{% endblocktrans %}{% endblock %}
{% block content %}
{% if step == 1 %}
<form action="" method="post">
{% csrf_token %}
{% form form=form %}{% endform %}
<button class="btn green waves-effect waves-light" type="submit" name="step-1">
<i class="material-icons left">open_in_new</i>
{% trans "Open login page" %}
</button>
</form>
{% elif step == 2 %}
{{ login_data|json_script:"login_data" }}
<p class="flow-text">
{% blocktrans %}
Waiting for successful login ...
{% endblocktrans %}
</p>
<p>
{% blocktrans with url=login_data.login %}
Login window has not opened?
<a href="{{ url }}" target="_blank">Try again.</a>
{% endblocktrans %}
</p>
<script src="{% static "js/helper.js" %}"></script>
<script src="{% static "js/nextcloud_talk.js" %}"></script>
{% endif %}
{% endblock %}
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load i18n %}
{% block browser_title %}{% blocktrans %}Third-party services{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}Third-party services{% endblocktrans %}{% endblock %}
{% block content %}
<div class="card">
<div class="card-content">
{% if nextcloud.connected %}
<span class="badge new green right">{% trans "Connected" %}</span>
{% else %}
<span class="badge new red right">{% trans "Not connected" %}</span>
{% endif %}
<span class="card-title">{% trans "Nextcloud Talk" %}</span>
{% if nextcloud.connected %}
<p>
{% blocktrans with login_name=nextcloud.login_name %}
Connected Nextcloud user: {{ login_name }}
{% endblocktrans %}
</p>
<br/>
<a class="btn red waves-effect waves-light" href="{% url "disconnect_nextcloud_talk" %}">
<i class="material-icons left">leak_remove</i>
{% trans "Disconnect" %}
</a>
{% else %}
<p>
{% blocktrans %}
In order to send notifications by Nextcloud Talk you must connect a Nextcloud user which should send the
notifications to the users.
{% endblocktrans %}
</p>
<br/>
<a class="btn green waves-effect waves-light" href="{% url "connect_nextcloud_talk" %}">
<i class="material-icons left">leak_add</i>
{% trans "Connect" %}
</a>
{% endif %}
</div>
</div>
{% endblock %}
...@@ -19,6 +19,10 @@ urlpatterns = [ ...@@ -19,6 +19,10 @@ urlpatterns = [
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("data_management/", views.data_management, name="data_management"), path("data_management/", views.data_management, name="data_management"),
path("status/", views.system_status, name="system_status"), path("status/", views.system_status, name="system_status"),
path("third_party/", views.third_party_services, name="third_party_services"),
path("third_party/nextcloud_talk/", views.connect_nextcloud_talk, name="connect_nextcloud_talk"),
path("third_party/nextcloud_talk/disconnect/", views.disconnect_nextcloud_talk, name="disconnect_nextcloud_talk"),
path("third_party/nextcloud_talk/poll/", views.poll_nextcloud_talk, name="poll_nextcloud_talk"),
path("school_management", views.school_management, name="school_management"), path("school_management", views.school_management, name="school_management"),
path("school/information/edit", views.edit_school, name="edit_school_information"), path("school/information/edit", views.edit_school, name="edit_school_information"),
path("school/term/edit", views.edit_schoolterm, name="edit_school_term"), path("school/term/edit", views.edit_schoolterm, name="edit_school_term"),
......
from typing import Union
import requests
from constance import config
HEADERS = {
"User-Agent": "AlekSIS",
}
INITIATE_LOGIN_PROCESS_URL = "index.php/login/v2"
def initiate_login_process(nextcloud_url: str) -> dict:
url = nextcloud_url + INITIATE_LOGIN_PROCESS_URL
r = requests.post(url, headers=HEADERS)
return r.json()
def login_process_poll(endpoint: str, token: str) -> Union[dict, bool]:
r = requests.post(endpoint, headers=HEADERS, data={"token": token})
if r.status_code != 200:
return False
return r.json()
def is_connected() -> bool:
return (
config.NEXTCLOUD_TALK_SERVER != ""
and config.NEXTCLOUD_TALK_LOGIN_NAME != ""
and config.NEXTCLOUD_TALK_APP_PASSWORD
)
from typing import Optional from typing import Optional
from constance import config
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpRequest, HttpResponse from django.http import Http404, HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -15,10 +16,11 @@ from .forms import ( ...@@ -15,10 +16,11 @@ from .forms import (
EditSchoolForm, EditSchoolForm,
EditTermForm, EditTermForm,
PersonsAccountsFormSet, PersonsAccountsFormSet,
) NextcloudServerForm)
from .models import Activity, Group, Notification, Person, School, DashboardWidget from .models import Activity, Group, Notification, Person, School, DashboardWidget
from .tables import GroupsTable, PersonsTable from .tables import GroupsTable, PersonsTable
from .util import messages from .util import messages, nextcloud
from .util.nextcloud import initiate_login_process, login_process_poll
@person_required @person_required
...@@ -264,3 +266,80 @@ def notification_mark_read(request: HttpRequest, id_: int) -> HttpResponse: ...@@ -264,3 +266,80 @@ def notification_mark_read(request: HttpRequest, id_: int) -> HttpResponse:
raise PermissionDenied(_("You are not allowed to mark notifications from other users as read!")) raise PermissionDenied(_("You are not allowed to mark notifications from other users as read!"))
return redirect("index") return redirect("index")
@admin_required
def third_party_services(request: HttpRequest) -> HttpResponse:
context = {}
context["nextcloud"] = {
"connected": nextcloud.is_connected(),
"login_name": config.NEXTCLOUD_TALK_LOGIN_NAME
}
return render(request, "core/third_party_services.html", context)
@admin_required
def connect_nextcloud_talk(request: HttpRequest) -> HttpResponse:
context = {}
if request.method == "GET":
form = NextcloudServerForm()
context["step"] = 1
context["form"] = form
elif request.method == "POST":
if "step-1" in request.POST:
form = NextcloudServerForm(request.POST)
if form.is_valid():
url = form.cleaned_data["url"]
if url[-1] != "/":
url += "/"
r = initiate_login_process(url)
request.session["nextcloud_poll"] = True
request.session["nextcloud_login_data"] = r
context["login_data"] = r
context["step"] = 2
return render(request, "core/connect_nextcloud_talk.html", context)
@admin_required
def disconnect_nextcloud_talk(request: HttpRequest) -> HttpResponse:
config.NEXTCLOUD_TALK_SERVER = ""
config.NEXTCLOUD_TALK_LOGIN_NAME = ""
config.NEXTCLOUD_TALK_APP_PASSWORD = ""
messages.success(request, _("The Nextcloud user was successfully disconnected ."))
return redirect("third_party_services")
@admin_required
def poll_nextcloud_talk(request: HttpRequest) -> HttpResponse:
done = False
if request.session.get("nextcloud_poll", False):
login_data = request.session["nextcloud_login_data"]
r = login_process_poll(login_data["poll"]["endpoint"], login_data["poll"]["token"])
done = r != False
if done:
config.NEXTCLOUD_TALK_SERVER = r["server"]
config.NEXTCLOUD_TALK_LOGIN_NAME = r["loginName"]
config.NEXTCLOUD_TALK_APP_PASSWORD = r["appPassword"]
messages.success(request, _("The Nextcloud user was successfully connected."))
del request.session["nextcloud_login_data"]
del request.session["nextcloud_poll"]
return JsonResponse({"done": done})
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment