requirements.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. """
  2. flaskbb.utils.requirements
  3. ~~~~~~~~~~~~~~~~~~~~~~~~~~
  4. Authorization requirements for FlaskBB.
  5. :copyright: (c) 2015 by the FlaskBB Team.
  6. :license: BSD, see LICENSE for more details
  7. """
  8. import logging
  9. from flask_allows import And, Or, Permission, Requirement
  10. from flaskbb.exceptions import FlaskBBError
  11. from flaskbb.forum.locals import current_forum, current_post, current_topic
  12. from flaskbb.forum.models import Forum, Post, Topic
  13. logger = logging.getLogger(__name__)
  14. class Has(Requirement):
  15. def __init__(self, permission):
  16. self.permission = permission
  17. def __repr__(self):
  18. return "<Has({!s})>".format(self.permission)
  19. def fulfill(self, user):
  20. return user.permissions.get(self.permission, False)
  21. class IsAuthed(Requirement):
  22. def fulfill(self, user):
  23. return user.is_authenticated
  24. class IsModeratorInForum(IsAuthed):
  25. def __init__(self, forum=None, forum_id=None):
  26. self.forum_id = forum_id
  27. self.forum = forum
  28. def fulfill(self, user):
  29. moderators = self._get_forum_moderators()
  30. return (
  31. super(IsModeratorInForum, self).fulfill(user)
  32. and self._user_is_forum_moderator(user, moderators)
  33. )
  34. def _user_is_forum_moderator(self, user, moderators):
  35. return user in moderators
  36. def _get_forum_moderators(self):
  37. return self._get_forum().moderators
  38. def _get_forum(self):
  39. if self.forum is not None:
  40. return self.forum
  41. elif self.forum_id is not None:
  42. return self._get_forum_from_id()
  43. return self._get_forum_from_request()
  44. def _get_forum_from_id(self):
  45. return Forum.query.get(self.forum_id)
  46. def _get_forum_from_request(self):
  47. if not current_forum:
  48. raise FlaskBBError("Could not load forum data")
  49. return current_forum
  50. class IsSameUser(IsAuthed):
  51. def __init__(self, topic_or_post=None):
  52. self._topic_or_post = topic_or_post
  53. def fulfill(self, user):
  54. return (
  55. super(IsSameUser, self).fulfill(user)
  56. and user.id == self._determine_user()
  57. )
  58. def _determine_user(self):
  59. if self._topic_or_post is not None:
  60. return self._topic_or_post.user_id
  61. return self._get_user_id_from_post()
  62. def _get_user_id_from_post(self):
  63. if current_post:
  64. return current_post.user_id
  65. elif current_topic:
  66. return current_topic.user_id
  67. else:
  68. raise FlaskBBError("Could not determine user")
  69. class TopicNotLocked(Requirement):
  70. def __init__(self, topic=None, topic_id=None, post_id=None, post=None):
  71. self._topic = topic
  72. self._topic_id = topic_id
  73. self._post = post
  74. self._post_id = post_id
  75. def fulfill(self, user):
  76. return not any(self._determine_locked())
  77. def _determine_locked(self):
  78. """
  79. Returns a pair of booleans:
  80. * Is the topic locked?
  81. * Is the forum the topic belongs to locked?
  82. Except in the case of a topic instance being provided to the
  83. constructor, all of these tuples are SQLA KeyedTuples.
  84. """
  85. if self._topic is not None:
  86. return self._topic.locked, self._topic.forum.locked
  87. elif self._post is not None:
  88. return self._post.topic.locked, self._post.topic.forum.locked
  89. elif self._topic_id is not None:
  90. return (
  91. Topic.query.join(Forum, Forum.id == Topic.forum_id).filter(
  92. Topic.id == self._topic_id
  93. ).with_entities(
  94. Topic.locked, Forum.locked
  95. ).first()
  96. )
  97. else:
  98. return self._get_topic_from_request()
  99. def _get_topic_from_request(self):
  100. if current_topic:
  101. return current_topic.locked, current_forum.locked
  102. else:
  103. raise FlaskBBError("How did you get this to happen?")
  104. class ForumNotLocked(Requirement):
  105. def __init__(self, forum=None, forum_id=None):
  106. self._forum = forum
  107. self._forum_id = forum_id
  108. def fulfill(self, user):
  109. return self._is_forum_locked()
  110. def _is_forum_locked(self):
  111. forum = self._determine_forum()
  112. return not forum.locked
  113. def _determine_forum(self):
  114. if self._forum is not None:
  115. return self._forum
  116. elif self._forum_id is not None:
  117. return Forum.query.get(self._forum_id)
  118. else:
  119. return self._get_forum_from_request()
  120. def _get_forum_from_request(self):
  121. if current_forum:
  122. return current_forum
  123. raise FlaskBBError("Could not determine forum")
  124. class CanAccessForum(Requirement):
  125. def fulfill(self, user):
  126. if not current_forum:
  127. raise FlaskBBError("Could not load forum data")
  128. forum_groups = {g.id for g in current_forum.groups}
  129. user_groups = {g.id for g in user.groups}
  130. return bool(forum_groups & user_groups)
  131. def IsAtleastModeratorInForum(forum_id=None, forum=None):
  132. return Or(
  133. IsAtleastSuperModerator,
  134. IsModeratorInForum(forum_id=forum_id, forum=forum),
  135. )
  136. IsMod = And(IsAuthed(), Has("mod"))
  137. IsSuperMod = And(IsAuthed(), Has("super_mod"))
  138. IsAdmin = And(IsAuthed(), Has("admin"))
  139. IsAtleastModerator = Or(IsAdmin, IsSuperMod, IsMod)
  140. IsAtleastSuperModerator = Or(IsAdmin, IsSuperMod)
  141. CanBanUser = Or(IsAtleastSuperModerator, Has("mod_banuser"))
  142. CanEditUser = Or(IsAtleastSuperModerator, Has("mod_edituser"))
  143. CanEditPost = Or(
  144. IsAtleastSuperModerator,
  145. And(IsModeratorInForum(), Has("editpost")),
  146. And(CanAccessForum(), IsSameUser(), Has("editpost"), TopicNotLocked()),
  147. )
  148. CanDeletePost = CanEditPost
  149. CanPostReply = Or(
  150. And(CanAccessForum(), Has("postreply"), TopicNotLocked()),
  151. IsModeratorInForum(),
  152. IsAtleastSuperModerator,
  153. )
  154. CanPostTopic = Or(
  155. And(CanAccessForum(), Has("posttopic"), ForumNotLocked()),
  156. IsAtleastSuperModerator,
  157. IsModeratorInForum(),
  158. )
  159. CanDeleteTopic = Or(
  160. IsAtleastSuperModerator,
  161. And(IsModeratorInForum(), Has("deletetopic")),
  162. And(CanAccessForum(), IsSameUser(), Has("deletetopic"), TopicNotLocked()),
  163. )
  164. def permission_with_identity(requirement, name=None):
  165. """
  166. Permission instance factory that can set a user at construction time
  167. can optionally name the closure for nicer debugging
  168. """
  169. def _(user):
  170. return Permission(requirement, identity=user)
  171. if name is not None:
  172. _.__name__ = name
  173. return _
  174. # Template Requirements
  175. def has_permission(permission):
  176. def _(user):
  177. return Permission(Has(permission), identity=user)
  178. _.__name__ = "Has_{}".format(permission)
  179. return _
  180. def can_moderate(user, forum):
  181. kwargs = {}
  182. if isinstance(forum, int):
  183. kwargs["forum_id"] = forum
  184. elif isinstance(forum, Forum):
  185. kwargs["forum"] = forum
  186. return Permission(IsAtleastModeratorInForum(**kwargs), identity=user)
  187. def can_post_reply(user, topic=None):
  188. kwargs = {}
  189. if isinstance(topic, int):
  190. kwargs["topic_id"] = topic
  191. elif isinstance(topic, Topic):
  192. kwargs["topic"] = topic
  193. return Permission(
  194. Or(
  195. IsAtleastSuperModerator,
  196. IsModeratorInForum(),
  197. And(Has("postreply"), TopicNotLocked(**kwargs)),
  198. ),
  199. identity=user,
  200. )
  201. def can_edit_post(user, topic_or_post=None):
  202. kwargs = {}
  203. if isinstance(topic_or_post, int):
  204. kwargs["topic_id"] = topic_or_post
  205. elif isinstance(topic_or_post, Topic):
  206. kwargs["topic"] = topic_or_post
  207. elif isinstance(topic_or_post, Post):
  208. kwargs["post"] = topic_or_post
  209. return Permission(
  210. Or(
  211. IsAtleastSuperModerator,
  212. And(IsModeratorInForum(), Has("editpost")),
  213. And(
  214. IsSameUser(topic_or_post),
  215. Has("editpost"),
  216. TopicNotLocked(**kwargs),
  217. ),
  218. ),
  219. identity=user,
  220. )
  221. def can_post_topic(user, forum):
  222. kwargs = {}
  223. if isinstance(forum, int):
  224. kwargs["forum_id"] = forum
  225. elif isinstance(forum, Forum):
  226. kwargs["forum"] = forum
  227. return Permission(
  228. Or(
  229. IsAtleastSuperModerator,
  230. IsModeratorInForum(**kwargs),
  231. And(Has("posttopic"), ForumNotLocked(**kwargs)),
  232. ),
  233. identity=user,
  234. )
  235. def can_delete_topic(user, topic=None):
  236. kwargs = {}
  237. if isinstance(topic, int):
  238. kwargs["topic_id"] = topic
  239. elif isinstance(topic, Topic):
  240. kwargs["topic"] = topic
  241. return Permission(
  242. Or(
  243. IsAtleastSuperModerator,
  244. And(IsModeratorInForum(), Has("deletetopic")),
  245. And(IsSameUser(), Has("deletetopic"), TopicNotLocked(**kwargs)),
  246. ),
  247. identity=user,
  248. )