178 lines
6.6 KiB
Python
178 lines
6.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# This code is part of Ansible, but is an independent component.
|
|
# This particular file snippet, and this file snippet only, is BSD licensed.
|
|
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
|
# still belong to the author of the module, and may assign their own license
|
|
# to the complete work.
|
|
|
|
# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
|
|
#
|
|
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
__metaclass__ = type
|
|
|
|
from stringprep import (
|
|
in_table_a1,
|
|
in_table_b1,
|
|
in_table_c3,
|
|
in_table_c4,
|
|
in_table_c5,
|
|
in_table_c6,
|
|
in_table_c7,
|
|
in_table_c8,
|
|
in_table_c9,
|
|
in_table_c12,
|
|
in_table_c21_c22,
|
|
in_table_d1,
|
|
in_table_d2,
|
|
)
|
|
from unicodedata import normalize
|
|
|
|
from ansible.module_utils.six import text_type
|
|
|
|
|
|
def is_unicode_str(string):
|
|
return True if isinstance(string, text_type) else False
|
|
|
|
|
|
def mapping_profile(string):
|
|
"""RFC4013 Mapping profile implementation."""
|
|
# Regarding RFC4013,
|
|
# This profile specifies:
|
|
# - non-ASCII space characters [StringPrep, C.1.2] that can be
|
|
# mapped to SPACE (U+0020), and
|
|
# - the "commonly mapped to nothing" characters [StringPrep, B.1]
|
|
# that can be mapped to nothing.
|
|
|
|
tmp = []
|
|
for c in string:
|
|
# If not the "commonly mapped to nothing"
|
|
if not in_table_b1(c):
|
|
if in_table_c12(c):
|
|
# map non-ASCII space characters
|
|
# (that can be mapped) to Unicode space
|
|
tmp.append(u' ')
|
|
else:
|
|
tmp.append(c)
|
|
|
|
return u"".join(tmp)
|
|
|
|
|
|
def is_ral_string(string):
|
|
"""RFC3454 Check bidirectional category of the string"""
|
|
# Regarding RFC3454,
|
|
# Table D.1 lists the characters that belong
|
|
# to Unicode bidirectional categories "R" and "AL".
|
|
# If a string contains any RandALCat character, a RandALCat
|
|
# character MUST be the first character of the string, and a
|
|
# RandALCat character MUST be the last character of the string.
|
|
if in_table_d1(string[0]):
|
|
if not in_table_d1(string[-1]):
|
|
raise ValueError('RFC3454: incorrect bidirectional RandALCat string.')
|
|
return True
|
|
return False
|
|
|
|
|
|
def prohibited_output_profile(string):
|
|
"""RFC4013 Prohibited output profile implementation."""
|
|
# Implements:
|
|
# RFC4013, 2.3. Prohibited Output.
|
|
# This profile specifies the following characters as prohibited input:
|
|
# - Non-ASCII space characters [StringPrep, C.1.2]
|
|
# - ASCII control characters [StringPrep, C.2.1]
|
|
# - Non-ASCII control characters [StringPrep, C.2.2]
|
|
# - Private Use characters [StringPrep, C.3]
|
|
# - Non-character code points [StringPrep, C.4]
|
|
# - Surrogate code points [StringPrep, C.5]
|
|
# - Inappropriate for plain text characters [StringPrep, C.6]
|
|
# - Inappropriate for canonical representation characters [StringPrep, C.7]
|
|
# - Change display properties or deprecated characters [StringPrep, C.8]
|
|
# - Tagging characters [StringPrep, C.9]
|
|
# RFC4013, 2.4. Bidirectional Characters.
|
|
# RFC4013, 2.5. Unassigned Code Points.
|
|
|
|
# Determine how to handle bidirectional characters (RFC3454):
|
|
if is_ral_string(string):
|
|
# If a string contains any RandALCat characters,
|
|
# The string MUST NOT contain any LCat character:
|
|
is_prohibited_bidi_ch = in_table_d2
|
|
bidi_table = 'D.2'
|
|
else:
|
|
# Forbid RandALCat characters in LCat string:
|
|
is_prohibited_bidi_ch = in_table_d1
|
|
bidi_table = 'D.1'
|
|
|
|
RFC = 'RFC4013'
|
|
for c in string:
|
|
# RFC4013 2.3. Prohibited Output:
|
|
if in_table_c12(c):
|
|
raise ValueError('%s: prohibited non-ASCII space characters '
|
|
'that cannot be replaced (C.1.2).' % RFC)
|
|
if in_table_c21_c22(c):
|
|
raise ValueError('%s: prohibited control characters (C.2.1).' % RFC)
|
|
if in_table_c3(c):
|
|
raise ValueError('%s: prohibited private Use characters (C.3).' % RFC)
|
|
if in_table_c4(c):
|
|
raise ValueError('%s: prohibited non-character code points (C.4).' % RFC)
|
|
if in_table_c5(c):
|
|
raise ValueError('%s: prohibited surrogate code points (C.5).' % RFC)
|
|
if in_table_c6(c):
|
|
raise ValueError('%s: prohibited inappropriate for plain text '
|
|
'characters (C.6).' % RFC)
|
|
if in_table_c7(c):
|
|
raise ValueError('%s: prohibited inappropriate for canonical '
|
|
'representation characters (C.7).' % RFC)
|
|
if in_table_c8(c):
|
|
raise ValueError('%s: prohibited change display properties / '
|
|
'deprecated characters (C.8).' % RFC)
|
|
if in_table_c9(c):
|
|
raise ValueError('%s: prohibited tagging characters (C.9).' % RFC)
|
|
|
|
# RFC4013, 2.4. Bidirectional Characters:
|
|
if is_prohibited_bidi_ch(c):
|
|
raise ValueError('%s: prohibited bidi characters (%s).' % (RFC, bidi_table))
|
|
|
|
# RFC4013, 2.5. Unassigned Code Points:
|
|
if in_table_a1(c):
|
|
raise ValueError('%s: prohibited unassigned code points (A.1).' % RFC)
|
|
|
|
|
|
def saslprep(string):
|
|
"""RFC4013 implementation.
|
|
Implements "SASLprep" profile (RFC4013) of the "stringprep" algorithm (RFC3454)
|
|
to prepare Unicode strings representing user names and passwords for comparison.
|
|
Regarding the RFC4013, the "SASLprep" profile is intended to be used by
|
|
Simple Authentication and Security Layer (SASL) mechanisms
|
|
(such as PLAIN, CRAM-MD5, and DIGEST-MD5), as well as other protocols
|
|
exchanging simple user names and/or passwords.
|
|
|
|
Args:
|
|
string (unicode string): Unicode string to validate and prepare.
|
|
|
|
Returns:
|
|
Prepared unicode string.
|
|
"""
|
|
# RFC4013: "The algorithm assumes all strings are
|
|
# comprised of characters from the Unicode [Unicode] character set."
|
|
# Validate the string is a Unicode string
|
|
# (text_type is the string type if PY3 and unicode otherwise):
|
|
if not is_unicode_str(string):
|
|
raise TypeError('input must be of type %s, not %s' % (text_type, type(string)))
|
|
|
|
# RFC4013: 2.1. Mapping.
|
|
string = mapping_profile(string)
|
|
|
|
# RFC4013: 2.2. Normalization.
|
|
# "This profile specifies using Unicode normalization form KC."
|
|
string = normalize('NFKC', string)
|
|
if not string:
|
|
return u''
|
|
|
|
# RFC4013: 2.3. Prohibited Output.
|
|
# RFC4013: 2.4. Bidirectional Characters.
|
|
# RFC4013: 2.5. Unassigned Code Points.
|
|
prohibited_output_profile(string)
|
|
|
|
return string
|