WFI.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import cherrypy
  2. import chevron
  3. import hashlib
  4. import bcrypt
  5. import os
  6. @cherrypy.tools.register("before_handler")
  7. def auth(groups):
  8. if cherrypy.session.get("login") in groups:
  9. return
  10. else:
  11. raise cherrypy.HTTPRedirect("/login")
  12. def human_readable_size(size):
  13. units = ("B", "KiB", "MiB", "GiB")
  14. unit = 0
  15. while size >= 1024 and unit < len(units):
  16. size /= 1024
  17. unit += 1
  18. return f"{size:.2f}{units[unit]}"
  19. def ensure_dir_exists(directory):
  20. if not os.path.exists(directory):
  21. os.mkdir(directory)
  22. def load_template(template):
  23. result = ""
  24. with open(f"{template}.html") as fp:
  25. result = fp.read()
  26. return result
  27. class WriteFreelyImages:
  28. def __init__(self, templates, base_dir, db, app_root=""):
  29. # templates must be a `dict`
  30. # key => page name like `index` or `login`
  31. # value => either function returning string or a string
  32. # db must be path to WriteFreely's database, either sqlite or sql
  33. # examples:
  34. # - sqlite:///tmp/wf.db
  35. # - mysql://username:password@host:port/db_name
  36. self.templates = map(lambda t: (t if callable(t[1]) else (t[0], lambda: t[1])),
  37. templates.items())
  38. self.templates = dict(self.templates)
  39. self.base_dir = base_dir
  40. self.SUPPORTED_DBS = ("mysql", "sqlite")
  41. if not db.startswith(self.SUPPORTED_DBS):
  42. raise ValueError("<db> must be either sqlite or mysql")
  43. if db.startswith("sqlite"):
  44. import sqlite3
  45. self.sqlite = sqlite3
  46. self.db = db.replace("sqlite://","")
  47. else:
  48. import MySQLdb
  49. parts = db.replace("mysql://", "").split("@")
  50. parts[0] = parts[0].split(":")
  51. parts[1] = parts[1].split(":")
  52. parts[1][1] = parts[1][1].split("/")
  53. args = dict()
  54. args["user"] = parts[0][0]
  55. args["passwd"] = parts[0][1]
  56. args["host"] = parts[1][0]
  57. args["port"] = int(parts[1][1][0])
  58. args["db"] = parts[1][1][1]
  59. # ^ I think it is only Farooq who understands what's going on here...
  60. # TODO: find a more "readable" way
  61. self.db_connection = MySQLdb.connect(**args)
  62. self.db = None
  63. self.size_limit = 1 * 1024 * 1024 # 1 MiB
  64. self.app_root = app_root
  65. self.message_with_redirect = f"""
  66. <html>
  67. <head>
  68. <meta http-equiv="refresh" content="4; url='{app_root}'
  69. </head>
  70. <body>{{{{message}}}}<br><a href="{app_root}">Back to home</a></body>
  71. </html>
  72. """
  73. @cherrypy.tools.auth(groups=("admin", "user"))
  74. @cherrypy.expose
  75. def index(self):
  76. username = cherrypy.session["uname"]
  77. size = os.path.getsize
  78. images = [
  79. {
  80. "name": image,
  81. "size": human_readable_size(
  82. size(f"{self.base_dir}/{username}/{image}"))
  83. }
  84. for image in
  85. os.listdir(f"{self.base_dir}/{username}")]
  86. template = self.templates["index"]()
  87. is_admin = cherrypy.session["login"] == "admin"
  88. data = {"files": images,
  89. "logged_as": username,
  90. "is_admin": is_admin,
  91. "app_root": self.app_root}
  92. return chevron.render(template, data)
  93. @cherrypy.expose
  94. @cherrypy.tools.auth(groups=("admin",))
  95. def admin(self, user=""):
  96. directory = f"{self.base_dir}/{user}"
  97. files = os.listdir(directory)
  98. template = self.templates["admin"]()
  99. size = lambda file: human_readable_size(os.path.getsize(file))
  100. if user:
  101. files = ({"name": file,
  102. "size": size(f"{self.base_dir}/{user}/{file}")}
  103. for file in files)
  104. else:
  105. files = ({"name": file} for file in files)
  106. data = {"files": files, "user": user, "app_root": self.app_root}
  107. return chevron.render(template, data)
  108. @cherrypy.expose
  109. @cherrypy.tools.auth(groups=("admin", "user"))
  110. def rename(self, old, new, user=""):
  111. if not user and cherrypy.session["login"] != "admin":
  112. t = self.message_with_template
  113. msg = "You must be an admin in order to rename somebody else's image"
  114. return chevron.render(t,
  115. {
  116. "message": msg
  117. })
  118. if not user:
  119. user = cherrypy.session["uname"]
  120. redirect = "/"
  121. else:
  122. redirect = f"/admin?user={user}"
  123. base = f"{self.base_dir}/{user}"
  124. os.rename(f"{base}/{old}", f"{base}/{new}")
  125. print(redirect)
  126. raise cherrypy.HTTPRedirect(redirect)
  127. @cherrypy.tools.auth(groups=("admin", "user"))
  128. @cherrypy.expose
  129. def upload(self, files):
  130. if type(files) != list:
  131. files = [files]
  132. directory = f"{self.base_dir}/{cherrypy.session['uname']}"
  133. for file in files:
  134. with open(f"{directory}/{file.filename}", "wb") as fp:
  135. buffer = file.file.read(6 * 1024)
  136. while buffer:
  137. fp.write(buffer)
  138. buffer = file.file.read(6 * 1024)
  139. raise cherrypy.HTTPRedirect("/")
  140. @cherrypy.tools.auth(groups=("admin", "user"))
  141. @cherrypy.expose
  142. def logout(self):
  143. cherrypy.session["login"] = ""
  144. cherrypy.session["uname"] = ""
  145. raise cherrypy.HTTPRedirect("/login")
  146. @cherrypy.tools.auth(groups=("admin", "user"))
  147. @cherrypy.expose
  148. def delete(self, image, user=""):
  149. if not user and cherrypy.session["login"] != "admin":
  150. t = self.message_with_template
  151. msg = "You must be an admin in order to delete somebody else's image"
  152. return chevron.render(t,
  153. {
  154. "message": msg
  155. })
  156. if not user:
  157. user = cherrypy.session["uname"]
  158. redirect = "/"
  159. else:
  160. redirect = f"/admin?user={user}"
  161. os.remove(f"{self.base_dir}/{user}/{image}")
  162. raise cherrypy.HTTPRedirect(redirect)
  163. @cherrypy.expose
  164. def login(self, username="", password=""):
  165. template = self.templates["login"]()
  166. incorrect = {"message": "Incorrect username or password"}
  167. blank = {"message": ""}
  168. incorrect["app_root"] = blank["app_root"] = self.app_root
  169. if username and password:
  170. if self.db:
  171. conn = self.sqlite.connect(self.db)
  172. cursor = conn.cursor()
  173. else:
  174. cursor = self.db_connection.cursor()
  175. cursor.execute(f"SELECT * FROM users WHERE username='{username}';")
  176. record = cursor.fetchone()
  177. if record:
  178. if not bcrypt.checkpw(password.encode(), record[2]):
  179. return chevron.render(template, incorrect)
  180. else:
  181. is_admin = record[0] == 1 # TODO: Get it from DB once WF
  182. # does so...
  183. else:
  184. return chevron.render(template, incorrect)
  185. cherrypy.session["login"] = "admin" if is_admin else "user"
  186. cherrypy.session["uname"] = username
  187. ensure_dir_exists(f"{self.base_dir}/{username}")
  188. if self.db:
  189. conn.close()
  190. raise cherrypy.HTTPRedirect("/")
  191. else:
  192. return chevron.render(template, blank)
  193. if __name__ == "__main__":
  194. templates = dict()
  195. templates["index"] = lambda: load_template("index")
  196. templates["admin"] = lambda: load_template("admin")
  197. templates["login"] = lambda: load_template("login")
  198. db_path = "sqlite:///home/farooqkz/writefreely/writefreely.db"
  199. app = WriteFreelyImages(templates, "./images/", db_path)
  200. conf = {"global": {"tools.sessions.on": True}}
  201. cherrypy.quickstart(app, "/", conf)