A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 line
6.8 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2009-2012 Ben Kurtovic <ben.kurtovic@verizon.net>
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a copy
  6. # of this software and associated documentation files (the "Software"), to deal
  7. # in the Software without restriction, including without limitation the rights
  8. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. # copies of the Software, and to permit persons to whom the Software is
  10. # furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. # SOFTWARE.
  22. from fnmatch import fnmatch
  23. import sqlite3 as sqlite
  24. from threading import Lock
  25. __all__ = ["PermissionsDB"]
  26. class PermissionsDB(object):
  27. """
  28. **EarwigBot: Permissions Database Manager**
  29. Controls the :file:`permissions.db` file, which stores the bot's owners and
  30. admins for the purposes of using certain dangerous IRC commands.
  31. """
  32. ADMIN = 1
  33. OWNER = 2
  34. def __init__(self, dbfile):
  35. self._dbfile = dbfile
  36. self._db_access_lock = Lock()
  37. self._data = {}
  38. def __repr__(self):
  39. """Return the canonical string representation of the PermissionsDB."""
  40. res = "PermissionsDB(dbfile={0!r})"
  41. return res.format(self._dbfile)
  42. def __str__(self):
  43. """Return a nice string representation of the PermissionsDB."""
  44. return "<PermissionsDB at {0}>".format(self._dbfile)
  45. def _create(self, conn):
  46. """Initialize the permissions database with its necessary tables."""
  47. query = """CREATE TABLE users (user_nick, user_ident, user_host,
  48. user_rank)"""
  49. conn.execute(query)
  50. def _is_rank(self, user, rank):
  51. """Return True if the given user has the given rank, else False."""
  52. try:
  53. for rule in self._data[rank]:
  54. if user in rule:
  55. return rule
  56. except KeyError:
  57. pass
  58. return False
  59. def _set_rank(self, user, rank):
  60. """Add a User to the database under a given rank."""
  61. query = "INSERT INTO users VALUES (?, ?, ?, ?)"
  62. with self._db_access_lock:
  63. with sqlite.connect(self._dbfile) as conn:
  64. conn.execute(query, (user.nick, user.ident, user.host, rank))
  65. try:
  66. self._data[rank].append(user)
  67. except KeyError:
  68. self._data[rank] = [user]
  69. return user
  70. def _del_rank(self, user, rank):
  71. """Remove a User from the database."""
  72. query = """DELETE FROM users WHERE user_nick = ? AND user_ident = ? AND
  73. user_host = ? AND user_rank = ?"""
  74. with self._db_access_lock:
  75. try:
  76. for rule in self._data[rank]:
  77. if user in rule:
  78. with sqlite.connect(self._dbfile) as conn:
  79. args = (user.nick, user.ident, user.host, rank)
  80. conn.execute(query, args)
  81. self._data[rank].remove(rule)
  82. return rule
  83. except KeyError:
  84. pass
  85. return None
  86. @property
  87. def data(self):
  88. """A dict of all entries in the permissions database."""
  89. return self._data
  90. def load(self):
  91. """Load permissions from an existing database, or create a new one."""
  92. query = "SELECT user_nick, user_ident, user_host, user_rank FROM users"
  93. self._data = {}
  94. with sqlite.connect(self._dbfile) as conn, self._db_access_lock:
  95. try:
  96. for nick, ident, host, rank in conn.execute(query):
  97. try:
  98. self._data[rank].append(_User(nick, ident, host))
  99. except KeyError:
  100. self._data[rank] = [_User(nick, ident, host)]
  101. except sqlite.OperationalError:
  102. self._create(conn)
  103. def has_exact(self, rank, nick="*", ident="*", host="*"):
  104. """Return ``True`` if there is an exact match for this rule."""
  105. try:
  106. for usr in self._data[rank]:
  107. if nick != usr.nick or ident != usr.ident or host != usr.host:
  108. continue
  109. return usr
  110. except KeyError:
  111. pass
  112. return False
  113. def is_admin(self, data):
  114. """Return ``True`` if the given user is a bot admin, else ``False``."""
  115. user = _User(data.nick, data.ident, data.host)
  116. return self._is_rank(user, rank=self.ADMIN)
  117. def is_owner(self, data):
  118. """Return ``True`` if the given user is a bot owner, else ``False``."""
  119. user = _User(data.nick, data.ident, data.host)
  120. return self._is_rank(user, rank=self.OWNER)
  121. def add_admin(self, nick="*", ident="*", host="*"):
  122. """Add a nick/ident/host combo to the bot admins list."""
  123. return self._set_rank(_User(nick, ident, host), rank=self.ADMIN)
  124. def add_owner(self, nick="*", ident="*", host="*"):
  125. """Add a nick/ident/host combo to the bot owners list."""
  126. return self._set_rank(_User(nick, ident, host), rank=self.OWNER)
  127. def remove_admin(self, nick="*", ident="*", host="*"):
  128. """Remove a nick/ident/host combo to the bot admins list."""
  129. return self._del_rank(_User(nick, ident, host), rank=self.ADMIN)
  130. def remove_owner(self, nick="*", ident="*", host="*"):
  131. """Remove a nick/ident/host combo to the bot owners list."""
  132. return self._del_rank(_User(nick, ident, host), rank=self.OWNER)
  133. class _User(object):
  134. """A class that represents an IRC user for the purpose of testing rules."""
  135. def __init__(self, nick, ident, host):
  136. self.nick = nick
  137. self.ident = ident
  138. self.host = host
  139. def __repr__(self):
  140. """Return the canonical string representation of the User."""
  141. res = "_User(nick={0!r}, ident={1!r}, host={2!r})"
  142. return res.format(self.nick, self.ident, self.host)
  143. def __str__(self):
  144. """Return a nice string representation of the User."""
  145. return "{0}!{1}@{2}".format(self.nick, self.ident, self.host)
  146. def __contains__(self, user):
  147. if fnmatch(user.nick, self.nick):
  148. if fnmatch(user.ident, self.ident):
  149. if fnmatch(user.host, self.host):
  150. return True
  151. return False