app.py 42 KB

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