DomainWhitelistPlugin.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <?php
  2. // This file is part of GNU social - https://www.gnu.org/software/social
  3. //
  4. // GNU social is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // GNU social is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Restrict the email addresses in a domain to a select whitelist
  18. *
  19. * @category Cache
  20. * @package GNUsocial
  21. * @author Evan Prodromou <evan@status.net>
  22. * @author Zach Copley <zach@status.net>
  23. * @copyright 2011 StatusNet, Inc.
  24. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  25. */
  26. defined('GNUSOCIAL') || die();
  27. /**
  28. * Restrict the email addresses to a domain whitelist
  29. *
  30. * @category General
  31. * @package GNUsocial
  32. * @author Evan Prodromou <evan@status.net>
  33. * @author Zach Copley <zach@status.net>
  34. * @copyright 2011 StatusNet, Inc.
  35. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  36. */
  37. class DomainWhitelistPlugin extends Plugin
  38. {
  39. const PLUGIN_VERSION = '2.0.0';
  40. /**
  41. * Get the path to the plugin's installation directory. Used
  42. * to link in js files and whatnot.
  43. *
  44. * @return String the absolute path
  45. */
  46. protected function getPath()
  47. {
  48. return preg_replace('/^' . preg_quote(INSTALLDIR, '/') . '\//', '', dirname(__FILE__));
  49. }
  50. /**
  51. * Link in a JavaScript script for the whitelist invite form
  52. *
  53. * @param Action $action Action being shown
  54. *
  55. * @return boolean hook flag
  56. */
  57. public function onEndShowStatusNetScripts($action)
  58. {
  59. $name = $action->arg('action');
  60. if ($name == 'invite') {
  61. $action->script($this->getPath() . '/js/whitelistinvite.js');
  62. }
  63. return true;
  64. }
  65. public function onRequireValidatedEmailPlugin_Override($user, &$knownGood)
  66. {
  67. $knownGood = (!empty($user->email) && $this->matchesWhitelist($user->email));
  68. return true;
  69. }
  70. public function onEndValidateUserEmail($user, $email, &$valid)
  71. {
  72. if ($valid) { // it's otherwise valid
  73. if (!$this->matchesWhitelist($email)) {
  74. $whitelist = $this->getWhitelist();
  75. if (count($whitelist) == 1) {
  76. // TRANS: Client exception thrown when a given e-mailaddress is not in the domain whitelist.
  77. // TRANS: %s is a whitelisted e-mail domain.
  78. $message = sprintf(
  79. _m('Email address must be in this domain: %s.'),
  80. $whitelist[0]
  81. );
  82. } else {
  83. // TRANS: Client exception thrown when a given e-mailaddress is not in the domain whitelist.
  84. // TRANS: %s are whitelisted e-mail domains separated by comma's (localisable).
  85. $message = sprintf(
  86. _m('Email address must be in one of these domains: %s.'),
  87. // TRANS: Separator for whitelisted domains.
  88. implode(_m('SEPARATOR', ', '), $whitelist)
  89. );
  90. }
  91. throw new ClientException($message);
  92. }
  93. }
  94. return true;
  95. }
  96. public function onStartAddEmailAddress($user, $email)
  97. {
  98. if (!$this->matchesWhitelist($email)) {
  99. // TRANS: Exception thrown when an e-mail address does not match the site's domain whitelist.
  100. throw new Exception(_m('That email address is not allowed on this site.'));
  101. }
  102. return true;
  103. }
  104. public function onEndValidateEmailInvite($user, $email, &$valid)
  105. {
  106. if ($valid) {
  107. $valid = $this->matchesWhitelist($email);
  108. }
  109. return true;
  110. }
  111. public function matchesWhitelist($email)
  112. {
  113. $whitelist = $this->getWhitelist();
  114. if (empty($whitelist) || empty($whitelist[0])) {
  115. return true;
  116. }
  117. $userDomain = $this->domainFromEmail($email);
  118. return in_array($userDomain, $whitelist);
  119. }
  120. /**
  121. * Helper function to pull out a domain from
  122. * an email address
  123. *
  124. * @param string $email and email address
  125. * @return string the domain
  126. */
  127. public function domainFromEmail($email)
  128. {
  129. $parts = explode('@', $email);
  130. return strtolower(trim($parts[1]));
  131. }
  132. public function getWhitelist()
  133. {
  134. $whitelist = common_config('email', 'whitelist');
  135. if (is_array($whitelist)) {
  136. return $this->sortWhiteList($whitelist);
  137. } else {
  138. return explode('|', $whitelist);
  139. }
  140. }
  141. /**
  142. * This is a filter function passed in to array_filter()
  143. * in order to strip out the user's domain, which will
  144. * be re-inserted as the first element (see sortWhitelist()
  145. * below).
  146. *
  147. * @param string $domain domain to check
  148. * @return boolean whether to include the domain
  149. */
  150. public function userDomainFilter($domain)
  151. {
  152. $user = common_current_user();
  153. $userDomain = $this->domainFromEmail($user->email);
  154. if ($userDomain == $domain) {
  155. return false;
  156. }
  157. return true;
  158. }
  159. /**
  160. * This function sorts the whitelist alphabetically, and sets the
  161. * current user's domain as the first element in the array of
  162. * allowed domains. Mostly, this is for the JavaScript on the invite
  163. * page--in the case of multiple allowed domains, it's nicer if the
  164. * user's own domain is the first option, and this seemed like a good
  165. * way to do it.
  166. *
  167. * @param array $whitelist whitelist of allowed email domains
  168. * @return array an ordered or sorted version of the whitelist
  169. */
  170. public function sortWhitelist($whitelist)
  171. {
  172. $whitelist = array_unique($whitelist);
  173. natcasesort($whitelist);
  174. $user = common_current_user();
  175. if (!empty($user) && !empty($user->email)) {
  176. $userDomain = $this->domainFromEmail($user->email);
  177. $orderedWhitelist = array_values(
  178. array_filter(
  179. $whitelist,
  180. array($this, "userDomainFilter")
  181. )
  182. );
  183. if (in_array($userDomain, $whitelist)) {
  184. array_unshift($orderedWhitelist, $userDomain);
  185. }
  186. return $orderedWhitelist;
  187. }
  188. return $whitelist;
  189. }
  190. /**
  191. * Show a fancier invite form when domains are restricted to the
  192. * whitelist.
  193. *
  194. * @param action $action the invite action
  195. * @return boolean hook value
  196. */
  197. public function onStartShowInviteForm($action)
  198. {
  199. $this->showConfirmDialog($action);
  200. $form = new WhitelistInviteForm($action, $this->getWhitelist());
  201. $form->show();
  202. return false;
  203. }
  204. public function showConfirmDialog($action)
  205. {
  206. // For JQuery UI modal dialog
  207. $action->elementStart(
  208. 'div',
  209. // TRANS: Title for invitiation deletion dialog.
  210. array('id' => 'confirm-dialog', 'title' => _m('Confirmation Required'))
  211. );
  212. // TRANS: Confirmation text for invitation deletion dialog.
  213. $action->text(_m('Really delete this invitation?'));
  214. $action->elementEnd('div');
  215. }
  216. /**
  217. * This is a bit of a hack. We take the values from the custom
  218. * whitelist invite form and reformat them so they look like
  219. * their coming from the the normal invite form.
  220. *
  221. * @param action &$action the invite action
  222. * @return boolean hook value
  223. */
  224. public function onStartSendInvitations(&$action)
  225. {
  226. $emails = [];
  227. $usernames = $action->arg('username');
  228. $domains = $action->arg('domain');
  229. foreach ($usernames as $key => $username) {
  230. if (!empty($username)) {
  231. $emails[] = $username . '@' . $domains[$key] . "\n";
  232. }
  233. }
  234. $action->args['addresses'] = implode('', $emails);
  235. return true;
  236. }
  237. public function onPluginVersion(array &$versions): bool
  238. {
  239. $versions[] = array('name' => 'DomainWhitelist',
  240. 'version' => self::PLUGIN_VERSION,
  241. 'author' => 'Evan Prodromou, Zach Copley',
  242. 'homepage' => GNUSOCIAL_ENGINE_REPO_URL . 'tree/master/plugins/DomainWhitelist',
  243. 'rawdescription' =>
  244. // TRANS: Plugin description.
  245. _m('Restrict domains for email users.'));
  246. return true;
  247. }
  248. }