From 93b7f433ce9fa9a8968f3ff0e93003f24dcec78b Mon Sep 17 00:00:00 2001 From: Miguel Barão Date: Tue, 31 May 2016 13:26:36 +0100 Subject: [PATCH] - implemented the application logic in app.py. - the serve.py is now only the interface, everything goes through App class. - implemented an /admin page to authorize students and reset passwords. this is done with a webservice that responds to AJAX. --- .gitignore | 1 + BUGS.md | 90 +++++++++++++++++++++++++----------------------------------------------------------------- app.py | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ config/server.conf | 21 +++------------------ database.py | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------- myauth.py | 169 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- questions.py | 19 +++++++++++-------- serve.py | 420 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------------------------------------ static/js/admin.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ static/js/bootstrap-3.3.6.min.js | 7 +++++++ static/js/bootstrap.min.js | 7 ------- static/js/bootstrap.min.js | 1 + static/js/jquery-2.1.4.min.js | 4 ---- static/js/jquery-2.2.4.min.js | 4 ++++ static/js/jquery.min.js | 2 +- templates/admin.html | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ templates/grade.html | 81 +++++++++++++++++++++++++++++---------------------------------------------------- templates/login.html | 32 +++++++++++--------------------- templates/test.html | 22 ++++++++-------------- test.py | 40 +++++++++++++++++++++++----------------- 20 files changed, 799 insertions(+), 596 deletions(-) create mode 100644 .gitignore create mode 100644 app.py delete mode 100644 myauth.py create mode 100644 static/js/admin.js create mode 100644 static/js/bootstrap-3.3.6.min.js delete mode 100644 static/js/bootstrap.min.js create mode 120000 static/js/bootstrap.min.js delete mode 100644 static/js/jquery-2.1.4.min.js create mode 100644 static/js/jquery-2.2.4.min.js create mode 100644 templates/admin.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/BUGS.md b/BUGS.md index 981281b..e235aae 100644 --- a/BUGS.md +++ b/BUGS.md @@ -1,81 +1,41 @@ # BUGS -- em practice, depois da submissao o teste corrigido perde as respostas anteriores. perguntas estao todas expostas. -- cherrypy faz logs para consola... -- mensagens info nao aparecem no serve.py +- pagina de login nao esta a apresentar bem. parece que precisa de autorizacao para aceder a /static... - usar thread.Lock para aceder a variaveis de estado. -- ordenar activos por hora de login. -- se no teste uma das "ref" nao existir nos ficheiros de perguntas, rebenta. -- no final do teste (em modo avaliacao) o aluno é removido da lista allowed. para fazer novamente necessita de aprovação do professor. -- mesmo aluno pode entrar várias vezes em simultaneo... -- qd scripts não são executáveis rebenta. Testar isso e dar uma mensagem de erro. -- paths manipulation in strings is unix only ('/something'). use os.path to create paths. -- alunos vêm nota final arredondada às decimas, mas é apenas um arredondamento visual. Pode acontecer o aluno chumbar, mas ver uma nota positiva (e.g. 9.46 mostra 9.5 e presume que esta aprovado). Mostrar 3 casas? -- alunos podem entrar duas vezes em simultaneo. impedir, e permitir ao docente fazer kick-out -- detectar se falta 'correct' nas perguntas. -- check if script to generate questions exist before instantiation. -- testar regex na definicao das perguntas. como se faz rawstring em yaml? singlequote? problemas de backslash??? sim... necessário fazer \\ em varios casos, mas não é claro! e.g. \n é convertido em espaço mas \w é convertido em \\ e w. Solução (http://stackoverflow.com/questions/10771163/python-interpreting-a-regex-from-a-yaml-config-file) é fazer - correct: !!python/regex '^(yes|no)' - +- permitir adicionar imagens nas perguntas +- browser e ip usados gravado no test. +- configuracao dos logs cherrypy para se darem bem com os outros # TODO -- refazer questions.py para ter uma classe QuestionFectory? -- refazer serve.py para usar uma classe App() com lógica separada do cherrypy -- controlar acessos dos alunos: allowed/denied/online threadsafe na App() +- implementar practice mode. +- SQLAlchemy em vez da classe database. - argumentos da linha de comando a funcionar. -- permitir adicionar imagens nas perguntas - aviso na pagina principal para quem usa browser da treta -- permitir enviar varios testes, aluno escolhe qual o teste que quer fazer. +- permitir varios testes, aluno escolhe qual o teste que quer fazer. - criar perguntas de outros tipos, e.g. associação, ordenação, varios textinput -- browser e ip usados gravado no test. +- perguntas para professor corrigir mais tarde. - single page web no frontend -- SQLAlchemy em vez da classe database. -- script de correcção pode enviar dicionario yaml com grade e comentarios. ex: - grade: 0.5 - comments: Falhou na função xpto. - os comentários são guardados no teste (ficheiro) ou enviados para o browser no modo practice. -- warning quando se executa novamente o mesmo teste na consola. ie se ja houver submissoes desse teste. -- na cotacao da pergunta indicar o intervalo, e.g. [-0.2, 1], [0, 0.5] -- Criar botão para o docente fazer enable/disable do aluno explicitamente (exames presenciais). -- criar script json2md.py ou outra forma de gerar um teste ja realizado +- criar script json2md.py ou outra forma de visualizar um teste ja realizado - Menu para professor com link para /results e /students -- perguntas para professor corrigir mais tarde. -- share do score em /results (email) - fazer uma calculadora javascript e por no menu. surge como modal +- GeoIP? +- mostrar botão de reset apenas para alunos com password definida? # FIXED -- ordenar lista de alunos pelos online/offline, e depois pelo numero. -- hash das passwords concatenadas com salt gerado aleatoriamente. necessario acrescentar salt de cada aluno. gerar salt com os.urandom(256) -- fix ans directory. relative to what?? current dir? -- textarea com opcao de numero de linhas (consoante o programa a desenvolver podem ser necessarias mais ou menos linhas) -- desligar submissao com tecla enter no chrome/mac -- se database for mal configurada, é criada uma base de dados vazia e rebenta na autenticacao. -- questions type script, necessário dar um caminho exacto relativamete ao directorio do server em vez da pergunta. deveria ser possivel mover as perguntas de directorio sem rebentar os caminhos. -- check that files exist in questions generator e correct textarea. add path in test.yaml -- scripts generator and correct should consider the questions path. -- testar envio de parametros para stdin para perguntas tipo generator. -- mathjax e jquery no login -- mostrar erro quando nao consegue importar questions files -- pacotes exactos usados para instalar. -- detectar colisoes nas referencias das perguntas. -- usar pomba da ue moderna. -- /results esta ordenado por numero e nao por data -- numeros das perguntas não fazem sentido quando há caixas de informação (numerar informacao tb?) -- Quando apresenta o teste, preencher com os valores definidos em answer (permite que professor dê informação à partida, e no modo practice fiquem com o preenchido anteriormente) -- information points é definido onde? test.y ou questions.py? -- textarea monospace -- disable tab behavior in textarea. -- command line options --debug --show_points --show_hints --practice_mode -- manual de utilizacao. -- criar pergunta gerada por script externo. -- debug mode -- in the train_mode, there is no way to logout. Add logout option in the menu. -- simplificar a gravacao do teste em json. -- mostrar numero ordem em /results -- modal a pedir confirmação de submissão. -- pontos devem estar normalizados escala 0-20 -- mostrar numero de alunos online em /students -- mostrar cotacao das perguntas, show_points, default:False +- Não mostrar Professor nos activos em /admin +- /admin mostrar actualizações automaticamente? +- se no teste uma das "ref" nao existir nos ficheiros de perguntas, rebenta. +- alunos podem estar online, mas browser perder sessao => nao conseguem mais entrar porque a App pensa que estão online. Permitir login e dar o mesmo teste. +- pagina de management dos alunos. + mostrar online ordenados por hora de login, offline por número. + permitir reset da pw e allow/disallow +- script de correcção pode enviar dicionario yaml com grade e comentarios. ex: + grade: 0.5 + comments: Falhou na função xpto. + os comentários são guardados no teste (ficheiro) ou enviados para o browser no modo practice. +- testar regex na definicao das perguntas. como se faz rawstring em yaml? + singlequote? problemas de backslash??? sim... necessário fazer \\ em varios casos, mas não é claro! e.g. \n é convertido em espaço mas \w é convertido em \\ e w. Solução (http://stackoverflow.com/questions/10771163/python-interpreting-a-regex-from-a-yaml-config-file) é fazer + correct: !regex '^(yes|no)' diff --git a/app.py b/app.py new file mode 100644 index 0000000..69e5e37 --- /dev/null +++ b/app.py @@ -0,0 +1,162 @@ + + +import logging +from os import path +import sqlite3 + +import bcrypt + +import test +import database + +# ============================================================================ +# Application +# ============================================================================ +class App(object): + def __init__(self, filename, conf): + # online = { + # uid1: { + # 'student': {'number': 123, 'name': john}, + # 'test': ... + # } + # uid2: {...} + # } + logger.info('============= Running perguntations =============') + self.online = dict() # {uid: {'student':{}}} + self.allowed = set([13361,13682]) # '0' is hardcoded to allowed elsewhere + self.testfactory = test.TestFactory(filename, conf=conf) + self.db = database.Database(self.testfactory['database']) # FIXME + try: + n = self.db.get_count_students() + except sqlite3.OperationalError as e: + logger.critical('Database not usable {}.'.format(self.db.db)) + raise e + else: + logger.info('Database has {} students registered.'.format(n)) + + + def exit(self): + logger.critical('----------- !!! Server terminated !!! -----------') + + + def login(self, uid, try_pw): + if uid not in self.allowed and uid != '0': + # not allowed + logger.warning('Student {}: not allowed to login.'.format(uid)) + return False + + student = self.db.get_student(uid) + if student is None: + # not found + logger.warning('Student {}: not found in database.'.format(uid)) + return False + + # uid found in database + name, pw = student + + if pw == '': + # update password on first login + hashed_pw = bcrypt.hashpw(try_pw.encode('utf-8'), bcrypt.gensalt()) + self.db.update_password(uid, hashed_pw) + logger.warning('Student {}: first login, password updated.'.format(uid)) + elif bcrypt.hashpw(try_pw.encode('utf-8'), pw) != pw: + # wrong password + logger.info('Student {}: wrong password.'.format(uid)) + return False + + # success + self.allowed.discard(uid) + if uid in self.online: + logger.warning('Student {}: already logged in.'.format(uid)) + else: + self.online[uid] = {'student': {'name': name, 'number': uid}} + logger.info('Student {}: logged in.'.format(uid)) + return True + + + def logout(self, uid): + if uid not in self.online: + # this should never happen + logger.error('Student {}: tried to logout, but is not logged in.'.format(uid)) + return False + else: + logger.info('Student {}: logged out.'.format(uid)) + del self.online[uid] + return True + + def generate_test(self, uid): + if uid in self.online: + logger.info('Student {}: generating new test.'.format(uid)) + student_id = self.online[uid]['student'] + self.online[uid]['test'] = self.testfactory.generate(student_id) + return self.online[uid]['test'] + else: + logger.error('Student {}: offline, can''t generate test'.format(uid)) + return None + + def correct_test(self, uid, ans): + t = self.online[uid]['test'] + t.update_answers(ans) + grade = t.correct() + logger.info('Student {0}: finished with {1} points.'.format(uid, grade)) + + if t['save_answers']: + fname = ' -- '.join((t['student']['number'], t['ref'], str(t['finish_time']))) + '.json' + fpath = path.abspath(path.join(t['answers_dir'], fname)) + t.save_json(fpath) + + self.db.save_test(t) + self.db.save_questions(t) + return grade + + + # --- helpers (getters) + def get_student_name(self, uid): + return self.online[uid]['student']['name'] + def get_test(self, uid, default=None): + return self.online[uid].get('test', default) + def get_test_qtypes(self, uid): + return {q['ref']:q['type'] for q in self.online[uid]['test']['questions']} + def get_student_grades(self, uid): + return self.db.get_student_grades(uid) + + def get_online_students(self): + # list of ('uid', 'name', 'start_time') sorted by start time + return sorted( + ((k, v['student']['name'], str(v.get('test', {}).get('start_time', '---'))) for k,v in self.online.items() if k != '0'), + key=lambda k: k[2] # sort key + ) + + def get_offline_students(self): + # list of ('uid', 'name') sorted by number + return sorted((s[:2] for s in self.db.get_all_students() if s[0] not in self.online), key=lambda k: k[0]) + + # def get_this_students_grades(self): + # # list of ('uid', 'name') sorted by number + # return self.db.get_students_grades(self.testfactory['ref']) + + def get_allowed_students(self): + # set of 'uid' allowed to login + return self.allowed + + # --- helpers (change state) + def allow_student(self, uid): + self.allowed.add(uid) + logger.info('Student {}: allowed to login.'.format(uid)) + + def deny_student(self, uid): + self.allowed.discard(uid) + logger.info('Student {}: denied to login'.format(uid)) + + def reset_password(self, uid): + self.db.reset_password(uid) + logger.info('Student {}: password reset to ""'.format(uid)) + +# ============================================================================ +ch = logging.StreamHandler() +ch.setLevel(logging.INFO) +ch.setFormatter(logging.Formatter('%(asctime)s | %(name)-10s | %(levelname)-8s | %(message)s')) + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) +logger.addHandler(ch) diff --git a/config/server.conf b/config/server.conf index 60d8d08..1b27ac7 100644 --- a/config/server.conf +++ b/config/server.conf @@ -12,33 +12,18 @@ server.thread_pool= 10 server.socket_host = '0.0.0.0' server.socket_port = 8080 -; Uncomment to enable SSL -; 'builtin' or 'pyopenssl' +; Uncomment to enable SSL (ssl_module = 'builtin' or 'pyopenssl') ; server.ssl_module = 'builtin' ; server.ssl_certificate = 'certs/webserver.crt' ; server.ssl_private_key = 'certs/webserver.key' + ; not required for snakeoil: ; server.ssl_certificate_chain = 'ca_certs.crt' -log.screen = False - # add path to the log files here. empty strings disable logging ; log.error_file = 'logs/errors.log' ; log.access_file = 'logs/access.log' log.error_file = '' log.access_file = '' +log.screen = False -# DO NOT DISABLE SESSIONS! -tools.sessions.on = True -tools.sessions.timeout = 240 -tools.sessions.storage_type = 'ram' -tools.sessions.storage_path = 'sessions' - -; FIXME what is this for? -tools.auth.on = True - -[/] -; Default root is under the server directory, but can be changed here -; tools.staticdir.root = os.path.normpath(os.path.abspath(os.path.curdir)) -tools.staticdir.dir = 'static' -tools.staticdir.on = True diff --git a/database.py b/database.py index f2b77c9..74ad141 100644 --- a/database.py +++ b/database.py @@ -1,83 +1,113 @@ import sqlite3 -from hashlib import sha256 class Database(object): def __init__(self, db): self.db = db # sqlite3 filename - # FIXME check that database exists - # get results from previous tests of a student - def student_grades(self, uid): + def get_count_students(self): with sqlite3.connect(self.db) as c: - grades = c.execute('SELECT test_id,grade,finish_time FROM tests WHERE student_id==?', [uid]) - return grades.fetchall() + sql = 'SELECT COUNT(*) FROM students' + return c.execute(sql).fetchone()[0] - # return list of students and their results for a given test - def test_grades(self, test_id): + def reset_password(self, uid): with sqlite3.connect(self.db) as c: - # with all tests done by each student: - # cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;' - - # only the best result for each student - cmd = ''' - SELECT student_id, name, MAX(grade), finish_time - FROM students INNER JOIN tests - ON students.number=tests.student_id - WHERE test_id==? AND student_id!=0 - GROUP BY student_id - ORDER BY grade DESC, finish_time DESC;''' - return c.execute(cmd, [test_id]).fetchall() - - # return list of students and their results for a given test - def test_grades2(self, test_id): + sql = 'UPDATE students SET password="" WHERE number=?' + c.execute(sql, [uid]) + + def update_password(self, uid, pw): + # saves pw as is (should be already hashed) with sqlite3.connect(self.db) as c: - # with all tests done by each student: - # cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;' - - # only the best result for each student - cmd = ''' - SELECT student_id, name, grade, start_time, finish_time - FROM students INNER JOIN tests - ON students.number=tests.student_id - WHERE test_id==? - ORDER BY finish_time ASC;''' - return c.execute(cmd, [test_id]).fetchall() - - # get list of students in the database - def get_students(self): + sql = 'UPDATE students SET password=? WHERE number=?' + c.execute(sql, (pw, uid)) + + def get_student(self, uid): with sqlite3.connect(self.db) as c: - students = c.execute('SELECT number,name,password FROM students ORDER BY number ASC;') - return students.fetchall() + sql = 'SELECT name,password FROM students WHERE number=?' + try: + name, pw = c.execute(sql, [uid]).fetchone() + except: + return None + else: + return (name, pw) - # the following methods update de database data + def get_all_students(self): + with sqlite3.connect(self.db) as c: + sql = 'SELECT number,name,password FROM students ORDER BY number ASC' + students = c.execute(sql).fetchall() + return students - def save_test(self, t): + # get all results for a particular test. If a student has submited more than + # one test, returns the highest grade FIXME not tested, not used + # def get_students_grades(self, testid): + # with sqlite3.connect(self.db) as c: + # grades = c.execute('SELECT student_id,MAX(grade) FROM tests WHERE test_id==?', [testid]) + # return grades.fetchall() + + # get results from previous tests of a student + def get_student_grades(self, uid): with sqlite3.connect(self.db) as c: - # store result of the test - values = (t['ref'], t['student']['number'], t['grade'], str(t['start_time']), str(t['finish_time'])) - c.execute('INSERT INTO tests VALUES (?,?,?,?,?)', values) + grades = c.execute('SELECT test_id,grade,finish_time FROM tests WHERE student_id==?', [uid]) + return grades.fetchall() - # store grade of every question in the test - try: - ans = [(t['ref'], q['ref'], t['student']['number'], q['grade'], str(t['finish_time'])) for q in t['questions']] - except KeyError as e: - print(' * Questions {0} do not have grade defined.'.format(tuple(q['ref'] for q in t['questions'] if 'grade' not in q))) - raise e - c.executemany('INSERT INTO questions VALUES (?,?,?,?,?)', ans) - - def student_reset_pw(self, d): - # d = {'12345': 'mypassword', ...} + def save_test(self, t): with sqlite3.connect(self.db) as c: - for num, pw in d.items(): - if pw != '': - pw = sha256(pw.encode('utf-8')).hexdigest() - cmd = 'UPDATE students SET password=? WHERE number=?' - c.execute(cmd, (pw, num)) + # save final grade of the test + sql = 'INSERT INTO tests VALUES (?,?,?,?,?)' + test = (t['ref'], t['student']['number'], t['grade'], str(t['start_time']), str(t['finish_time'])) + c.execute(sql, test) - def student_add(self, number, name, password=''): # FIXME testar... + def save_questions(self, t): with sqlite3.connect(self.db) as c: - if password != '': - password = sha256(password.encode('utf-8')).hexdigest() - cmd = 'INSERT INTO students VALUES (?, ?, ?);' - c.execute(cmd, number, name, password) + # save grades of all the questions (omits questions without grade) + sql = 'INSERT INTO questions VALUES (?,?,?,?,?)' + questions = [(t['ref'], q['ref'], t['student']['number'], q['grade'], str(t['finish_time'])) for q in t['questions'] if 'grade' in q] + c.executemany(sql, questions) + + + + + # def insert_student(self, number, name, password=''): # FIXME testar... + # with sqlite3.connect(self.db) as c: + # if password != '': + # password = sha256(password.encode('utf-8')).hexdigest() # FIXME bcrypt + # cmd = 'INSERT INTO students VALUES (?, ?, ?);' + # c.execute(cmd, number, name, password) + + + + + # # return list of students and their results for a given test + # def get_test_grades(self, test_id): + # with sqlite3.connect(self.db) as c: + # # with all tests done by each student: + # # cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;' + + # # only the best result for each student + # cmd = ''' + # SELECT student_id, name, MAX(grade), finish_time + # FROM students INNER JOIN tests + # ON students.number=tests.student_id + # WHERE test_id==? AND student_id!=0 + # GROUP BY student_id + # ORDER BY grade DESC, finish_time DESC;''' + # return c.execute(cmd, [test_id]).fetchall() + + # # return list of students and their results for a given test + # def test_grades2(self, test_id): + # with sqlite3.connect(self.db) as c: + # # with all tests done by each student: + # # cmd = 'SELECT student_id,name,grade FROM students INNER JOIN tests ON students.number=tests.student_id WHERE test_id==? ORDER BY grade DESC;' + + # # only the best result for each student + # cmd = ''' + # SELECT student_id, name, grade, start_time, finish_time + # FROM students INNER JOIN tests + # ON students.number=tests.student_id + # WHERE test_id==? + # ORDER BY finish_time ASC;''' + # return c.execute(cmd, [test_id]).fetchall() + + + # the following methods update de database data + diff --git a/myauth.py b/myauth.py deleted file mode 100644 index 5186ed3..0000000 --- a/myauth.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- encoding: utf-8 -*- - -# TODO: -# - testar logout -# - session fixation -# - member_of... - -import cherrypy -import sqlite3 -import bcrypt -from mako.lookup import TemplateLookup -import urllib -import html -from os import path - -# path where this file is located -server_path = path.dirname(path.realpath(__file__)) - -SESSION_KEY = 'userid' - -templates = TemplateLookup(directories=[server_path+'/templates'], input_encoding='utf-8') - - -def credentials_ok(uid, password, db): - '''Given a userid, password and database, checks if the userid exists in - the database, a checks if the password is correct. The password is - updated if it's initially empty. - Returns the name of the student on success, otherwise returns None. - ''' - - with sqlite3.connect(db) as c: - # search student in database - sql_cmd = 'SELECT name,password FROM students WHERE number=?' - try: - name, pwhash = c.execute(sql_cmd, [uid]).fetchone() - except: - cherrypy.log.error('Student %s not found!' % uid, 'APPLICATION') - return None - - # student found in db - if pwhash == '': - # update password on first login - hashed = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) - sql_cmd = 'UPDATE students SET password=? WHERE number=?' - c.execute(sql_cmd, (hashed, uid)) - cherrypy.log.error('Student %s updated his password.' % uid, 'APPLICATION') - return name - else: - # check password - if bcrypt.hashpw(password.encode('utf-8'), pwhash) == pwhash: - cherrypy.log.error('Student %s logged in.' % uid, 'APPLICATION') - return name - else: - cherrypy.log.error('Student %s wrong password.' % uid, 'APPLICATION') - return None - - -def check_auth(*args, **kwargs): - """A tool that looks in config for 'auth.require'. If found and it - is not None, a login is required and the entry is evaluated as a list of - conditions that the user must fulfill""" - conditions = cherrypy.request.config.get('auth.require', None) - get_parmas = urllib.request.quote(cherrypy.request.request_line.split()[1]) - if conditions is not None: - user = cherrypy.session.get(SESSION_KEY, None) - - # if already logged in, must satisfy conditions - # else redirects to login page - if user is not None: - cherrypy.request.login = user - for condition in conditions: - # A condition is just a callable that returns true or false - if not condition(): - raise cherrypy.HTTPRedirect("/auth/login?from_page=%s" % get_parmas) - else: - raise cherrypy.HTTPRedirect("/auth/login?from_page=%s" % get_parmas) - -cherrypy.tools.auth = cherrypy.Tool('before_handler', check_auth) - - -def require(*conditions): - """A decorator that appends conditions to the auth.require config - variable.""" - def decorate(f): - if not hasattr(f, '_cp_config'): - f._cp_config = dict() - if 'auth.require' not in f._cp_config: - f._cp_config['auth.require'] = [] - f._cp_config['auth.require'].extend(conditions) - return f - return decorate - - -# Conditions are callables that return True -# if the user fulfills the conditions they define, False otherwise -# -# They can access the current username as cherrypy.request.login -# -# Define those at will however suits the application. - -def member_of(groupname): - def check(): - # replace with actual check if is in - return cherrypy.request.login == 'joe' and groupname == 'admin' # FIXME - return check - - -def name_is(reqd_username): - return lambda: reqd_username == cherrypy.request.login - - -# These might be handy -def any_of(*conditions): - """Returns True if any of the conditions match""" - def check(): - for c in conditions: - if c(): - return True - return False - return check - - -# By default all conditions are required, but this might still be -# needed if you want to use it inside of an any_of(...) condition -def all_of(*conditions): - """Returns True if all of the conditions match""" - def check(): - for c in conditions: - if not c(): - return False - return True - return check - - -# ============================================================================ -class AuthController(object): - def __init__(self, database): - self.database = database - - @cherrypy.expose - def login(self, uid=None, pw=None, from_page='/'): - # XSS vulnerability if from_page is maliciously formed. - # e.g. /auth/login?from_page="/> + + + + + + + + + +
+ +
+
+ Activo(s) +
+
+ + + + + + + + + + + +
NúmeroNomeInício do teste
+
+
+ +
+
+ Inactivo(s) +
+
+ + + + + + + + + + + + +
AutorizadoNúmeroNomePassword
+
+
+ +
+ + diff --git a/templates/grade.html b/templates/grade.html index 8bfe857..05e3172 100644 --- a/templates/grade.html +++ b/templates/grade.html @@ -4,20 +4,20 @@ - ${t['title']} - + ${title} + - - + + - - + +