Skip to content

Commit e3debe1

Browse files
rishabhKalakotitheSage21
authored andcommitted
Sessions (#122)
* basic login/ register * sessions working * cookie expiry added * removed http.cookie import * removed checkLogin() handler (used for debugging) * Apply suggestions from code review Co-Authored-By: Arjoonn Sharma <[email protected]> * message display * bug fixes * changed name for decorator function * Apply suggestions from code review Co-Authored-By: Arjoonn Sharma <[email protected]> * authentication added to more handlers
1 parent f5146b0 commit e3debe1

File tree

6 files changed

+146
-27
lines changed

6 files changed

+146
-27
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,5 @@ venv/
6969

7070
.pytest_cache
7171
submission_record.db
72-
72+
user_record.db
73+
session_record.db

server.py

Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
1-
from bottle import Bottle, run, template, static_file, request, route, redirect,error
2-
import os
3-
import sys
4-
import datetime
1+
import bottle
2+
import os, sys, datetime
3+
import string, random
4+
55
from collections import defaultdict, namedtuple
66
import shelve
77

88
path = os.path.abspath(__file__)
99
dir_path = os.path.dirname(path)
10-
app = Bottle()
10+
app = bottle.Bottle()
1111

1212
database_path = "submission_record.db"
13+
user_db = "user_record.db"
14+
sessions_db = "session_record.db"
1315
questions = {}
1416
contests = {}
1517
question_dir = "files/questions"
1618

1719
Question = namedtuple("Question", "output statement")
1820
Submission = namedtuple("Submission", "question time output is_correct contest")
19-
# questions, code, description, start_time, end_time
2021
Contest = namedtuple("Contest", "description questions start_time end_time")
22+
User = namedtuple("User", "password")
2123

2224
# dummy contests
2325
contests["PRACTICE"] = Contest(
@@ -48,53 +50,69 @@
4850
for i in os.listdir(question_dir):
4951
if not i.isdigit():
5052
continue
51-
# read the correct output as bytes object
5253
with open(os.path.join(question_dir, i, "output.txt"), "rb") as fl:
5354
output = fl.read()
5455
with open(os.path.join(question_dir, i, "statement.txt"), "r") as fl:
5556
statement = fl.read()
5657
questions[i] = Question(output=output, statement=statement)
5758

5859

60+
def login_required(function):
61+
def login_redirect(*args, **kwargs):
62+
if not logggedIn():
63+
return bottle.template("home.html", message="Login required.")
64+
return function(*args, **kwargs)
65+
return login_redirect
66+
5967
@app.route("/")
6068
def changePath():
61-
return redirect("/dashboard")
69+
return bottle.redirect("/home")
70+
71+
72+
@app.get("/home")
73+
def home():
74+
if logggedIn():
75+
return bottle.redirect("/dashboard")
76+
return bottle.template("home.html", message="")
6277

6378

6479
@app.get("/dashboard")
80+
@login_required
6581
def dashboard():
66-
return template("dashboard.html", contests=contests)
82+
return bottle.template("dashboard.html", contests=contests)
6783

6884

6985
@app.get("/contest/<code>/<number>")
86+
@login_required
7087
def contest(code, number):
7188
if not code in contests:
7289
return "Contest does not exist"
7390
if contests[code].start_time > datetime.datetime.now():
7491
return "The contest had not started yet."
7592
statement = questions[number].statement
76-
return template(
77-
"index.html", question_number=number, contest=code, question=statement
93+
return bottle.template(
94+
"question.html", question_number=number, contest=code, question=statement
7895
)
7996

8097

8198
@app.get("/contest/<code>")
99+
@login_required
82100
def contest(code):
83101
if not code in contests:
84102
return "Contest does not exist"
85103
if contests[code].start_time > datetime.datetime.now():
86104
return "The contest had not started yet."
87-
return template("contest.html", code=code, contest=contests[code])
105+
return bottle.template("contest.html", code=code, contest=contests[code])
88106

89107

90108
@app.get("/question/<path:path>")
91109
def download(path):
92-
return static_file(path, root=question_dir)
110+
return bottle.static_file(path, root=question_dir)
93111

94112

95113
@app.get("/static/<filepath:path>")
96114
def server_static(filepath):
97-
return static_file(filepath, root=os.path.join(dir_path, "static"))
115+
return bottle.static_file(filepath, root=os.path.join(dir_path, "static"))
98116

99117

100118
@app.get("/ranking/<code>")
@@ -124,7 +142,7 @@ def contest_ranking(code):
124142
order.sort(key=lambda x: x[1], reverse=True)
125143
order = [entry for entry in order if entry[1] > 0]
126144
order = [(user, score, rank) for rank, (user, score) in enumerate(order, start=1)]
127-
return template("rankings.html", people=order)
145+
return bottle.template("rankings.html", people=order)
128146

129147

130148
@app.get("/ranking")
@@ -149,12 +167,68 @@ def rankings():
149167
order = [(user, score, rank) for rank, (user, score) in enumerate(order, start=1)]
150168
return template("rankings.html", people=order)
151169

170+
def logggedIn():
171+
if not bottle.request.get_cookie("s_id"):
172+
return False
173+
with shelve.open(sessions_db) as sessions:
174+
return bottle.request.get_cookie("s_id") in sessions
175+
176+
177+
def createSession(username):
178+
session_id = "".join(
179+
random.choice(string.ascii_letters + string.digits) for i in range(20)
180+
)
181+
bottle.response.set_cookie(
182+
"s_id",
183+
session_id,
184+
expires=datetime.datetime.now() + datetime.timedelta(days=30),
185+
)
186+
with shelve.open(sessions_db) as sessions:
187+
sessions[session_id] = username
188+
return bottle.redirect("/dashboard")
189+
190+
191+
@app.post("/login")
192+
def login():
193+
username = bottle.request.forms.get("username")
194+
password = bottle.request.forms.get("password")
195+
with shelve.open(user_db) as users:
196+
if not username in users:
197+
return bottle.template("home.html", message="User does not exist.")
198+
if users[username].password != password:
199+
return bottle.template("home.html", message="Incorrect password.")
200+
return createSession(username)
201+
202+
203+
@app.post("/register")
204+
def register():
205+
username = bottle.request.forms.get("username")
206+
password = bottle.request.forms.get("password")
207+
with shelve.open(user_db) as users:
208+
if username in users:
209+
return bottle.template(
210+
"home.html",
211+
message="Username already exists. Select a different username",
212+
)
213+
users[username] = User(password=password)
214+
return createSession(username)
215+
216+
217+
@app.get("/logout")
218+
def logout():
219+
with shelve.open(sessions_db) as sessions:
220+
del sessions[bottle.request.get_cookie("s_id")]
221+
bottle.response.delete_cookie("s_id")
222+
return bottle.redirect("/home")
223+
152224

153225
@app.post("/check/<code>/<number>")
226+
@login_required
154227
def file_upload(code, number):
155-
u_name = request.forms.get("username") # accepting username
228+
with shelve.open(sessions_db) as sessions:
229+
u_name = sessions[bottle.request.get_cookie("s_id")]
156230
time = datetime.datetime.now()
157-
uploaded = request.files.get("upload").file.read()
231+
uploaded = bottle.request.files.get("upload").file.read()
158232
expected = questions[number].output
159233
expected = expected.strip()
160234
uploaded = uploaded.strip()
@@ -164,7 +238,6 @@ def file_upload(code, number):
164238
submissions = (
165239
[] if u_name not in submission_record else submission_record[u_name]
166240
)
167-
# submissions = submission_record.get(u_name, list())
168241
submissions.append(
169242
Submission(
170243
question=number,
@@ -186,5 +259,4 @@ def file_upload(code, number):
186259
def error404(error):
187260
return template("error.html" ,errorcode=error.status_code , errorbody = error.body)
188261

189-
190-
run(app, host="localhost", port=8080)
262+
bottle.run(app, host="localhost", port=8080)

views/contest.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ <h1>{{code}}</h1>
99
{{contest.description}}
1010
</p>
1111
</div>
12+
<div class="container">
13+
<a href="/ranking/{{code}}" class="btn btn-primary">Rankings</a>
14+
</div>
1215
<div class="container">
1316
% for qno in range(len(contest.questions)):
1417
<a href="/contest/{{code}}/{{contest.questions[qno]}}">{{qno+1}}</a><br />

views/dashboard.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ <h1>Contests</h1>
2323
</tbody>
2424
</table>
2525
</div>
26+
<div class="container">
27+
<a href="/logout" class="btn btn-primary">Logout</a>
28+
</div>
2629
</body>
2730
</html>

views/home.html

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
% include('base.html', title="PyJudge")
2+
<body>
3+
<div class="container text-center">
4+
<h1>
5+
PyJudge
6+
</h1>
7+
</div>
8+
% if message:
9+
<div class="alert alert-success alert-dismissible" style='width:auto;'>
10+
<button type="button" class="close" data-dismiss="alert">&times;</button>
11+
<strong>Error!</strong> {{message}}
12+
</div>
13+
% end
14+
<div class="container">
15+
<ul class="nav nav-tabs">
16+
<li class="nav-item">
17+
<a class="nav-link active" data-toggle="tab" href="#login">Login</a>
18+
</li>
19+
<li class="nav-item">
20+
<a class="nav-link" data-toggle="tab" href="#register">Register</a>
21+
</li>
22+
</ul>
23+
<div class="tab-content">
24+
<div id="login" class="tab-pane container active">
25+
<form name="login" method="post" action="/login">
26+
<label>Username:</label>
27+
<input type="text" name="username" required /><br />
28+
<label>Password</label>
29+
<input type="password" name="password" required /><br />
30+
<input class="btn btn-primary" type="submit" value="Login" />
31+
</form>
32+
</div>
33+
<div id="register" class="tab-pane container fade">
34+
<form name="register" method="post" action="/register">
35+
<label>Username:</label>
36+
<input type="text" name="username" required /><br />
37+
<label>Password:</label>
38+
<input type="password" name="password" required /><br />
39+
<input class="btn btn-primary" type="submit" value="Register" />
40+
</form>
41+
</div>
42+
</div>
43+
</div>
44+
</body>

views/index.html renamed to views/question.html

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,8 @@ <h1>Submission Page</h1>
1818
You can submit the code in any language.
1919
You can upload the file below.
2020
</p>
21-
<form action="/check/{{contest}}/{{question_number}}" method = "post" enctype = "multipart/form-data">
22-
<div class="form-group">
23-
<label for="username">Username: </label>
24-
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
25-
<input class="form-control" type="text" name="username" required />
26-
</div>
21+
<form action="/check/{{contest}}/{{question_number}}" method = "post" enctype = "multipart/form-data">
22+
Output file: <br />
2723
<input type="file" name="upload" required />
2824
<button class="btn btn-primary" type="submit">Upload</button>
2925
</form>

0 commit comments

Comments
 (0)