app.py 35 KB

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