PageRenderTime 55ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/r2/r2/controllers/validator/wiki.py

https://github.com/stevewilber/reddit
Python | 294 lines | 184 code | 54 blank | 56 comment | 72 complexity | b32ce9729b14b6be2afb7f9904551fec MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0
  1. # The contents of this file are subject to the Common Public Attribution
  2. # License Version 1.0. (the "License"); you may not use this file except in
  3. # compliance with the License. You may obtain a copy of the License at
  4. # http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
  5. # License Version 1.1, but Sections 14 and 15 have been added to cover use of
  6. # software over a computer network and provide for limited attribution for the
  7. # Original Developer. In addition, Exhibit A has been modified to be consistent
  8. # with Exhibit B.
  9. #
  10. # Software distributed under the License is distributed on an "AS IS" basis,
  11. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  12. # the specific language governing rights and limitations under the License.
  13. #
  14. # The Original Code is reddit.
  15. #
  16. # The Original Developer is the Initial Developer. The Initial Developer of
  17. # the Original Code is reddit Inc.
  18. #
  19. # All portions of the code written by reddit are Copyright (c) 2006-2012 reddit
  20. # Inc. All Rights Reserved.
  21. ###############################################################################
  22. from os.path import normpath
  23. import datetime
  24. import re
  25. from pylons.controllers.util import redirect_to
  26. from pylons import c, g, request
  27. from r2.models.wiki import WikiPage, WikiRevision
  28. from r2.controllers.validator import Validator, validate, make_validated_kw
  29. from r2.lib.db import tdb_cassandra
  30. MAX_PAGE_NAME_LENGTH = g.wiki_max_page_name_length
  31. MAX_SEPARATORS = g.wiki_max_page_separators
  32. def wiki_validate(*simple_vals, **param_vals):
  33. def val(fn):
  34. def newfn(self, *a, **env):
  35. kw = make_validated_kw(fn, simple_vals, param_vals, env)
  36. for e in c.errors:
  37. e = c.errors[e]
  38. if e.code:
  39. self.handle_error(e.code, e.name)
  40. return fn(self, *a, **kw)
  41. return newfn
  42. return val
  43. def this_may_revise(page=None):
  44. if not c.user_is_loggedin:
  45. return False
  46. if c.user_is_admin:
  47. return True
  48. return may_revise(c.site, c.user, page)
  49. def this_may_view(page):
  50. user = c.user if c.user_is_loggedin else None
  51. return may_view(c.site, user, page)
  52. def may_revise(sr, user, page=None):
  53. if sr.is_moderator(user):
  54. # Mods may always contribute
  55. return True
  56. elif sr.wikimode != 'anyone':
  57. # If the user is not a mod and the mode is not anyone,
  58. # then the user may not edit.
  59. return False
  60. if page and page.restricted and not page.special:
  61. # People may not contribute to restricted pages
  62. # (Except for special pages)
  63. return False
  64. if sr.is_wikibanned(user):
  65. # Users who are wiki banned in the subreddit may not contribute
  66. return False
  67. if page and not may_view(sr, user, page):
  68. # Users who are not allowed to view the page may not contribute to the page
  69. return False
  70. if not user.can_wiki():
  71. # Global wiki contributute ban
  72. return False
  73. if page and page.has_editor(user.name):
  74. # If the user is an editor on the page, they may edit
  75. return True
  76. if not sr.can_submit(user):
  77. # If the user can not submit to the subreddit
  78. # They should not be able to contribute
  79. return False
  80. if page and page.special:
  81. # If this is a special page
  82. # (and the user is not a mod or page editor)
  83. # They should not be allowed to revise
  84. return False
  85. if page and page.permlevel > 0:
  86. # If the page is beyond "anyone may contribute"
  87. # A normal user should not be allowed to revise
  88. return False
  89. if sr.is_wikicontributor(user):
  90. # If the user is a wiki contributor, they may revise
  91. return True
  92. karma = max(user.karma('link', sr), user.karma('comment', sr))
  93. if karma < sr.wiki_edit_karma:
  94. # If the user has too few karma, they should not contribute
  95. return False
  96. age = (datetime.datetime.now(g.tz) - user._date).days
  97. if age < sr.wiki_edit_age:
  98. # If they user's account is too young
  99. # They should not contribute
  100. return False
  101. # Otherwise, allow them to contribute
  102. return True
  103. def may_view(sr, user, page):
  104. # User being None means not logged in
  105. mod = sr.is_moderator(user) if user else False
  106. if mod:
  107. # Mods may always view
  108. return True
  109. if page.special:
  110. # Special pages may always be viewed
  111. # (Permission level ignored)
  112. return True
  113. level = page.permlevel
  114. if level < 2:
  115. # Everyone may view in levels below 2
  116. return True
  117. if level == 2:
  118. # Only mods may view in level 2
  119. return mod
  120. # In any other obscure level,
  121. # (This should not happen but just in case)
  122. # nobody may view.
  123. return False
  124. def normalize_page(page):
  125. # Case insensitive page names
  126. page = page.lower()
  127. # Normalize path
  128. page = normpath(page)
  129. # Chop off initial "/", just in case it exists
  130. page = page.lstrip('/')
  131. return page
  132. class AbortWikiError(Exception):
  133. pass
  134. page_match_regex = re.compile(r'^[\w_/]+\Z')
  135. class VWikiPage(Validator):
  136. def __init__(self, param, required=True, restricted=True, modonly=False, **kw):
  137. self.restricted = restricted
  138. self.modonly = modonly
  139. self.required = required
  140. Validator.__init__(self, param, **kw)
  141. def run(self, page):
  142. if not page:
  143. # If no page is specified, give the index page
  144. page = "index"
  145. try:
  146. page = str(page)
  147. except UnicodeEncodeError:
  148. return self.set_error('INVALID_PAGE_NAME', code=400)
  149. if ' ' in page:
  150. new_name = page.replace(' ', '_')
  151. url = '%s/%s' % (c.wiki_base_url, new_name)
  152. redirect_to(url)
  153. if not page_match_regex.match(page):
  154. return self.set_error('INVALID_PAGE_NAME', code=400)
  155. page = normalize_page(page)
  156. c.page = page
  157. if (not c.is_wiki_mod) and self.modonly:
  158. return self.set_error('MOD_REQUIRED', code=403)
  159. try:
  160. wp = self.validpage(page)
  161. except AbortWikiError:
  162. return
  163. # TODO: MAKE NOT REQUIRED
  164. c.page_obj = wp
  165. return wp
  166. def validpage(self, page):
  167. try:
  168. wp = WikiPage.get(c.site, page)
  169. if self.restricted and wp.restricted:
  170. if not wp.special:
  171. self.set_error('RESTRICTED_PAGE', code=403)
  172. raise AbortWikiError
  173. if not this_may_view(wp):
  174. self.set_error('MAY_NOT_VIEW', code=403)
  175. raise AbortWikiError
  176. return wp
  177. except tdb_cassandra.NotFound:
  178. if self.required:
  179. self.set_error('PAGE_NOT_FOUND', code=404)
  180. raise AbortWikiError
  181. return None
  182. def validversion(self, version, pageid=None):
  183. if not version:
  184. return
  185. try:
  186. r = WikiRevision.get(version, pageid)
  187. if r.is_hidden and not c.is_wiki_mod:
  188. self.set_error('HIDDEN_REVISION', code=403)
  189. raise AbortWikiError
  190. return r
  191. except (tdb_cassandra.NotFound, ValueError):
  192. self.set_error('INVALID_REVISION', code=404)
  193. raise AbortWikiError
  194. class VWikiPageAndVersion(VWikiPage):
  195. def run(self, page, *versions):
  196. wp = VWikiPage.run(self, page)
  197. if c.errors:
  198. return
  199. validated = []
  200. for v in versions:
  201. try:
  202. validated += [self.validversion(v, wp._id) if v and wp else None]
  203. except AbortWikiError:
  204. return
  205. return tuple([wp] + validated)
  206. class VWikiPageRevise(VWikiPage):
  207. def run(self, page, previous=None):
  208. wp = VWikiPage.run(self, page)
  209. if c.errors:
  210. return
  211. if not wp:
  212. return self.set_error('INVALID_PAGE', code=404)
  213. if not this_may_revise(wp):
  214. return self.set_error('MAY_NOT_REVISE', code=403)
  215. if previous:
  216. try:
  217. prev = self.validversion(previous, wp._id)
  218. except AbortWikiError:
  219. return
  220. return (wp, prev)
  221. return (wp, None)
  222. class VWikiPageCreate(VWikiPage):
  223. def __init__(self, param, **kw):
  224. VWikiPage.__init__(self, param, required=False, **kw)
  225. def run(self, page):
  226. wp = VWikiPage.run(self, page)
  227. if c.errors:
  228. return
  229. if wp:
  230. c.error = {'reason': 'PAGE_EXISTS'}
  231. elif c.is_wiki_mod and WikiPage.is_special(page):
  232. c.error = {'reason': 'PAGE_CREATED_ELSEWHERE'}
  233. elif WikiPage.is_restricted(page):
  234. self.set_error('RESTRICTED_PAGE', code=403)
  235. return
  236. elif page.count('/') > MAX_SEPARATORS:
  237. c.error = {'reason': 'PAGE_NAME_MAX_SEPARATORS', 'MAX_SEPARATORS': MAX_SEPARATORS}
  238. elif len(page) > MAX_PAGE_NAME_LENGTH:
  239. c.error = {'reason': 'PAGE_NAME_LENGTH', 'max_length': MAX_PAGE_NAME_LENGTH}
  240. return this_may_revise()