housekeeping.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. # nm.debian.org website housekeeping
  2. #
  3. # Copyright (C) 2012--2014 Enrico Zini <enrico@debian.org>
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU Affero General Public License as
  7. # published by the Free Software Foundation, either version 3 of the
  8. # License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU Affero General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Affero General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. from __future__ import print_function
  18. from __future__ import absolute_import
  19. from __future__ import division
  20. from __future__ import unicode_literals
  21. import django_housekeeping as hk
  22. from django.db import transaction
  23. from backend.housekeeping import MakeLink, Housekeeper
  24. from . import models as dmodels
  25. from backend import const
  26. import backend.models as bmodels
  27. import process.models as pmodels
  28. import process.ops as pops
  29. import logging
  30. log = logging.getLogger(__name__)
  31. class ProgressFinalisationsOnAccountsCreated(hk.Task):
  32. """
  33. Update pending dm_ga processes after the account is created
  34. """
  35. DEPENDS = [MakeLink, Housekeeper]
  36. @transaction.atomic
  37. def run_main(self, stage):
  38. # Get a lits of accounts from DSA
  39. dm_ga_uids = set()
  40. dd_uids = set()
  41. for entry in dmodels.list_people():
  42. if entry.is_dd and entry.single("keyFingerPrint") is not None:
  43. dd_uids.add(entry.uid)
  44. else:
  45. dm_ga_uids.add(entry.uid)
  46. # Check if pending processes got an account
  47. for proc in bmodels.Process.objects.filter(is_active=True):
  48. if proc.progress != const.PROGRESS_DAM_OK: continue
  49. finalised_msg = None
  50. if proc.applying_for == const.STATUS_DM_GA and proc.person.uid in dm_ga_uids:
  51. finalised_msg = "guest LDAP account created by DSA"
  52. if proc.applying_for in (const.STATUS_DD_NU, const.STATUS_DD_U) and proc.person.uid in dd_uids:
  53. finalised_msg = "LDAP account created by DSA"
  54. if finalised_msg is not None:
  55. old_status = proc.person.status
  56. proc.finalize(finalised_msg, audit_author=self.hk.housekeeper.user, audit_notes="DSA created the account: finalising process")
  57. log.info("%s: %s finalised: %s changes status %s->%s",
  58. self.IDENTIFIER, self.hk.link(proc), proc.person.uid, old_status, proc.person.status)
  59. class NewGuestAccountsFromDSA(hk.Task):
  60. """
  61. Create new Person entries for guest accounts created by DSA
  62. """
  63. DEPENDS = [MakeLink]
  64. @transaction.atomic
  65. def run_main(self, stage):
  66. for entry in dmodels.list_people():
  67. # Skip DDs
  68. if entry.is_dd and entry.single("keyFingerPrint") is not None: continue
  69. fpr = entry.single("keyFingerPrint")
  70. # Skip people without fingerprints
  71. if fpr is None: continue
  72. email = entry.single("emailForward")
  73. # Skip entries without emails (happens when running outside of the Debian network)
  74. if email is None: continue
  75. # Find the corresponding person in our database
  76. person = bmodels.Person.objects.get_from_other_db(
  77. "LDAP",
  78. uid=entry.uid,
  79. fpr=fpr,
  80. email=email,
  81. format_person=self.hk.link,
  82. )
  83. if not person:
  84. # New DC_GA
  85. audit_notes = "created new guest account entry from LDAP"
  86. person = bmodels.Person.objects.create_user(
  87. cn=entry.single("cn"),
  88. mn=entry.single("mn") or "",
  89. sn=entry.single("sn") or "",
  90. email=email,
  91. email_ldap=email,
  92. uid=entry.uid,
  93. fpr=fpr,
  94. status=const.STATUS_DC_GA,
  95. username="{}@invalid.example.org".format(entry.uid),
  96. audit_author=self.hk.housekeeper.user,
  97. audit_notes=audit_notes,
  98. )
  99. log.warn("%s: %s %s", self.IDENTIFIER, self.hk.link(person), audit_notes)
  100. else:
  101. # Validate fields
  102. if person.uid is not None and person.uid != entry.uid:
  103. log.warn("%s: LDAP has uid %s for person %s, but uid is %s in our database",
  104. self.IDENTIFIER, entry.uid, self.hk.link(person), person.uid)
  105. continue
  106. if person.fpr is not None and person.fpr != fpr:
  107. log.warn("%s: LDAP has fingerprint %s for person %s, but fingerprint is %s in our database",
  108. self.IDENTIFIER, fpr, self.hk.link(person), person.fpr)
  109. continue
  110. audit_notes = ["entry found in LDAP"]
  111. # Ignore differences in email forward: they are caught by
  112. # CheckLDAPConsistency
  113. if person.status in (const.STATUS_DC_GA, const.STATUS_DM_GA):
  114. # We already know about it: nothing to do
  115. pass
  116. elif person.status in (const.STATUS_DC, const.STATUS_DM):
  117. if person.status == const.STATUS_DM:
  118. # DM that becomes DM_GA (acquires uid)
  119. new_status = const.STATUS_DM_GA
  120. else:
  121. # DC that becomes DC_GA (acquires uid)
  122. new_status = const.STATUS_DC_GA
  123. audit_notes = "entry found in LDAP, adding 'guest account' status"
  124. try:
  125. process = pmodels.Process.objects.get(person=person, applying_for=new_status)
  126. except pmodels.Process.DoesNotExist:
  127. process = None
  128. if process is None:
  129. bmodels.Process.objects.create_instant_process(
  130. person, new_status, [
  131. # Add a log entry documenting what is happening
  132. bmodels.Log(
  133. changed_by=self.hk.housekeeper.user,
  134. progress=const.PROGRESS_DONE,
  135. logtext=audit_notes,
  136. ),
  137. ])
  138. person.uid = entry.uid
  139. person.status = new_status
  140. person.save(audit_author=self.hk.housekeeper.user, audit_notes=audit_notes)
  141. else:
  142. op = pops.CloseProcess(
  143. audit_author=self.hk.housekeeper.user, audit_notes=audit_notes,
  144. process=process,
  145. logtext=audit_notes,
  146. )
  147. op.execute()
  148. log.info("%s: %s %s", self.IDENTIFIER, self.hk.link(person), audit_notes)
  149. else:
  150. # New uid on a status that is not supposed to have one:
  151. # just warn about it
  152. log.warn("%s: LDAP has new uid %s for person %s, which already has status %s in our database",
  153. self.IDENTIFIER, entry.uid, self.hk.link(person), const.ALL_STATUS_DESCS[person.status])
  154. class CheckLDAPConsistency(hk.Task):
  155. """
  156. Show entries that do not match between LDAP and our DB
  157. """
  158. DEPENDS = [MakeLink, Housekeeper]
  159. def run_main(self, stage):
  160. # Prefetch people and index them by uid
  161. people_by_uid = dict()
  162. for p in bmodels.Person.objects.all():
  163. if p.uid is None: continue
  164. people_by_uid[p.uid] = p
  165. for entry in dmodels.list_people():
  166. person = people_by_uid.get(entry.uid, None)
  167. if person is None:
  168. fpr = entry.single("keyFingerPrint")
  169. if fpr:
  170. log.warn("%s: %s has fingerprint %s and gid %s in LDAP, but is not in our db",
  171. self.IDENTIFIER, entry.uid, fpr, entry.single("gidNumber"))
  172. else:
  173. args = {
  174. "cn": entry.single("cn"),
  175. "mn": entry.single("mn") or "",
  176. "sn": entry.single("sn") or "",
  177. "email": entry.single("emailForward"),
  178. "email_ldap": entry.single("emailForward"),
  179. "uid": entry.uid,
  180. "fpr": "FIXME-REMOVED-" + entry.uid,
  181. "username": "{}@invalid.example.org".format(entry.uid),
  182. "audit_author": self.hk.housekeeper.user,
  183. }
  184. if entry.is_dd:
  185. args["status"] = const.STATUS_REMOVED_DD
  186. args["audit_notes"] = "created to mirror a removed guest account from LDAP"
  187. if not args["email"]: args["email"] = "{}@debian.org".format(entry.uid)
  188. else:
  189. args["status"] = const.STATUS_REMOVED_DC_GA
  190. args["audit_notes"] = "created to mirror a removed DD account from LDAP"
  191. if not args["email"]: args["email"] = "{}@example.org".format(entry.uid)
  192. person = bmodels.Person.objects.create_user(**args)
  193. log.warn("%s: %s: %s", self.IDENTIFIER, self.hk.link(person), args["audit_notes"])
  194. else:
  195. if entry.is_dd and entry.single("keyFingerPrint") is not None:
  196. if person.status not in (const.STATUS_DD_U, const.STATUS_DD_NU):
  197. log.warn("%s: %s has supplementaryGid 'Debian' and fingerprint %s in LDAP, but in our db the state is %s",
  198. self.IDENTIFIER, self.hk.link(person), entry.single("keyFingerPrint"), const.ALL_STATUS_DESCS[person.status])
  199. email = entry.single("emailForward")
  200. if email != person.email:
  201. if email is not None:
  202. log.info("%s: %s changing email_ldap from %s to %s (source: LDAP)",
  203. self.IDENTIFIER, self.hk.link(person), person.email, email)
  204. person.email_ldap = email
  205. person.save(audit_author=self.hk.housekeeper.user, audit_notes="updated email_ldap from LDAP")
  206. # It gives lots of errors when run outside of the debian.org
  207. # network, since emailForward is not exported there, and it has
  208. # no use case I can think of so far
  209. #
  210. # else:
  211. # log.info("%s: %s has email %s but emailForward is empty in LDAP",
  212. # self.IDENTIFIER, self.hk.link(person), person.email)