test_auth.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. # This file is part of Radicale Server - Calendar Server
  2. # Copyright © 2012-2016 Jean-Marc Martins
  3. # Copyright © 2012-2017 Guillaume Ayoub
  4. # Copyright © 2017-2018 Unrud<unrud@outlook.com>
  5. #
  6. # This library is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This library is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. Radicale tests with simple requests and authentication.
  20. """
  21. import base64
  22. import os
  23. import shutil
  24. import tempfile
  25. from radicale import Application, config
  26. from .test_base import BaseTest
  27. import pytest # isort:skip
  28. class TestBaseAuthRequests(BaseTest):
  29. """Tests basic requests with auth.
  30. We should setup auth for each type before creating the Application object.
  31. """
  32. def setup(self):
  33. self.configuration = config.load()
  34. self.colpath = tempfile.mkdtemp()
  35. self.configuration["storage"]["filesystem_folder"] = self.colpath
  36. # Disable syncing to disk for better performance
  37. self.configuration["internal"]["filesystem_fsync"] = "False"
  38. # Set incorrect authentication delay to a very low value
  39. self.configuration["auth"]["delay"] = "0.002"
  40. def teardown(self):
  41. shutil.rmtree(self.colpath)
  42. def _test_htpasswd(self, htpasswd_encryption, htpasswd_content,
  43. test_matrix=None):
  44. """Test htpasswd authentication with user "tmp" and password "bepo"."""
  45. htpasswd_file_path = os.path.join(self.colpath, ".htpasswd")
  46. with open(htpasswd_file_path, "w") as f:
  47. f.write(htpasswd_content)
  48. self.configuration["auth"]["type"] = "htpasswd"
  49. self.configuration["auth"]["htpasswd_filename"] = htpasswd_file_path
  50. self.configuration["auth"]["htpasswd_encryption"] = htpasswd_encryption
  51. self.application = Application(self.configuration)
  52. if test_matrix is None:
  53. test_matrix = (
  54. ("tmp", "bepo", 207), ("tmp", "tmp", 401), ("tmp", "", 401),
  55. ("unk", "unk", 401), ("unk", "", 401), ("", "", 401))
  56. for user, password, expected_status in test_matrix:
  57. status, _, answer = self.request(
  58. "PROPFIND", "/",
  59. HTTP_AUTHORIZATION="Basic %s" % base64.b64encode(
  60. ("%s:%s" % (user, password)).encode()).decode())
  61. assert status == expected_status
  62. def test_htpasswd_plain(self):
  63. self._test_htpasswd("plain", "tmp:bepo")
  64. def test_htpasswd_plain_password_split(self):
  65. self._test_htpasswd("plain", "tmp:be:po", (
  66. ("tmp", "be:po", 207), ("tmp", "bepo", 401)))
  67. def test_htpasswd_sha1(self):
  68. self._test_htpasswd("sha1", "tmp:{SHA}UWRS3uSJJq2itZQEUyIH8rRajCM=")
  69. def test_htpasswd_ssha(self):
  70. self._test_htpasswd("ssha", "tmp:{SSHA}qbD1diw9RJKi0DnW4qO8WX9SE18W")
  71. def test_htpasswd_md5(self):
  72. try:
  73. import passlib # noqa: F401
  74. except ImportError:
  75. pytest.skip("passlib is not installed")
  76. self._test_htpasswd("md5", "tmp:$apr1$BI7VKCZh$GKW4vq2hqDINMr8uv7lDY/")
  77. def test_htpasswd_crypt(self):
  78. try:
  79. import crypt # noqa: F401
  80. except ImportError:
  81. pytest.skip("crypt is not installed")
  82. self._test_htpasswd("crypt", "tmp:dxUqxoThMs04k")
  83. def test_htpasswd_bcrypt(self):
  84. try:
  85. from passlib.hash import bcrypt
  86. from passlib.exc import MissingBackendError
  87. except ImportError:
  88. pytest.skip("passlib is not installed")
  89. try:
  90. bcrypt.hash("test-bcrypt-backend")
  91. except MissingBackendError:
  92. pytest.skip("bcrypt backend for passlib is not installed")
  93. self._test_htpasswd(
  94. "bcrypt",
  95. "tmp:$2y$05$oD7hbiQFQlvCM7zoalo/T.MssV3VNTRI3w5KDnj8NTUKJNWfVpvRq")
  96. def test_htpasswd_multi(self):
  97. self._test_htpasswd("plain", "ign:ign\ntmp:bepo")
  98. @pytest.mark.skipif(os.name == "nt", reason="leading and trailing "
  99. "whitespaces not allowed in file names")
  100. def test_htpasswd_whitespace_preserved(self):
  101. self._test_htpasswd("plain", " tmp : bepo ",
  102. ((" tmp ", " bepo ", 207),))
  103. def test_htpasswd_whitespace_not_trimmed(self):
  104. self._test_htpasswd("plain", " tmp : bepo ", (("tmp", "bepo", 401),))
  105. def test_htpasswd_comment(self):
  106. self._test_htpasswd("plain", "#comment\n #comment\n \ntmp:bepo\n\n")
  107. def test_remote_user(self):
  108. self.configuration["auth"]["type"] = "remote_user"
  109. self.application = Application(self.configuration)
  110. status, _, answer = self.request(
  111. "PROPFIND", "/",
  112. """<?xml version="1.0" encoding="utf-8"?>
  113. <propfind xmlns="DAV:">
  114. <prop>
  115. <current-user-principal />
  116. </prop>
  117. </propfind>""", REMOTE_USER="test")
  118. assert status == 207
  119. assert ">/test/<" in answer
  120. def test_http_x_remote_user(self):
  121. self.configuration["auth"]["type"] = "http_x_remote_user"
  122. self.application = Application(self.configuration)
  123. status, _, answer = self.request(
  124. "PROPFIND", "/",
  125. """<?xml version="1.0" encoding="utf-8"?>
  126. <propfind xmlns="DAV:">
  127. <prop>
  128. <current-user-principal />
  129. </prop>
  130. </propfind>""", HTTP_X_REMOTE_USER="test")
  131. assert status == 207
  132. assert ">/test/<" in answer
  133. def test_custom(self):
  134. """Custom authentication."""
  135. self.configuration["auth"]["type"] = "tests.custom.auth"
  136. self.application = Application(self.configuration)
  137. status, _, answer = self.request(
  138. "PROPFIND", "/tmp", HTTP_AUTHORIZATION="Basic %s" %
  139. base64.b64encode(("tmp:").encode()).decode())
  140. assert status == 207