Skip to content

Commit

Permalink
Mark users as teachers; allow teachers to access programs from other …
Browse files Browse the repository at this point in the history
…users by link.
  • Loading branch information
fpereiro committed Mar 17, 2021
1 parent 962c511 commit 84f8dad
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 12 deletions.
8 changes: 4 additions & 4 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from flask_commonmark import Commonmark
from werkzeug.urls import url_encode
from config import config
from auth import auth_templates, current_user, requires_login, is_admin
from auth import auth_templates, current_user, requires_login, is_admin, is_teacher
from utils import db_get, db_get_many, db_set, timems, type_check, object_check, db_del

# app.py
Expand Down Expand Up @@ -224,9 +224,9 @@ def index(level, step):
result = db_get ('programs', {'id': step})
if not result:
return 'No such program', 404
# Allow both the owner of the program and the admin user to access the program
# Allow only the owner of the program, the admin user and the teacher users to access the program
user = current_user (request)
if user ['username'] != result ['username'] and not is_admin (request):
if user ['username'] != result ['username'] and not is_admin (request) and not is_teacher (request):
return 'No such program!', 404
loaded_program = result ['code']
# We default to step 1 to provide a meaningful default assignment
Expand Down Expand Up @@ -494,7 +494,7 @@ def save_program (user):
# *** AUTH ***

import auth
auth.routes(app, requested_lang)
auth.routes (app, requested_lang)

# *** START SERVER ***

Expand Down
32 changes: 31 additions & 1 deletion auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def is_admin (request):
user = current_user (request)
return user ['username'] == os.getenv ('ADMIN_USER') or user ['email'] == os.getenv ('ADMIN_USER')

def is_teacher (request):
user = current_user (request)
return bool (user ['is_teacher'])

# The translations are imported here because current_user above is used by hedyweb.py and we need to avoid circular dependencies
import hedyweb
TRANSLATIONS = hedyweb.Translations ()
Expand Down Expand Up @@ -393,6 +397,31 @@ def reset ():

return '', 200

@app.route ('/admin/markAsTeacher', methods=['POST'])
def mark_as_teacher ():
if not is_admin (request):
return 'unauthorized', 403

body = request.json

# Validations
if not type_check (body, 'dict'):
return 'body must be an object', 400
if not object_check (body, 'username', 'str'):
return 'body.username must be a string', 400
if not object_check (body, 'is_teacher', 'bool'):
return 'body.is_teacher must be boolean', 400

user = db_get ('users', {'username': body ['username'].strip ().lower ()})

if not user:
return 'invalid username', 400

db_set ('users', {'username': user ['username'], 'is_teacher': 1 if body ['is_teacher'] else 0})

return '', 200


# Turn off verbose logs from boto/SES, thanks to https://github.com/boto/boto3/issues/521
import logging
for name in logging.Logger.manager.loggerDict.keys ():
Expand Down Expand Up @@ -450,7 +479,7 @@ def auth_templates (page, lang, menu, request):
# After hitting 1k users, it'd be wise to add pagination.
users = db_scan ('users')
userdata = []
fields = ['username', 'email', 'birth_year', 'country', 'gender', 'created', 'last_login', 'verification_pending']
fields = ['username', 'email', 'birth_year', 'country', 'gender', 'created', 'last_login', 'verification_pending', 'is_teacher']
for user in users:
data = {}
for field in fields:
Expand All @@ -459,6 +488,7 @@ def auth_templates (page, lang, menu, request):
else:
data [field] = None
data ['email_verified'] = not bool (data ['verification_pending'])
data ['is_teacher'] = bool (data ['is_teacher'])
data ['created'] = datetime.datetime.fromtimestamp (int (str (data ['created']) [:-3])).isoformat ()
if data ['last_login']:
data ['last_login'] = datetime.datetime.fromtimestamp (int (str (data ['last_login']) [:-3])).isoformat ()
Expand Down
4 changes: 4 additions & 0 deletions doc/backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@
- If there's no session or the logged in user is not the admin user, it returns 403.
- If successful, the route will return a template containing a table with all the users in the system and a total count of saved programs. The users will be sorted by creation date, last first.

- `POST /admin/markAsTeacher`
- This route allows the admin user to mark an user as teacher, which allows them to access a program from someone else by link.
- The body of the request should be of the shape `{username: STRING, is_teacher: BOOLEAN}`.

### Programs

- `GET /programs/delete/ID`
Expand Down
10 changes: 9 additions & 1 deletion static/js/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,15 @@ window.auth = {
else auth.error (auth.texts.ajax_error);
});
}
}
},
markAsTeacher: function (username, is_teacher) {
$.ajax ({type: 'POST', url: '/admin/markAsTeacher', data: JSON.stringify ({username: username, is_teacher: is_teacher}), contentType: 'application/json; charset=utf-8'}).done (function () {
alert (['User', username, 'successfully', is_teacher ? 'marked' : 'unmarked', 'as teacher'].join (' '));
}).fail (function (error) {
console.log (error);
alert (['Error when', is_teacher ? 'marking' : 'unmarking', 'user', username, 'as teacher'].join (' '));
});
},
}

// *** LOADERS ***
Expand Down
2 changes: 2 additions & 0 deletions templates/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ <h2>Users ({{ user_count }})</h2>
<td>Birth year</td>
<td>Country</td>
<td>Gender</td>
<td>Is teacher</td>
<td>Email verified</td>
</thead>
<tbody>
Expand All @@ -29,6 +30,7 @@ <h2>Users ({{ user_count }})</h2>
<td>{{user.birth_year}}</td>
<td>{{user.country}}</td>
<td>{{user.gender}}</td>
<td><input type="checkbox" {% if user.is_teacher %}checked="checked"{% endif %} onclick="window.auth.markAsTeacher ('{{user.username}}', {% if user.is_teacher %}false{% else %}true{% endif %})"</td>
<td>{{user.email_verified}}</td>
</tr>
{% endfor %}
Expand Down
14 changes: 8 additions & 6 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@

def type_check (val, Type):
if Type == 'dict':
return isinstance(val, dict)
return isinstance (val, dict)
if Type == 'list':
return isinstance(val, list)
return isinstance (val, list)
if Type == 'str':
return isinstance(val, str)
return isinstance (val, str)
if Type == 'int':
return isinstance(val, int)
return isinstance (val, int)
if Type == 'tuple':
return isinstance(val, tuple)
return isinstance (val, tuple)
if Type == 'fun':
return callable(val)
return callable (val)
if Type == 'bool':
return type (val) == bool

def object_check (obj, key, Type):
if not type_check (obj, 'dict') or not key in obj:
Expand Down

0 comments on commit 84f8dad

Please sign in to comment.