-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdatabase.py
220 lines (190 loc) · 9.01 KB
/
database.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import sqlite3
import hashlib
import os
def hash_password(password, salt=None):
"""
If user has already got a salt, then that will be passed in as salt parameter. If not, salt is defined as None, and
so a new salt is generated for them.
hashes password using hashlib
:param password:
:param salt:
:return: hashed password, salt
"""
if salt is None:
salt = os.urandom(16) # randomly generates a 16 bit binary string if the user is registering, otherwise uses
# their salt that was assigned.
password = str(password)
hashed = hashlib.pbkdf2_hmac('sha256', # The hash digest algorithm for hmac
password.encode('utf-8'), # Convert the password into bytes
salt, 1000 # number of iterations of hash- more iterations makes it more secure
)
return hashed, salt
class database:
def __init__(self):
self.conn = sqlite3.connect('HandwritingUsers') # establishes connection with database
self.c = self.conn.cursor()
# this is used to solve the problem of the c.fetchall() function returning a unicode string rather than utf-8
# before, it would return " u'username' " now with this line it returns " 'username' "
self.conn.text_factory = str
self.c.execute('''CREATE TABLE IF NOT EXISTS Users(
UserID integer NOT NULL PRIMARY KEY,
username text NOT NULL,
pw_hashed text NOT NULL,
salt text NOT NULL
)''')
self.c.execute('''CREATE TABLE IF NOT EXISTS Results(
ResultID integer NOT NULL PRIMARY KEY,
UserID integer NOT NULL,
Overall_accuracy integer,
num_attempts integer,
num_correct integer
)''')
self.c.execute('''CREATE TABLE IF NOT EXISTS Rounds(
RoundID integer NOT NULL PRIMARY KEY,
UserID integer NOT NULL,
ResultID integer NOT NULL,
Correct text,
Number_to_draw integer,
Number_drawn integer,
Certainty integer
)''')
self.conn.commit()
def user_in_db(self, username):
"""
check if user is in database
:param username:
:return True:
"""
self.c.execute("select * From Users where username = ?", (username,))
fetched = self.c.fetchone()
if fetched is None:
return False
return True
def insert(self, username, password):
"""
inserts the username into the database, and also hashes the password, then stores the salt,password,username.
:param username: StringVar
:param password: string
"""
username = str(username)
password = str(password)
pw_hashed, salt = hash_password(password) # no salt provided, default provided as none
self.c.execute("INSERT INTO Users(username,pw_hashed,salt) VALUES(?,?,?)",
(username, pw_hashed, salt)) # used parameterised sql to prevent sql injection
self.conn.commit()
def user_password_match(self, username, entered_password):
"""
Check if username and password match
:param username:
:param entered_password:
:return:
"""
self.c.execute(" SELECT username, salt FROM Users WHERE username = ?", (username,))
fetched = self.c.fetchone()
if fetched is None:
return False
else:
their_salt = fetched[1] # grabbed salt of the user from the database
pw_hashed, salt = hash_password(entered_password, their_salt) # used their salt to hash the password
# they entered. If match, they are given access
self.c.execute(
" SELECT username, pw_hashed, salt FROM Users WHERE username = ? AND pw_hashed = ? ",
(username, pw_hashed))
user_details = self.c.fetchall()
if len(user_details) == 0:
return False
else:
return True
def display_leaderboard(self):
self.c.execute('''SELECT Users.username, Results.Overall_accuracy,Results.num_attempts,Results.num_correct
FROM Users,Results
WHERE Users.UserID = Results.UserID
AND Results.num_attempts != 0
ORDER BY Results.Overall_accuracy DESC
''') # don't want to display people who haven't attempted the game yet
return self.c.fetchmany(5) # returns top 5
def get_user_scores(self, user): # get every round user has done, and display
"""
:param user:
:return: list of Rounds that user has done, list
"""
username = str(user)
try:
self.c.execute('select UserID from Users Where username = ?', (username,))
their_UserID = self.c.fetchone()[0]
self.c.execute('''
SELECT Rounds.Correct, Rounds.Number_to_draw, Rounds.Number_drawn, Rounds.certainty
FROM Rounds
WHERE Rounds.UserID = ? ''', (their_UserID,))
except TypeError:
return ['nothing entered']
return self.c.fetchall()
def insert_round(self, user, correct, num_to_draw, num_drawn, certainty):
"""
:param user: StringVar, username. stringvar since it is a tkinter entry. converted to string below
:param correct: Bool
:param num_to_draw: int
:param num_drawn: int
:param certainty: int
:return: nothing
Adds row to round table, using text file
"""
user = str(user)
if correct:
correct = 'Yes'
else:
correct = 'no'
try:
self.c.execute('select UserID from Users where username = ?', (user,))
userID = self.c.fetchone()[0]
self.c.execute('select ResultID from Results where userID = ?', (userID,))
resultID = self.c.fetchall() # want to find most recent result ID for updating. Since resultID auto
# increments,the most recent will be the largest. Therefore I can find the maximum value in the list
# c.fetchall()
resultID = max(resultID)[0]
self.c.execute(
'insert into Rounds(UserID,ResultID, Correct,Number_to_draw, Number_drawn, Certainty) VALUES(?,?,'
'?,?,?,?)',
(userID, resultID, correct, num_to_draw, num_drawn, certainty))
self.conn.commit()
except sqlite3.ProgrammingError:
pass
def insert_results(self, username, accuracy=None, num_attempts=None, num_correct=None):
"""
:param username:
:param accuracy: how certain the network was.
:param num_attempts:
:param num_correct:
:return:
"""
# when first
# creating result row, these values are none until the game ends, and they can be determined
try:
user = str(username)
self.c.execute('select UserID from Users where username = ?', (user,))
userID = self.c.fetchone()[0]
self.c.execute(
'insert into Results(UserID, Overall_accuracy,num_attempts, num_correct) VALUES(?,?,?,?)',
(userID, accuracy, num_attempts, num_correct))
self.c.execute('select * from Results')
self.conn.commit()
except sqlite3.ProgrammingError:
pass
def update_results(self, username, accuracy, num_attempts, num_correct): # when first
# creating result row, these values are none until the game ends, and they can be determined
try:
user = str(username)
self.c.execute('select UserID from Users where username = ?', (user,))
userID = self.c.fetchone()[0]
self.c.execute('select MAX(ResultID) from Results where userID = ?', (userID,))
resultID = self.c.fetchall() # want to find most recent result ID for updating. Since resultID auto increment,
# the most recent will be the largest. Therefore I can find the maximum value in the list c.fetchall()
resultID = resultID[0][0]
self.c.execute(
"Update Results SET(Overall_accuracy,num_attempts, num_correct) = (?,?,?) where UserID =? and ResultID = ?",
(accuracy, num_attempts, num_correct, userID, resultID))
self.c.execute('select * from Results')
self.conn.commit()
self.conn.close() # if connection not closed, database is locked, which means other clients cannot access it
except sqlite3.ProgrammingError:
pass