app.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  1. import datetime as datetime
  2. from urllib.parse import urlparse
  3. import webbrowser
  4. from emailtool.emailer import Emailer
  5. from flask import Flask, render_template, request, redirect, url_for, session, send_file
  6. from gevent.pywsgi import WSGIServer
  7. import socket
  8. import logging
  9. from peewee import Model, CharField, DateTimeField, SqliteDatabase, BooleanField, fn
  10. import hashlib
  11. import os
  12. import csv
  13. import time
  14. import threading
  15. import atexit
  16. db_name = 'app.db'
  17. db = SqliteDatabase(db_name)
  18. class User(Model):
  19. username = CharField()
  20. password = CharField()
  21. date_created = DateTimeField(default=datetime.datetime.now)
  22. logged_in = BooleanField(default=False)
  23. class Meta:
  24. database = db
  25. class IlsUser(Model):
  26. username = CharField()
  27. email = CharField()
  28. reset_datetime = CharField()
  29. class Meta:
  30. database = db
  31. class Settings(Model):
  32. name = CharField()
  33. value = CharField()
  34. class Meta:
  35. database = db
  36. class Schedule(Model):
  37. interval = CharField()
  38. class Meta:
  39. database = db
  40. class Log(Model):
  41. date = DateTimeField(default=datetime.datetime.now)
  42. username = CharField()
  43. action = CharField()
  44. class Meta:
  45. database = db
  46. class PasswordResetLog(Model):
  47. date = CharField(default=datetime.datetime.now().strftime('%Y-%m-%d'))
  48. username = CharField()
  49. class Meta:
  50. database = db
  51. class EmailReminderLog(Model):
  52. date = CharField(default=datetime.datetime.now().strftime('%Y-%m-%d'))
  53. username = CharField()
  54. email = CharField()
  55. interval = CharField()
  56. sent = BooleanField(default=False)
  57. ignore = BooleanField(default=False)
  58. class Meta:
  59. database = db
  60. def reminder_cleanup():
  61. reminders = EmailReminderLog.select().where(EmailReminderLog.date < datetime.datetime.now().strftime('%Y-%m-%d')).execute()
  62. for reminder in reminders:
  63. reminder.delete_instance()
  64. def scheduler(stop_event):
  65. while not stop_event.is_set():
  66. db.connect()
  67. # Get all scheduled intervals from the DB
  68. intervals = Schedule.select().execute()
  69. users = IlsUser.select().execute()
  70. # Get settings
  71. domain_name_setting = Settings.get(Settings.name == 'Domain Name')
  72. domain_name = domain_name_setting.value
  73. password_reset_interval_setting = Settings.get(Settings.name == 'Password Reset Interval')
  74. password_reset_interval = password_reset_interval_setting.value
  75. smtp_host_setting = Settings.get(Settings.name == 'SMTP Host')
  76. smtp_host = smtp_host_setting.value
  77. smtp_port_setting = Settings.get(Settings.name == 'SMTP Port')
  78. smtp_port = smtp_port_setting.value
  79. smtp_username_setting = Settings.get(Settings.name == 'SMTP Username')
  80. smtp_username = smtp_username_setting.value
  81. smtp_password_setting = Settings.get(Settings.name == 'SMTP Password')
  82. smtp_password = smtp_password_setting.value
  83. reset_link_setting = Settings.get(Settings.name == 'Password Reset URL')
  84. reset_link = reset_link_setting.value
  85. # Loop through each user and calculate the number of days until the users password
  86. # expires based on the password_reset_interval value.
  87. # Loop through all users
  88. # Schedule emails to be sent for each user and interval
  89. for user in users:
  90. # get the scheduled intervals
  91. for interval in intervals:
  92. # Calculate the date (yyyy-mm-dd) that the password will expire and subtract the interval
  93. # from that date to get the date that the email should be sent.
  94. # Example: If the password reset interval is 90 days and the current date is 2019-01-01
  95. # then the password will expire on 2019-04-01. If the interval is 30 days then the
  96. # email should be sent on 2019-03-02.
  97. # Calculate the date that the password will expire
  98. password_expiration_date = datetime.datetime.strptime(user.reset_datetime, "%Y-%m-%d") + datetime.timedelta(days=int(password_reset_interval))
  99. # Subtract the interval from the password expiration date to get the date that the email should be sent
  100. email_date = password_expiration_date - datetime.timedelta(days=int(interval.interval))
  101. # check if reminder has already been saved to the DB
  102. reminder = EmailReminderLog.select().where(EmailReminderLog.date == email_date.strftime('%Y-%m-%d'), EmailReminderLog.username == user.username, EmailReminderLog.interval == interval.interval).execute()
  103. if reminder:
  104. continue
  105. else:
  106. # Store the date in the DB
  107. EmailReminderLog.create(date=email_date.strftime('%Y-%m-%d'), email=user.email, username=user.username, interval=interval.interval).save()
  108. # Get all email reminders from the DB
  109. # if today's date matches the EmailReminderLog date then send the email and mark the reminder as sent
  110. reminders = EmailReminderLog.select().where(EmailReminderLog.date == datetime.datetime.now().strftime('%Y-%m-%d'), EmailReminderLog.ignore == 0, EmailReminderLog.sent == 0).execute()
  111. for reminder in reminders:
  112. # Send the email
  113. emailer = Emailer(smtp_host, smtp_port, smtp_username, smtp_password)
  114. if reminder.interval == 1:
  115. # 1 day reminder email
  116. emailer.send_email(reminder.email, 'ILS Password Reset Reminder',
  117. 'The password for ILS account with the username "'
  118. + reminder.username + '" will expire in ' + reminder.interval +
  119. ' day. To reset your password, please click the following link: ' + reset_link + '. If the password is not reset today the account will be locked.')
  120. else:
  121. # more than a day reminder email
  122. emailer.send_email(reminder.email, 'ILS Password Reset Reminder',
  123. 'The password for ILS account with the username "'
  124. + reminder.username + '" will expire in ' + reminder.interval +
  125. ' days. To reset your password, please click the following link: ' + reset_link)
  126. # Mark the reminder as sent
  127. reminder.sent = True
  128. reminder.save()
  129. db.close()
  130. # Remove all reminders from the DB that are older than today's date
  131. reminder_cleanup()
  132. time.sleep(10) # Sleep for 15 minutes
  133. # Encrypt the password with SHA256
  134. def encrypt_password(password):
  135. hash = hashlib.sha256(password.encode('utf-8')).hexdigest()
  136. return hash
  137. # Check for DB tables and create if they don't exist
  138. if db.table_exists('user') is False:
  139. db.create_tables([User, ])
  140. User.create(username='admin', password=encrypt_password('admin'),
  141. date_created=datetime.datetime.now(), logged_in=False).save()
  142. if db.table_exists('ilsuser') is False:
  143. db.create_tables([IlsUser, ])
  144. if db.table_exists('settings') is False:
  145. db.create_tables([Settings, ])
  146. Settings.create(name='Debug Mode', value=False).save()
  147. Settings.create(name='HTTP Port', value=5055).save()
  148. Settings.create(name='SMTP Host', value="").save()
  149. Settings.create(name='SMTP Port', value="587").save()
  150. Settings.create(name='SMTP Username', value="").save()
  151. Settings.create(name='SMTP Password', value="").save()
  152. Settings.create(name='Domain Name', value="lynx").save()
  153. Settings.create(name='Password Reset URL', value="https://terminal.idaho-lynx.org").save()
  154. Settings.create(name='Password Reset Interval', value="90").save()
  155. if db.table_exists('log') is False:
  156. db.create_tables([Log, ])
  157. if db.table_exists('password_reset_log') is False:
  158. db.create_tables([PasswordResetLog, ])
  159. if db.table_exists('schedule') is False:
  160. db.create_tables([Schedule, ])
  161. if db.table_exists('email_reminder_log') is False:
  162. db.create_tables([EmailReminderLog, ])
  163. db.close()
  164. settings = Settings.select().execute()
  165. debug_setting = Settings.get(Settings.name == 'Debug Mode')
  166. debug = debug_setting.value
  167. http_port_setting = Settings.get(Settings.name == 'HTTP Port')
  168. http_port = http_port_setting.value
  169. domain_name_setting = Settings.get(Settings.name == 'Domain Name')
  170. domain_name = domain_name_setting.value
  171. password_reset_url_setting = Settings.get(Settings.name == 'Password Reset URL')
  172. password_reset_url = password_reset_url_setting.value
  173. password_reset_interval_setting = Settings.get(Settings.name == 'Password Reset Interval')
  174. password_reset_interval = password_reset_interval_setting.value
  175. log = logging.getLogger('werkzeug')
  176. log.setLevel(logging.INFO)
  177. if debug.lower() == 'true':
  178. debug = True
  179. else:
  180. debug = False
  181. # send_email('aday@twinfallspubliclibrary.org', 'TEST', 'This is a test email')
  182. def shutdown_session(exception=None):
  183. print('Stopping HTTP Service...')
  184. http_server.stop()
  185. # Get the systems hostname
  186. def get_hostname():
  187. return socket.gethostname()
  188. # Get systems IP address
  189. def get_ip_address():
  190. return socket.gethostbyname(socket.gethostname())
  191. # Method to check if a URL is valid using regex
  192. def is_valid_url(url):
  193. try:
  194. result = urlparse(url)
  195. if all([result.scheme, result.netloc]):
  196. url = '{uri.scheme}://{uri.netloc}/'.format(uri=result)
  197. else:
  198. url = False
  199. return url
  200. except:
  201. return False
  202. def requires_auth():
  203. if 'username' in session:
  204. username = session['username']
  205. user = User.get(User.username == username)
  206. if user.logged_in is True:
  207. return True
  208. else:
  209. return False
  210. else:
  211. return False
  212. def admin_password_check():
  213. admin_user = User.get(User.username == 'admin')
  214. if admin_user.password == encrypt_password('admin'):
  215. return True
  216. db.close()
  217. # Start the scheduler loop in another thread
  218. shutdown_scheduler = threading.Event()
  219. scheduler_thread = threading.Thread(target=scheduler, args=(shutdown_scheduler,))
  220. scheduler_thread.start()
  221. # Start the HTTP Server
  222. app = Flask(__name__)
  223. #app.secret_key = os.urandom(24)
  224. app.secret_key = "DEVELOPMENT"
  225. def format_time_ago(timestamp):
  226. """Calculate the time passed since a datetime stamp and format it as a human-readable string."""
  227. now = datetime.datetime.now()
  228. diff = now - datetime.datetime.strptime(timestamp, "%Y-%m-%d")
  229. if diff.days > 365:
  230. years = diff.days // 365
  231. return f"{years} year{'s' if years > 1 else ''} ago"
  232. if diff.days > 30:
  233. months = diff.days // 30
  234. return f"{months} month{'s' if months > 1 else ''} ago"
  235. if diff.days > 0:
  236. return f"{diff.days} day{'s' if diff.days > 1 else ''} ago"
  237. return ""
  238. # Create method for time until
  239. def format_time_until(timestamp):
  240. """Calculate the time until a datetime stamp and format it as a human-readable string."""
  241. # Convert timestamp into datetime object
  242. timestamp = datetime.datetime.strptime(timestamp, '%Y-%m-%d')
  243. now = datetime.datetime.now()
  244. diff = timestamp - now
  245. if diff.days > 365:
  246. years = diff.days // 365
  247. return f"in {years} year{'s' if years > 1 else ''}"
  248. # return months and days
  249. if diff.days > 30:
  250. months = diff.days // 30
  251. days = diff.days % 30
  252. return f"in {months} month{'s' if months > 1 else ''} and {days} day{'s' if days > 1 else ''}"
  253. # return days
  254. if diff.days > 0:
  255. return f"in {diff.days} day{'s' if diff.days > 1 else ''}"
  256. # return hours and minutes
  257. if diff.seconds > 3600:
  258. hours = diff.seconds // 3600
  259. minutes = (diff.seconds % 3600) // 60
  260. return f"in {hours} hour{'s' if hours > 1 else ''} and {minutes} minute{'s' if minutes > 1 else ''}"
  261. # return minutes
  262. if diff.seconds > 60:
  263. minutes = diff.seconds // 60
  264. return f"in {minutes} minute{'s' if minutes > 1 else ''}"
  265. if diff.days > 30:
  266. months = diff.days // 30
  267. return f"in {months} month{'s' if months > 1 else ''}"
  268. if diff.days > 0:
  269. return f"in {diff.days} day{'s' if diff.days > 1 else ''}"
  270. return "Today"
  271. app.jinja_env.filters['time_since'] = format_time_ago
  272. app.jinja_env.filters['time_until'] = format_time_until
  273. @app.before_request
  274. def before_request():
  275. db.connect()
  276. @app.after_request
  277. def after_request(response):
  278. db.close()
  279. return response
  280. @app.route('/admin/password/reset', methods=['GET', 'POST'])
  281. def admin_password_reset():
  282. message = None
  283. if request.method == 'POST':
  284. password = request.form.get('password')
  285. password_confirm = request.form.get('password_confirm')
  286. if password != password_confirm:
  287. message = 'Passwords do not match'
  288. else:
  289. try:
  290. user = User.get(User.username == 'admin')
  291. user.password = encrypt_password(password)
  292. user.logged_in = True
  293. user.save()
  294. session['username'] = 'admin'
  295. return redirect(url_for('admin'))
  296. except Exception as e:
  297. print(e)
  298. message = 'Username not found'
  299. context = {
  300. 'message': message,
  301. }
  302. return render_template('admin_password_reset.html', context=context)
  303. # Create a route for the home page
  304. @app.route('/', methods=['GET', 'POST'])
  305. def index():
  306. error = None
  307. reset = False
  308. reset_url = is_valid_url(password_reset_url)
  309. reset_url_error = False
  310. if reset_url is False:
  311. reset_url_error = True
  312. if request.method == 'POST':
  313. username = request.form.get('username')
  314. # Check for the username in the DB
  315. try:
  316. user = IlsUser.filter(IlsUser.username == username).first()
  317. except Exception as e:
  318. print(e)
  319. user = None
  320. if user:
  321. # Reset login datetime
  322. user.reset_datetime = datetime.datetime.now().strftime("%Y-%m-%d")
  323. PasswordResetLog.create(username=user.username, date_created=datetime.datetime.now()).save()
  324. user.save()
  325. # Open the reset URL in a new tab if the URL is valid
  326. if reset_url is not False:
  327. webbrowser.open_new_tab(str(reset_url))
  328. # Set reset to True to pass back to the view to display the correct content back to the user.
  329. reset = True
  330. else:
  331. error = 'Invalid username'
  332. context = {
  333. 'domain': domain_name,
  334. 'error': error,
  335. 'reset': reset,
  336. 'reset_url': reset_url,
  337. 'reset_url_error': reset_url_error,
  338. }
  339. return render_template('index.html', context=context)
  340. # Create a route for admin page
  341. @app.route('/admin/')
  342. def admin():
  343. if admin_password_check():
  344. return redirect(url_for('admin_password_reset'))
  345. # Check to see if user is logged in
  346. if not requires_auth():
  347. return redirect(url_for('login'))
  348. # Get a count of all IlsUsers
  349. try:
  350. ils_users = IlsUser.select().count()
  351. except Exception as e:
  352. ils_users = None
  353. # Get a count of all EmailReminders that are not ignored or sent
  354. try:
  355. total_email_reminders = EmailReminderLog.select().where(EmailReminderLog.sent == False, EmailReminderLog.ignore == False).count()
  356. except Exception as e:
  357. total_email_reminders = None
  358. # Get a list of coming email reminders for the next 7 days
  359. try:
  360. email_reminders = PasswordResetLog.select().where(PasswordResetLog.date_created < datetime.datetime.now() + datetime.timedelta(days=7)).execute()
  361. except Exception as e:
  362. email_reminders = None
  363. # Get a list ils users that have passwords expiring in the next 7 days
  364. try:
  365. ils_users_expiring = IlsUser.select().where(IlsUser.password_expires < datetime.datetime.now() + datetime.timedelta(days=7)).execute()
  366. except Exception as e:
  367. ils_users_expiring = None
  368. # get a list of email notifications sent in the last 7 days
  369. try:
  370. # Get a list of all future email reminders
  371. email_reminders = EmailReminderLog.select().order_by(-EmailReminderLog.date).limit(15).execute()
  372. except Exception as e:
  373. email_reminders = None
  374. context = {
  375. 'ils_user_count': ils_users,
  376. 'ils_users_expiring': ils_users_expiring,
  377. 'email_reminders': email_reminders,
  378. 'total_email_reminders': total_email_reminders,
  379. }
  380. return render_template('admin.html', context=context)
  381. @app.route('/admin/users/', methods=['GET', 'POST'])
  382. def admin_users():
  383. # Check to see if user is logged in
  384. if not requires_auth():
  385. return redirect(url_for('login'))
  386. message = None
  387. if request.method == 'POST':
  388. username = request.form.get('username')
  389. password = request.form.get('password')
  390. confirm_password = request.form.get('confirm_password')
  391. # Check to see if username already exists
  392. try:
  393. user = User.filter(User.username == username).first()
  394. except Exception as e:
  395. print(e)
  396. user = None
  397. if user:
  398. message = 'Username already exists'
  399. else:
  400. if password == confirm_password:
  401. User.create(username=username, password=encrypt_password(password),
  402. date_created=datetime.datetime.now(), logged_in=False).save()
  403. message = 'User created successfully'
  404. Log.create(username=session['username'], action='Created admin user: %s' % username, ).save()
  405. else:
  406. message = 'Passwords do not match'
  407. # Get all admin users from the DB
  408. users = User.select().execute()
  409. context = {
  410. 'users': users,
  411. 'message': message,
  412. }
  413. return render_template('admin_users.html', context=context)
  414. @app.route('/admin/users/edit/<int:id>', methods=['GET', 'POST'])
  415. def admin_users_edit(id):
  416. # Check to see if user is logged in
  417. if not requires_auth():
  418. return redirect(url_for('login'))
  419. # Get the user from the DB
  420. user = User.get(User.id == id)
  421. message = None
  422. if request.method == 'POST':
  423. username = request.form.get('username')
  424. password = request.form.get('password')
  425. confirm_password = request.form.get('confirm_password')
  426. # Check to see if username already exists
  427. all_users = list()
  428. users = User.select().execute()
  429. for u in users:
  430. if u.username != user.username:
  431. all_users.append(u.username)
  432. if username in all_users:
  433. message = 'Username already exists'
  434. else:
  435. user.username = username
  436. if password is not None or password != '':
  437. if password == confirm_password:
  438. user.password = encrypt_password(password)
  439. else:
  440. message = 'Passwords do not match'
  441. user.save()
  442. message = 'User updated successfully'
  443. Log.create(username=session['username'], action='Updated admin user: %s' % username, ).save()
  444. context = {
  445. 'user': user,
  446. 'message': message,
  447. }
  448. return render_template('admin_user_edit.html', context=context)
  449. @app.route('/admin/users/delete/<int:id>', methods=['GET', 'POST'])
  450. def admin_users_delete(id):
  451. # Check to see if user is logged in
  452. if not requires_auth():
  453. return redirect(url_for('login'))
  454. # Get the user from the DB
  455. user = User.get(User.id == id)
  456. if user.username != 'admin':
  457. # Unset session variable if the user is deleting their own account
  458. if user.username == session['username']:
  459. session.pop('username', None)
  460. username = user.username
  461. user.delete_instance()
  462. Log.create(username=session['username'], action='Removed admin user: %s' % username, ).save()
  463. return redirect(url_for('admin_users'))
  464. @app.route('/admin/users/ils', methods=['GET', 'POST'])
  465. def admin_ils_users():
  466. # Check to see if user is logged in
  467. if not requires_auth():
  468. return redirect(url_for('login'))
  469. message = None
  470. if request.method == 'POST':
  471. username = request.form.get('username')
  472. email = request.form.get('email')
  473. date = request.form.get('date')
  474. print(date)
  475. # Check to see if username already exists
  476. try:
  477. user = IlsUser.filter(IlsUser.username == username).first()
  478. except Exception as e:
  479. print(e)
  480. user = None
  481. # Check if date field was submitted if so convert to datetime object
  482. if date is None or date == '':
  483. date = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d')
  484. if user:
  485. message = 'Username already exists'
  486. else:
  487. IlsUser.create(username=username, email=email, reset_datetime=date).save()
  488. message = 'ILS User: %s created successfully' % username
  489. Log.create(username=session['username'], action='Created ILS User: %s' % username, ).save()
  490. # Get all admin users from the DB
  491. users = IlsUser.select().execute()
  492. context = {
  493. 'users': users,
  494. 'message': message,
  495. }
  496. return render_template('admin_ils_users.html', context=context)
  497. @app.route('/admin/users/ils/delete/<int:id>', methods=['GET', 'POST'])
  498. def admin_ils_users_delete(id):
  499. # Check to see if user is logged in
  500. if not requires_auth():
  501. return redirect(url_for('login'))
  502. # Get the user from the DB
  503. user = IlsUser.get(IlsUser.id == id)
  504. username = user.username
  505. # Remove scheduled email reminders
  506. try:
  507. email_reminders = EmailReminderLog.select().where(EmailReminderLog.username == user.username).execute()
  508. for reminder in email_reminders:
  509. reminder.delete_instance()
  510. print('here')
  511. except Exception as e:
  512. print('there', e)
  513. user.delete_instance()
  514. Log.create(username=session['username'], action='Removed ILS user: %s' % username, ).save()
  515. return redirect(url_for('admin_ils_users'))
  516. @app.route('/admin/users/ils/edit/<int:id>', methods=['GET', 'POST'])
  517. def admin_ils_users_edit(id):
  518. # Check to see if user is logged in
  519. if not requires_auth():
  520. return redirect(url_for('login'))
  521. # Get the user from the DB
  522. user = IlsUser.get(IlsUser.id == id)
  523. message = None
  524. if request.method == 'POST':
  525. username = request.form.get('username')
  526. email = request.form.get('email')
  527. date = request.form.get('date')
  528. # Check to see if username already exists
  529. all_users = list()
  530. users = IlsUser.select().execute()
  531. for u in users:
  532. if u.username != user.username:
  533. all_users.append(u.username)
  534. if username in all_users:
  535. message = 'Username already exists'
  536. else:
  537. # Remove all scheduled email reminders. They will automatically be rescheduled with the new information.
  538. try:
  539. email_reminders = EmailReminderLog.select().where(EmailReminderLog.username == user.username).execute()
  540. for reminder in email_reminders:
  541. reminder.delete_instance()
  542. except Exception as e:
  543. print(e)
  544. user.username = username
  545. user.email = email
  546. if date is not None and date != '':
  547. user.reset_datetime = date
  548. else:
  549. user.reset_datetime = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d')
  550. user.save()
  551. message = 'User updated successfully'
  552. Log.create(username=session['username'], action='Updated ILS user: %s' % username, ).save()
  553. context = {
  554. 'user': user,
  555. 'message': message,
  556. }
  557. return render_template('admin_ils_user_edit.html', context=context)
  558. # create a route for generating a CSV file for download
  559. @app.route('/admin/users/ils/csv/download', methods=['GET', 'POST'])
  560. def admin_ils_users_csv_download():
  561. # Check to see if user is logged in
  562. if not requires_auth():
  563. return redirect(url_for('login'))
  564. # Create a CSV file with the users and don't add a blank line between rows
  565. with open('ils-users_%s.csv' % datetime.datetime.now().strftime('%Y-%m-%d'), 'w', newline='') as f:
  566. writer = csv.writer(f)
  567. writer.writerow(['ILS Username', 'Email', 'Last Password Reset (YYYY-MM-DD)'])
  568. users = IlsUser.select().execute()
  569. for user in users:
  570. date = datetime.datetime.strptime(user.reset_datetime, '%Y-%m-%d').strftime('%Y-%m-%d')
  571. writer.writerow([user.username, user.email, date])
  572. Log.create(username=session['username'], action='Downloaded ILS user CSV file.').save()
  573. # return the CSV file to the user
  574. return send_file('ils-users_%s.csv' % datetime.datetime.now().strftime('%Y-%m-%d'), as_attachment=True)
  575. @app.route('/admin/users/ils/csv/import', methods=['GET', 'POST'])
  576. def admin_ils_users_csv_import():
  577. # Check to see if user is logged in
  578. if not requires_auth():
  579. return redirect(url_for('login'))
  580. message = None
  581. if request.method == 'POST':
  582. csv_file = request.files['csv']
  583. if csv_file.filename != '':
  584. csv_file.save(os.path.join('uploads', csv_file.filename))
  585. with open(os.path.join('uploads', csv_file.filename), 'r') as f:
  586. reader = csv.reader(f)
  587. for row in reader:
  588. username = row[0]
  589. email = row[1]
  590. reset_date = row[2]
  591. try:
  592. reset_date = datetime.datetime.strptime(reset_date, '%m/%d/%Y').strftime('%Y-%m-%d')
  593. except Exception as e:
  594. print(e)
  595. try:
  596. reset_date = datetime.datetime.strptime(reset_date, '%Y-%m-%d').strftime('%Y-%m-%d')
  597. except Exception as e:
  598. print(e)
  599. # ignore the header row
  600. if username == 'ILS Username':
  601. continue
  602. # ignore blank rows
  603. if username == '':
  604. continue
  605. # Check if user already exists and if it does update the entry
  606. try:
  607. user = IlsUser.filter(IlsUser.username == username).first()
  608. except Exception as e:
  609. print(e)
  610. user = None
  611. if user:
  612. user.email = email
  613. # Check if the reset date is blank and if it is set it to the current date
  614. if reset_date == '' or reset_date is None:
  615. user.reset_datetime = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d')
  616. else:
  617. user.reset_datetime = reset_date
  618. user.save()
  619. else:
  620. if reset_date == '' or reset_date is None:
  621. user.reset_datetime = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d')
  622. else:
  623. reset_datetime = reset_date
  624. IlsUser.create(username=username, email=email, reset_datetime=reset_datetime).save()
  625. # Delete the uploaded file
  626. os.remove(os.path.join('uploads', csv_file.filename))
  627. return redirect(url_for('admin_ils_users'))
  628. context = {
  629. 'message': message,
  630. }
  631. return render_template('csv.html', context=context)
  632. @app.route('/admin/settings', methods=['GET', 'POST'])
  633. def settings():
  634. # Check to see if user is logged in
  635. if not requires_auth():
  636. return redirect(url_for('login'))
  637. message = None
  638. # Process form submission
  639. if request.method == 'POST':
  640. # Assign form values to variables
  641. id = request.form.get('id')
  642. value = request.form.get('value')
  643. # Check if the setting exists
  644. try:
  645. setting = Settings.get(Settings.id == id)
  646. except Exception as e:
  647. print(e)
  648. setting = None
  649. if setting:
  650. # Scrub the values based on the setting name
  651. if setting.name == 'Debug Mode':
  652. if value.lower() == 'true':
  653. value = True
  654. else:
  655. value = False
  656. value = bool(value)
  657. elif setting.name == 'HTTP Port':
  658. value = str(value)
  659. elif setting.name == 'SMTP Host':
  660. value = str(value)
  661. elif setting.name == 'SMTP Port':
  662. value = str(value)
  663. elif setting.name == 'SMTP Username':
  664. value = str(value)
  665. elif setting.name == 'SMTP Password':
  666. value = str(value)
  667. elif setting.name == 'Domain Name':
  668. value = str(value)
  669. elif setting.name == 'Password Reset URL':
  670. value = str(value)
  671. elif setting.name == 'Password Reset Interval':
  672. if value.isdigit():
  673. value = int(value)
  674. else:
  675. value = setting.value
  676. # Remove all scheduled reminders
  677. reminders = EmailReminderLog.select().execute()
  678. for reminder in reminders:
  679. reminder.delete_instance()
  680. # Update the setting
  681. old_value = setting.value
  682. setting.value = value
  683. setting.save()
  684. Log.create(username=session['username'], action='Changed %s setting from "%s" to "%s"' % (setting.name,
  685. old_value,
  686. value)).save()
  687. message = '%s updated successfully' % setting.name
  688. # Get settings from DB
  689. all_settings = Settings.select().execute()
  690. context = {
  691. 'settings': all_settings,
  692. 'message': message,
  693. }
  694. return render_template('settings.html', context=context)
  695. @app.route('/admin/schedule', methods=['GET', 'POST'])
  696. def schedule():
  697. # Check to see if user is logged in
  698. if not requires_auth():
  699. return redirect(url_for('login'))
  700. message = None
  701. # Get all schedules from the DB
  702. schedules = Schedule.select().order_by(Schedule.interval.cast("INTEGER")).execute()
  703. # add schedule
  704. if request.method == 'POST':
  705. # Assign form values to variables
  706. interval = request.form.get('interval')
  707. # Check if interval is a number
  708. try:
  709. int(interval)
  710. except Exception as e:
  711. print(e)
  712. message = 'Error creating schedule. Value submitted must be a whole number.'
  713. return render_template('schedule.html', context={'message': message, 'schedules': schedules})
  714. # Check if the schedule already exists
  715. try:
  716. schedule = Schedule.get(Schedule.interval == interval)
  717. except Exception as e:
  718. print(e)
  719. schedule = None
  720. if schedule:
  721. message = 'Schedule already exists'
  722. else:
  723. # Create the schedule
  724. Schedule.create(interval=interval).save()
  725. Log.create(username=session['username'], action='Created schedule for %s day interval.' % interval).save()
  726. message = 'Schedule: %s created successfully' % interval
  727. schedules = Schedule.select().order_by(Schedule.interval.cast("INTEGER")).execute()
  728. context = {
  729. 'schedules': schedules,
  730. 'message': message,
  731. }
  732. return render_template('schedule.html', context=context)
  733. @app.route('/admin/schedule/emails')
  734. def scheduled_emails():
  735. # Check to see if user is logged in
  736. if not requires_auth():
  737. return redirect(url_for('login'))
  738. # Get all future emailreminderlogs
  739. reminders = EmailReminderLog.select().where(EmailReminderLog.date > datetime.datetime.now(), EmailReminderLog.ignore == False).order_by(
  740. -EmailReminderLog.date).execute()
  741. context = {
  742. 'reminders': reminders,
  743. }
  744. return render_template('scheduled_emails.html', context=context)
  745. @app.route('/admin/schedule/emails/clear')
  746. def reset_scheduled_emails():
  747. # Check to see if user is logged in
  748. if not requires_auth():
  749. return redirect(url_for('login'))
  750. # Get all future emailreminderlogs
  751. reminders = EmailReminderLog.select().execute()
  752. for reminder in reminders:
  753. reminder.delete_instance()
  754. return redirect(url_for('scheduled_emails'))
  755. # remove schedule
  756. @app.route('/admin/schedule/remove/reminder/<int:id>')
  757. def reminder_remove(id):
  758. # Check to see if user is logged in
  759. if not requires_auth():
  760. return redirect(url_for('login'))
  761. # Get EmailReminderLogs with the interval of the schedule being removed
  762. email_reminder_log = EmailReminderLog.get(EmailReminderLog.id == id)
  763. Log.create(username=session['username'],
  764. action='Removed %s day reminder for %s (%s)' % (email_reminder_log.interval, email_reminder_log.username, email_reminder_log.email)).save()
  765. email_reminder_log.ignore = True
  766. email_reminder_log.save()
  767. return redirect(url_for('scheduled_emails'))
  768. # remove schedule
  769. @app.route('/admin/schedule/remove/<int:id>', methods=['GET', 'POST'])
  770. def schedule_remove(id):
  771. # Check to see if user is logged in
  772. if not requires_auth():
  773. return redirect(url_for('login'))
  774. # Get Schedule by id
  775. try:
  776. schedule = Schedule.get(Schedule.id == id)
  777. except Exception as e:
  778. schedule = None
  779. if schedule:
  780. # Get EmailReminderLogs with the interval of the schedule being removed
  781. email_reminder_logs = EmailReminderLog.select().where(EmailReminderLog.interval == schedule.interval).execute()
  782. for email_reminder_log in email_reminder_logs:
  783. email_reminder_log.delete_instance()
  784. schedule.delete_instance()
  785. Log.create(username=session['username'], action='Removed schedule for a %s day reminder.' % schedule.interval).save()
  786. return redirect(url_for('schedule'))
  787. @app.route('/admin/system/log')
  788. def system_log():
  789. # Check to see if user is logged in
  790. if not requires_auth():
  791. return redirect(url_for('login'))
  792. # Get all logs from the DB
  793. logs = Log.select().order_by(Log.id.desc()).execute()
  794. context = {
  795. 'logs': logs,
  796. }
  797. return render_template('system_log.html', context=context)
  798. @app.route('/admin/system/log/password/resets')
  799. def password_reset_log():
  800. # Check to see if user is logged in
  801. if not requires_auth():
  802. return redirect(url_for('login'))
  803. # Get all logs from the DB
  804. logs = PasswordResetLog.select().order_by(PasswordResetLog.id.desc()).execute()
  805. context = {
  806. 'logs': logs,
  807. }
  808. return render_template('password_reset_log.html', context=context)
  809. @app.route('/logout')
  810. def logout():
  811. if 'username' in session:
  812. username = session['username']
  813. user = User.get(User.username == username)
  814. user.logged_in = False
  815. Log.create(username=session['username'], action='Logged out').save()
  816. user.save()
  817. session.pop('username', None)
  818. return redirect(url_for('login'))
  819. @app.route('/login', methods=['GET', 'POST'])
  820. def login():
  821. if request.method == 'POST':
  822. username = request.form.get('username')
  823. password = encrypt_password(request.form.get('password'))
  824. try:
  825. user = User.filter(User.username == username and User.password == password).first()
  826. except Exception as e:
  827. print(e)
  828. user = None
  829. session.pop('username', None)
  830. if user:
  831. # Login user
  832. session['username'] = request.form.get('username')
  833. user.logged_in = True
  834. Log.create(username=session['username'], action='Logged in').save()
  835. user.save()
  836. return redirect(url_for('admin'))
  837. else:
  838. error = 'Invalid Credentials. Please try again.'
  839. context = {
  840. 'error': error
  841. }
  842. return render_template('login.html', context=context)
  843. context = {
  844. }
  845. return render_template('login.html', context=context)
  846. def clean_up():
  847. shutdown_scheduler.set()
  848. http_server.stop()
  849. if __name__ == "__main__":
  850. print("------------------------- Start up -----------------------------")
  851. print("Starting HTTP Service on port %s..." % http_port)
  852. if debug is True:
  853. print("Debug mode is enabled.")
  854. http_server = WSGIServer(('0.0.0.0', int(http_port)), app)
  855. else:
  856. http_server = WSGIServer(('0.0.0.0', int(http_port)), app, log=log, error_log=log)
  857. print("HTTP Service Started.")
  858. print("--------------------- Application Details ---------------------")
  859. print("Application started at %s" % datetime.datetime.now())
  860. print("System IP Address: %s" % get_ip_address())
  861. print("System Hostname: %s" % get_hostname())
  862. print("Access the Dashboard using a web browser using any of the following:")
  863. print("http://%s:%s or http://%s:%s" % (get_hostname(), http_port, get_ip_address(), http_port))
  864. print("---------------------------------------------------------------")
  865. print("To stop the application close this window.")
  866. print("---------------------------------------------------------------")
  867. http_server.serve_forever()
  868. atexit.register(clean_up)