saslprep.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. # Copyright 2016-present MongoDB, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """An implementation of RFC4013 SASLprep."""
  15. from bson.py3compat import text_type as _text_type
  16. try:
  17. import stringprep
  18. except ImportError:
  19. HAVE_STRINGPREP = False
  20. def saslprep(data):
  21. """SASLprep dummy"""
  22. if isinstance(data, _text_type):
  23. raise TypeError(
  24. "The stringprep module is not available. Usernames and "
  25. "passwords must be ASCII strings.")
  26. return data
  27. else:
  28. HAVE_STRINGPREP = True
  29. import unicodedata
  30. # RFC4013 section 2.3 prohibited output.
  31. _PROHIBITED = (
  32. # A strict reading of RFC 4013 requires table c12 here, but
  33. # characters from it are mapped to SPACE in the Map step. Can
  34. # normalization reintroduce them somehow?
  35. stringprep.in_table_c12,
  36. stringprep.in_table_c21_c22,
  37. stringprep.in_table_c3,
  38. stringprep.in_table_c4,
  39. stringprep.in_table_c5,
  40. stringprep.in_table_c6,
  41. stringprep.in_table_c7,
  42. stringprep.in_table_c8,
  43. stringprep.in_table_c9)
  44. def saslprep(data, prohibit_unassigned_code_points=True):
  45. """An implementation of RFC4013 SASLprep.
  46. :Parameters:
  47. - `data`: The string to SASLprep. Unicode strings
  48. (python 2.x unicode, 3.x str) are supported. Byte strings
  49. (python 2.x str, 3.x bytes) are ignored.
  50. - `prohibit_unassigned_code_points`: True / False. RFC 3454
  51. and RFCs for various SASL mechanisms distinguish between
  52. `queries` (unassigned code points allowed) and
  53. `stored strings` (unassigned code points prohibited). Defaults
  54. to ``True`` (unassigned code points are prohibited).
  55. :Returns:
  56. The SASLprep'ed version of `data`.
  57. """
  58. if not isinstance(data, _text_type):
  59. return data
  60. if prohibit_unassigned_code_points:
  61. prohibited = _PROHIBITED + (stringprep.in_table_a1,)
  62. else:
  63. prohibited = _PROHIBITED
  64. # RFC3454 section 2, step 1 - Map
  65. # RFC4013 section 2.1 mappings
  66. # Map Non-ASCII space characters to SPACE (U+0020). Map
  67. # commonly mapped to nothing characters to, well, nothing.
  68. in_table_c12 = stringprep.in_table_c12
  69. in_table_b1 = stringprep.in_table_b1
  70. data = u"".join(
  71. [u"\u0020" if in_table_c12(elt) else elt
  72. for elt in data if not in_table_b1(elt)])
  73. # RFC3454 section 2, step 2 - Normalize
  74. # RFC4013 section 2.2 normalization
  75. data = unicodedata.ucd_3_2_0.normalize('NFKC', data)
  76. in_table_d1 = stringprep.in_table_d1
  77. if in_table_d1(data[0]):
  78. if not in_table_d1(data[-1]):
  79. # RFC3454, Section 6, #3. If a string contains any
  80. # RandALCat character, the first and last characters
  81. # MUST be RandALCat characters.
  82. raise ValueError("SASLprep: failed bidirectional check")
  83. # RFC3454, Section 6, #2. If a string contains any RandALCat
  84. # character, it MUST NOT contain any LCat character.
  85. prohibited = prohibited + (stringprep.in_table_d2,)
  86. else:
  87. # RFC3454, Section 6, #3. Following the logic of #3, if
  88. # the first character is not a RandALCat, no other character
  89. # can be either.
  90. prohibited = prohibited + (in_table_d1,)
  91. # RFC3454 section 2, step 3 and 4 - Prohibit and check bidi
  92. for char in data:
  93. if any(in_table(char) for in_table in prohibited):
  94. raise ValueError(
  95. "SASLprep: failed prohibited character check")
  96. return data