From 2637db42f7e43b5094b61d61b03e5dd583d9b8d4 Mon Sep 17 00:00:00 2001
From: Roman Karwacik <romank@fsmpi.rwth-aachen.de>
Date: Mon, 27 Apr 2020 17:41:37 +0200
Subject: [PATCH] Added Moodle authentication

---
 l2pauth.py            |  21 ++++++++++++++++++++-
 server.py             |   5 +++++
 static/moderator.js   |  18 ++++++++++++++++++
 static/moodle.png     | Bin 0 -> 1319 bytes
 template_helper.py    |  10 ++++++++++
 templates/course.html |   2 ++
 templates/faq.html    |  12 ++++++------
 templates/macros.html |   3 +++
 8 files changed, 64 insertions(+), 7 deletions(-)
 create mode 100644 static/moodle.png

diff --git a/l2pauth.py b/l2pauth.py
index 3c971e7..4944806 100644
--- a/l2pauth.py
+++ b/l2pauth.py
@@ -3,12 +3,18 @@ import requests
 
 L2P_BASE = 'https://www3.elearning.rwth-aachen.de/_vti_bin/l2pservices/api.svc/v1/'
 OAUTH_BASE = 'https://oauth.campus.rwth-aachen.de/oauth2waitress/oauth2.svc/'
+MOODLE_BASE = 'https://moped.ecampus.rwth-aachen.de/proxy/api/v2/eLearning/Moodle/'
 
 def l2pget(endpoint, token, **args):
 	args['accessToken'] = token
 	r = requests.request('GET', L2P_BASE+endpoint, params=args)
 	return r.json()
 
+def moodleget(endpoint, token, **args):
+	args['token'] = token
+	r = requests.request('GET', MOODLE_BASE+endpoint, params=args)
+	return r.json()
+
 def oauthget(endpoint, **args):
 	args['client_id'] = config['L2P_APIKEY']
 	r = requests.request('POST', OAUTH_BASE+endpoint, data=args)
@@ -23,6 +29,15 @@ def start_l2pauth():
 	session['oauthscope'] = 'l2p'
 	return redirect(code['verification_url']+'?q=verify&d='+code['user_code'])
 
+@app.route('/internal/moodleauth')
+def start_moodleauth():
+	if 'L2P_APIKEY' not in config:
+		return render_template("500.html"), 500
+	code = oauthget('code', scope='moodle.rwth')
+	session['oauthcode'] = code['device_code']
+	session['oauthscope'] = 'moodle'
+	return redirect(code['verification_url']+'?q=verify&d='+code['user_code'])
+
 @app.route('/internal/rwthauth')
 def start_rwthauth():
 	if 'L2P_APIKEY' not in config:
@@ -42,12 +57,16 @@ def finish_oauth():
 	if token.get('status') != 'ok':
 		return
 	del session['oauthcode']
-	if session['oauthscope'] not in ['l2p', 'rwth']:
+	if session['oauthscope'] not in ['l2p', 'rwth', 'moodle']:
 		return
 	session['rwthintern'] = True
 	if session['oauthscope'] == 'l2p':
 		session['l2p_courses'] = []
 		for course in l2pget('viewAllCourseInfo', token['access_token'])['dataSet']:
 			session['l2p_courses'].append(course['uniqueid'])
+	elif session['oauthscope'] == 'moodle':
+		session['moodle_courses'] = []
+		for course in moodleget('getmyenrolledcourses', token['access_token'])['Data']:
+			session['moodle_courses'].append(str(course['id']))
 	del session['oauthscope']
 	oauthget('token', refresh_token=token['refresh_token'], grant_type='invalidate')
diff --git a/server.py b/server.py
index 4c8ef69..6e969ee 100644
--- a/server.py
+++ b/server.py
@@ -332,6 +332,11 @@ def lecture(id, course=None, courseid=None):
 				flash(text+'. Du bist kein Teilnehmer des L2P-Kurses! <a target="_blank" class="reloadonclose" href="'+url_for('start_l2pauth')+'">Kurse aktualisieren</a>.', category='player')
 			else:
 				flash(text+'. <a target="_blank" class="reloadonclose" href="'+url_for('start_l2pauth')+'">Hier authorisieren</a>.', category='player')
+		elif mode == 'moodle':
+			if 'moodle_courses' in session:
+				flash(text+'. Du bist kein Teilnehmer des Moodle-Kurses! <a target="_blank" class="reloadonclose" href="'+url_for('start_moodleauth')+'">Kurse aktualisieren</a>.', category='player')
+			else:
+				flash(text+'. <a target="_blank" class="reloadonclose" href="'+url_for('start_moodleauth')+'">Hier authorisieren</a>.', category='player')
 		else:
 			flash(text+'.', category='player')
 	return render_template('embed.html' if request.endpoint == 'embed' else 'lecture.html', course=courses[0], lecture=lecture, videos=videos, chapters=chapters, seek=request.args.get('t'))
diff --git a/static/moderator.js b/static/moderator.js
index 306d1f5..524cc1d 100644
--- a/static/moderator.js
+++ b/static/moderator.js
@@ -197,6 +197,9 @@ var moderator = {
 							case 'l2p':
 								permstring = '(' + perm.param1 + ')'
 								break;
+							case 'moodle':
+								permstring = '(' + perm.param1 + ')'
+								break;
 						}
 						html += '<option data-id="'+perm.id+'" data-type="'+perm.type+'" data-param1="'+perm.param1+'" data-param2="'+perm.param2+'">#'+perm.id+' '+perm.type+' '+ permstring +'</option>';
 					}
@@ -220,6 +223,9 @@ var moderator = {
 				case 'l2p':
 					$(".authl2p", container).val(perm.param1);
 					break
+				case 'moodle':
+					$(".authmoodle", container).val(perm.param1);
+					break
 			}
 			$(".authtype option[value="+perm.type+"]").prop("selected", true);
 		},
@@ -266,6 +272,9 @@ var moderator = {
 				case 'l2p':
 					perm.param1 = $(".authl2p", container).val();
 					break
+				case 'moodle':
+					perm.param1 = $(".authmoodle", container).val();
+					break
 			}
 			return perm;
 		},
@@ -277,18 +286,27 @@ var moderator = {
 			$(".authuser", container).val('');
 			$(".authpassword", container).val('');
 			$(".authl2p", container).val('');
+			$(".authmoodle", container).val('');
 			switch (type) {
 				case 'password':
 					$(".passwordinput",container).show();
 					$(".authl2p",container).hide();
+					$(".authmoodle",container).hide();
 					break;
 				case 'l2p':
 					$(".passwordinput",container).hide();
 					$(".authl2p",container).show();
+					$(".authmoodle",container).hide();
+					break;
+				case 'moodle':
+					$(".passwordinput",container).hide();
+					$(".authl2p",container).hide();
+					$(".authmoodle",container).show();
 					break;
 				default:
 					$(".passwordinput",container).hide();
 					$(".authl2p",container).hide();
+					$(".authmoodle",container).hide();
 					break;
 			}
 		},
diff --git a/static/moodle.png b/static/moodle.png
new file mode 100644
index 0000000000000000000000000000000000000000..29ac72931fcf4950b9365bd82ece1cc9025f0e71
GIT binary patch
literal 1319
zcmeAS@N?(olHy`uVBq!ia0y~yU@!t<4mJh`208nVjSLKo`I*kn0h!6k3=A3*OD9@;
zGdqg3+6%jWjMUJ`n&@=YDNy7{Q1V1Ij}ESVv)-gl=BU)!Ev<5c%S?=s`{J?ihxQl3
zHiQbWxC(6iApRpuWledEqT?f-&EFrNnREJ#<cHSFRmaxJw)$M#xM*QQLSd$5z<(!Q
z+bx$)diZQV`_e<@%>Kup`=mni+e6p?{~jvqTX^`|!ex#XPUgDq6<!v5-p%OR-fI0N
zL`Sbi!zSe<W9#}U>ni`Ov3U7$cT%>U6|>z{;|XgL);4{eGF4Dc<lX5dpMSltyy82n
z_syX>SMp}=z2U+ssM6pz&-9W7kM~)xw5zp??e|3%J-YZbKj?sv`=luq?Jj4%!=1C7
zN}TtZuulDb-{$R+A2Y7+wd5%EU-$bO|5xh(tzGY~PkcSC@8Ew9@td3M)h&JNI^uM)
zYuPVs<4m8P>U+EOv+j#3gXFL`?w?M7Q5E`7#ii?gp^mv%J1%Vh>p6E0|7o+iX!_>u
z;f6Q)(SO;PxHGt?OjVuDz`(#+;1OBOz`!j8!i<;h*8O2%V2<;2aSX|DemnE#YTrPa
z<LfKAB#l}o%$)GBGe}OP@}aAo%K4yIVxE<s;wwHp@N83(Q|WZ_G@7nx<oZn0=<{Eh
z>bc*p-rK+RyVdVI#rbRFYptKJx&Ny-`^VpJH*<3*Cb7ZbA>#|$F(wg33sl^>Cbza8
zDu3v_VVlc_^2J<5PuMeRdJGf`C6*toNGxH>c`v9J`p~&wYl_Eli^RSS9uW#>JdU-!
z+??Llt1gu-&KH_uAK@GCsVBCLTfHUy(EKKgMU}x#y5TAo-c@BB;w}2DvgM3*J}I6~
z-W-RuTmK%qJ?Y$Tg>@czmEL=%2lBnCdf4;9+JJ#+r%u6(*<O+Dd;4|PiEiWa*4psT
z(({;KZQS__$4hO3XXq7V@`!10=iCiH<os~mhaElo+9p3km~U?9`xE@~?gWcYf9KZR
zL!l2>AAjckv7h_RgnyNcUxk~qKRZlj`eDDLcGF?6`Mg`oql)g#knLD&YTjA0$S$Ar
zJ`0}(qmb=`-yDg)hfaHYT`b&od%>c~#?SU%xS`g#dO!0<`}As&-DQh1_GWzB^Ze8F
z&7sQSrIrQz%34pIb-fVUA(pYd`=I4{G392ljajKL8(DUA-k4>V<Sj3Dj$5W~(_X)X
z)6Xwv8CpnPzr*rNnN7z^BkkhbPaehPoQqp@5Bc}&^yQy1u=7ox6Mb6GHhS)}*U}qa
z%vd;~$y52noU=@;A9TE}KKJ-c;9-OCZJ`ssE4|)!jlcH%-uc4H*M3&4wOrjYTaxb~
zOZc;(!#5|h^{ea=ZhL-aVaXMK>+@Wn&wTw+uKN6<wBbAHny!VeV)GU5NgoQn+^BQ8
z-))xFxrVPf%=eYL&v*TL@;W+ul3Mik=HvZRx4D)~WZ&VmX<grj++$o<CJSh#oC$a{
z`TNJ$=`Dw?Tr!sAa(FtV8s)j>c`I)Zns_L5=0`2joep|lnt$uVvmCvSzGPZ>B-pWb
z>yjy-&dqtl^iyT=7t6@{yCuu)4{p08tG`!7bYFCkm4IB>F5Rg&z4n>yU+SqL?BOl@
zRZ?HrBStXZO!~?7TjKYNt8N|B|H}}0WXIN|k`0xcUTjWz`1Gcme*cN3OgoqV+;jWE
zQJ+~JZ2wsnne<o0?Nm&OeV=lr>Y}rNNVZjfiP47R(GAvZcTaRlc0i%Vl*ax0mF!e*
UugGm+U|?YIboFyt=akR{0O7%8&j0`b

literal 0
HcmV?d00001

diff --git a/template_helper.py b/template_helper.py
index 2effb59..cece694 100644
--- a/template_helper.py
+++ b/template_helper.py
@@ -91,6 +91,9 @@ def checkperm(perms, username=None, password=None):
 		elif perm['type'] == 'l2p':
 			if perm['param1'] in session.get('l2p_courses', []):
 				return True
+		elif perm['type'] == 'moodle':
+			if perm['param1'] in session.get('moodle_courses', []):
+				return True
 		elif perm['type'] == 'rwth':
 			if session.get('rwthintern', False):
 				return True
@@ -108,6 +111,7 @@ def permdescr(perms):
 	public = False
 	password = False
 	l2p_courses = []
+	moodle_courses = []
 	rwth_intern = False
 	fsmpi_intern = False
 	for perm in perms:
@@ -117,6 +121,8 @@ def permdescr(perms):
 			password = True
 		elif perm['type'] == 'l2p':
 			l2p_courses.append(perm['param1'])
+		elif perm['type'] == 'moodle':
+			moodle_courses.append(perm['param1'])
 		elif perm['type'] == 'rwth':
 			rwth_intern = True
 		elif perm['type'] == 'fsmpi':
@@ -133,6 +139,10 @@ def permdescr(perms):
 		if password:
 			return 'l2p', 'Nur für Teilnehmer der Veranstaltung und Nutzer mit Passwort verfügbar'
 		return 'l2p', 'Nur für Teilnehmer der Veranstaltung verfügbar'
+	if moodle_courses:
+		if password:
+			return 'moodle', 'Nur für Teilnehmer der Veranstaltung und Nutzer mit Passwort verfügbar'
+		return 'moodle', 'Nur für Teilnehmer der Veranstaltung verfügbar'
 	if password:
 		return 'password', 'Nur für Nutzer mit Passwort verfügbar'
 	return 'none', 'Nicht verfügbar'
diff --git a/templates/course.html b/templates/course.html
index 2bd5d92..a43010e 100644
--- a/templates/course.html
+++ b/templates/course.html
@@ -126,12 +126,14 @@
 						<option value="rwth">RWTH intern</option>
 						<option value="fsmpi">FSMPI intern</option>
 						<option value="l2p">L2P Lernraum</option>
+						<option value="moodle">Moodle Lernraum</option>
 						<option value="none">Kein Zugriff</option>
 					</select>
 					<input class="col-xs-12 passwordinput authuser" type="text" placeholder="Benutzername">
 					<input class="col-xs-10 passwordinput authpassword" type="text" placeholder="Passwort">
 					<button class="col-xs-2 passwordinput authpgen" type="button" onclick="$('.authpassword',this.parentNode).val(moderator.permissioneditor.randompw());"><span class="fa fa-refresh" aria-hidden="true"></span></button>
 					<input class="col-xs-12 authl2p" type="text" placeholder="Lernraum" style="display: none;">
+					<input class="col-xs-12 authmoodle" type="text" placeholder="Lernraum" style="display: none;">
 					<button class="col-xs-4" onclick="moderator.permissioneditor.addbtnclick(this)">Add</button>
 					<button class="col-xs-4" onclick="moderator.permissioneditor.updatebtnclick(this)">Update</button>
 					<button class="col-xs-4" onclick="moderator.permissioneditor.delbtnclick(this)">Delete</button>
diff --git a/templates/faq.html b/templates/faq.html
index 698b8fb..7e2e5d6 100644
--- a/templates/faq.html
+++ b/templates/faq.html
@@ -53,7 +53,7 @@
 		<ul>
 			<li><strong>Öffentliche</strong> Videos (<span class="fa fa-globe-asia"></span>) können von jedem abgespielt werden.</li>
 			<li><strong>RWTH-interne</strong> Videos (<span class="fa" style="width: 25px; height: 17px; background-size: cover; background-image: url('/static/rwth.png');"></span>) sind nur für RWTH-Angehörige verfügbar. Zum Anschauen ist eine Authentifizierung über das RWTH-Single-Sign-On nötig.</li>
-			<li><strong>Lernraum-interne</strong> Videos (<span class="fa" style="width: 12px; height: 14px; background-size: cover; background-image: url('/static/l2p-logo.gif');"></span>) sind nur für Teilnehmer eines bestimmten L2P-Lernraums zugänglich. Wir überprüfen dies, indem du uns kurzzeitig Zugriff auf deinen L2P-Account gibst, aus dem wir die Liste deiner Lernräume auslesen. Solltest du eine Vorlesung hören, dem zugehörigen Lernraum aber nicht hinzugefügt worden sein, melde dich bitte beim entsprechenden Dozenten.</li>
+			<li><strong>Lernraum-interne</strong> Videos (<span class="fa" style="width: 12px; height: 14px; background-size: cover; background-image: url('/static/l2p-logo.gif');"></span> oder <span class="fa" style="width: 12px; height: 14px; background-size: cover; background-image: url('/static/moodle.png');"></span>) sind nur für Teilnehmer eines bestimmten L2P- oder Moodle-Lernraums zugänglich. Wir überprüfen dies, indem du uns kurzzeitig Zugriff auf deinen L2P- oder Moodle-Account gibst, aus dem wir die Liste deiner Lernräume auslesen. Solltest du eine Vorlesung hören, dem zugehörigen Lernraum aber nicht hinzugefügt worden sein, melde dich bitte beim entsprechenden Dozenten.</li>
 			<li><strong>Passwort-geschützte</strong> Videos (<span class="fa fa-lock"></span>) sind nur nach Eingabe eines Passworts verfügbar, welches jedem zur Verfügung gestellt wurde, der Zugriff haben sollte. In der Regel gibt es gute Gründe, warum dieser Zugriffsschutz gewählt wurde.</li>
 		</ul>
 		<p>Falls du keinen Zugriff auf ein Video hast, wird dies auf der Player-Seite angezeigt und ggf. auf die weiteren Schritte zur Authentifizierung verwiesen. Die erläuterten Möglichkeiten decken nicht jeden denkbaren Fall ab. Solltest du Zugriff auf eine Veranstaltung benötigen, aber aus irgendwelchen Gründen nicht haben, schreib uns eine Mail.</p>
@@ -66,15 +66,15 @@
 	{% endcall %}
 
 	<div class="faqHeader">Technisches</div>
-	{% call faqentry("l2prights", "Warum benötigt ihr lesenden und schreibenden Zugriff auf meine L2P-Lernräume?") %}
-		<p>Auf vielfachen Wunsch unserer Dozenten haben wir die Möglichkeit umgesetzt, Videos nur für Teilnehmer eines bestimmten L2P-Lernraums zugänglich zu machen.
-		Um dies umzusetzen benötigen wir die Liste der Lernräume eines Nutzers, und damit Zugriff auf den L2P-Account.
-		Leider erlaubt uns das L2P nur entweder keinen oder vollen (also lesenden und schreibenden) Zugriff, sodass du uns zur Authentifizierung für Lernraum-interne Videos mehr Zugriffsrechte geben musst, als theoretisch nötig.</p>
+	{% call faqentry("l2prights", "Warum benötigt ihr lesenden und schreibenden Zugriff auf meine L2P oder Moodle-Lernräume?") %}
+		<p>Auf vielfachen Wunsch unserer Dozenten haben wir die Möglichkeit umgesetzt, Videos nur für Teilnehmer eines bestimmten L2P- oder Moodle-Lernraums zugänglich zu machen.
+		Um dies umzusetzen benötigen wir die Liste der Lernräume eines Nutzers, und damit Zugriff auf den L2P- oder Moodle-Account.
+		Leider erlaubt uns das L2P sowie Moodle nur entweder keinen oder vollen (also lesenden und schreibenden) Zugriff, sodass du uns zur Authentifizierung für Lernraum-interne Videos mehr Zugriffsrechte geben musst, als theoretisch nötig.</p>
 		<p>Um die Missbrauchsgefahr zu minimieren, widerrufen wir die Zugriffsrechte auf deine Lernräume, sobald wir die Liste der Lernräume abgerufen haben.
 		Du kannst dies <a href="https://oauth.campus.rwth-aachen.de/manage/">hier</a> überprüfen.
 		Außerdem speichern wir keine Authentifikationsdaten auf unserem Server.
 		</p>
-		<p>Details zu der dafür verwendeten Schnittstelle findest du in der <a href="https://www3.elearning.rwth-aachen.de/_vti_bin/L2PServices/api.svc/v1/Documentation">L2P-API-Dokumentation</a>.</p>
+		<p>Details zu der dafür verwendeten Schnittstelle findest du in der <a href="https://www3.elearning.rwth-aachen.de/_vti_bin/L2PServices/api.svc/v1/Documentation">L2P-API-Dokumentation</a> und der <a href="https://moped.ecampus.rwth-aachen.de/proxy/api/v2/documentation">Moodle-API-Dokumentation</a>.</p>
 	{% endcall %}
 	{% call faqentry("cookies", "Welche Cookies werden gesetzt und wofür?") %}
 		<p>Beim reinen Betrachten der Seite setzen wir überhaupt keine Cookies. Einzig beim Abruf oder Abspielen von Videodateien und bei der Authentifizierung für RWTH- oder Lernraum-interne Videos werden Cookies gesetzt.</p>
diff --git a/templates/macros.html b/templates/macros.html
index 786fb87..999df29 100644
--- a/templates/macros.html
+++ b/templates/macros.html
@@ -377,6 +377,9 @@ $('#embedcodebtn').popover(
 	{% if permdescription[0] == 'l2p' %}
 		{% set permlogos = '<span class="fa" aria-hidden="true" style="width: 12px; height: 14px; background-size: cover; background-image: url(\'/static/l2p-logo.gif\');"></span>' %}
 	{% endif %}
+	{% if permdescription[0] == 'moodle' %}
+		{% set permlogos = '<span class="fa" aria-hidden="true" style="width: 12px; height: 14px; background-size: cover; background-image: url(\'/static/moodle.png\');"></span>' %}
+	{% endif %}
 	{% if permdescription[0] == 'rwth' %}
 		{% set permlogos = '<span class="fa" aria-hidden="true" style="width: 25px; height: 20px; background-size: cover; background-image: url(\'/static/rwth.png\');"></span>' %}
 	{% endif %}
-- 
GitLab