mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
postgresql_user: add scram-sha-256 password support (#100)
* postgresql_user: add support for scram-sha-256 passwords * postgresql_user: add support for scram-sha-256 passwords * add changelog fragment * fix
This commit is contained in:
parent
dee5de23d7
commit
bb459cb014
5 changed files with 391 additions and 5 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- postgresql_user - add scram-sha-256 support (https://github.com/ansible/ansible/issues/49878).
|
176
plugins/module_utils/saslprep.py
Normal file
176
plugins/module_utils/saslprep.py
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
# -*- 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>
|
||||||
|
|
||||||
|
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
|
|
@ -48,7 +48,7 @@ options:
|
||||||
- Unhashed password will automatically be hashed when saved into the
|
- Unhashed password will automatically be hashed when saved into the
|
||||||
database if C(encrypted) parameter is set, otherwise it will be save in
|
database if C(encrypted) parameter is set, otherwise it will be save in
|
||||||
plain text format.
|
plain text format.
|
||||||
- When passing a hashed password it must be generated with the format
|
- When passing an MD5-hashed password it must be generated with the format
|
||||||
C('str["md5"] + md5[ password + username ]'), resulting in a total of
|
C('str["md5"] + md5[ password + username ]'), resulting in a total of
|
||||||
35 characters. An easy way to do this is C(echo "md5$(echo -n
|
35 characters. An easy way to do this is C(echo "md5$(echo -n
|
||||||
'verysecretpasswordJOE' | md5sum | awk '{print $1}')").
|
'verysecretpasswordJOE' | md5sum | awk '{print $1}')").
|
||||||
|
@ -157,6 +157,8 @@ notes:
|
||||||
Use NOLOGIN role_attr_flags to change this behaviour.
|
Use NOLOGIN role_attr_flags to change this behaviour.
|
||||||
- If you specify PUBLIC as the user (role), then the privilege changes will apply to all users (roles).
|
- If you specify PUBLIC as the user (role), then the privilege changes will apply to all users (roles).
|
||||||
You may not specify password or role_attr_flags when the PUBLIC user is specified.
|
You may not specify password or role_attr_flags when the PUBLIC user is specified.
|
||||||
|
- SCRAM-SHA-256-hashed passwords (SASL Authentication) require PostgreSQL version 10 or newer.
|
||||||
|
On the previous versions the whole hashed string will be used as a password.
|
||||||
seealso:
|
seealso:
|
||||||
- module: postgresql_privs
|
- module: postgresql_privs
|
||||||
- module: postgresql_membership
|
- module: postgresql_membership
|
||||||
|
@ -164,6 +166,9 @@ seealso:
|
||||||
- name: PostgreSQL database roles
|
- name: PostgreSQL database roles
|
||||||
description: Complete reference of the PostgreSQL database roles documentation.
|
description: Complete reference of the PostgreSQL database roles documentation.
|
||||||
link: https://www.postgresql.org/docs/current/user-manag.html
|
link: https://www.postgresql.org/docs/current/user-manag.html
|
||||||
|
- name: PostgreSQL SASL Authentication
|
||||||
|
description: Complete reference of the PostgreSQL SASL Authentication.
|
||||||
|
link: https://www.postgresql.org/docs/current/sasl-authentication.html
|
||||||
author:
|
author:
|
||||||
- Ansible Core Team
|
- Ansible Core Team
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
|
@ -232,6 +237,15 @@ EXAMPLES = r'''
|
||||||
groups:
|
groups:
|
||||||
- user_ro
|
- user_ro
|
||||||
- user_rw
|
- user_rw
|
||||||
|
|
||||||
|
# Create user with a cleartext password if it does not exist or update its password.
|
||||||
|
# The password will be encrypted with SCRAM algorithm (available since PostgreSQL 10)
|
||||||
|
- name: Create appclient user with SCRAM-hashed password
|
||||||
|
postgresql_user:
|
||||||
|
name: appclient
|
||||||
|
password: "secret123"
|
||||||
|
environment:
|
||||||
|
PGOPTIONS: "-c password_encryption=scram-sha-256"
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = r'''
|
RETURN = r'''
|
||||||
|
@ -245,7 +259,9 @@ queries:
|
||||||
import itertools
|
import itertools
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
from hashlib import md5
|
from hashlib import md5, sha256
|
||||||
|
import hmac
|
||||||
|
from base64 import b64decode
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
@ -267,13 +283,24 @@ from ansible_collections.community.general.plugins.module_utils.postgres import
|
||||||
PgMembership,
|
PgMembership,
|
||||||
postgres_common_argument_spec,
|
postgres_common_argument_spec,
|
||||||
)
|
)
|
||||||
from ansible.module_utils._text import to_bytes, to_native
|
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||||
from ansible.module_utils.six import iteritems
|
from ansible.module_utils.six import iteritems
|
||||||
|
import ansible_collections.community.general.plugins.module_utils.saslprep as saslprep
|
||||||
|
|
||||||
|
try:
|
||||||
|
# pbkdf2_hmac is missing on python 2.6, we can safely assume,
|
||||||
|
# that postresql 10 capable instance have at least python 2.7 installed
|
||||||
|
from hashlib import pbkdf2_hmac
|
||||||
|
pbkdf2_found = True
|
||||||
|
except ImportError:
|
||||||
|
pbkdf2_found = False
|
||||||
|
|
||||||
|
|
||||||
FLAGS = ('SUPERUSER', 'CREATEROLE', 'CREATEDB', 'INHERIT', 'LOGIN', 'REPLICATION')
|
FLAGS = ('SUPERUSER', 'CREATEROLE', 'CREATEDB', 'INHERIT', 'LOGIN', 'REPLICATION')
|
||||||
FLAGS_BY_VERSION = {'BYPASSRLS': 90500}
|
FLAGS_BY_VERSION = {'BYPASSRLS': 90500}
|
||||||
|
|
||||||
|
SCRAM_SHA256_REGEX = r'^SCRAM-SHA-256\$(\d+):([A-Za-z0-9+\/=]+)\$([A-Za-z0-9+\/=]+):([A-Za-z0-9+\/=]+)$'
|
||||||
|
|
||||||
VALID_PRIVS = dict(table=frozenset(('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER', 'ALL')),
|
VALID_PRIVS = dict(table=frozenset(('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER', 'ALL')),
|
||||||
database=frozenset(
|
database=frozenset(
|
||||||
('CREATE', 'CONNECT', 'TEMPORARY', 'TEMP', 'ALL')),
|
('CREATE', 'CONNECT', 'TEMPORARY', 'TEMP', 'ALL')),
|
||||||
|
@ -350,6 +377,39 @@ def user_should_we_change_password(current_role_attrs, user, password, encrypted
|
||||||
if password == '':
|
if password == '':
|
||||||
if current_role_attrs['rolpassword'] is not None:
|
if current_role_attrs['rolpassword'] is not None:
|
||||||
pwchanging = True
|
pwchanging = True
|
||||||
|
|
||||||
|
# SCRAM hashes are represented as a special object, containing hash data:
|
||||||
|
# `SCRAM-SHA-256$<iteration count>:<salt>$<StoredKey>:<ServerKey>`
|
||||||
|
# for reference, see https://www.postgresql.org/docs/current/catalog-pg-authid.html
|
||||||
|
elif current_role_attrs['rolpassword'] is not None \
|
||||||
|
and pbkdf2_found \
|
||||||
|
and re.match(SCRAM_SHA256_REGEX, current_role_attrs['rolpassword']):
|
||||||
|
|
||||||
|
r = re.match(SCRAM_SHA256_REGEX, current_role_attrs['rolpassword'])
|
||||||
|
try:
|
||||||
|
# extract SCRAM params from rolpassword
|
||||||
|
it = int(r.group(1))
|
||||||
|
salt = b64decode(r.group(2))
|
||||||
|
server_key = b64decode(r.group(4))
|
||||||
|
# we'll never need `storedKey` as it is only used for server auth in SCRAM
|
||||||
|
# storedKey = b64decode(r.group(3))
|
||||||
|
|
||||||
|
# from RFC5802 https://tools.ietf.org/html/rfc5802#section-3
|
||||||
|
# SaltedPassword := Hi(Normalize(password), salt, i)
|
||||||
|
# ServerKey := HMAC(SaltedPassword, "Server Key")
|
||||||
|
normalized_password = saslprep.saslprep(to_text(password))
|
||||||
|
salted_password = pbkdf2_hmac('sha256', to_bytes(normalized_password), salt, it)
|
||||||
|
|
||||||
|
server_key_verifier = hmac.new(salted_password, digestmod=sha256)
|
||||||
|
server_key_verifier.update(b'Server Key')
|
||||||
|
|
||||||
|
if server_key_verifier.digest() != server_key:
|
||||||
|
pwchanging = True
|
||||||
|
except Exception:
|
||||||
|
# We assume the password is not scram encrypted
|
||||||
|
# or we cannot check it properly, e.g. due to missing dependencies
|
||||||
|
pwchanging = True
|
||||||
|
|
||||||
# 32: MD5 hashes are represented as a sequence of 32 hexadecimal digits
|
# 32: MD5 hashes are represented as a sequence of 32 hexadecimal digits
|
||||||
# 3: The size of the 'md5' prefix
|
# 3: The size of the 'md5' prefix
|
||||||
# When the provided password looks like a MD5-hash, value of
|
# When the provided password looks like a MD5-hash, value of
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
become_user: "{{ pg_user }}"
|
become_user: "{{ pg_user }}"
|
||||||
become: yes
|
become: yes
|
||||||
register: result
|
register: result
|
||||||
postgresql_parameters: ¶meters
|
postgresql_query_parameters: &query_parameters
|
||||||
db: postgres
|
db: postgres
|
||||||
name: "{{ db_user1 }}"
|
|
||||||
login_user: "{{ pg_user }}"
|
login_user: "{{ pg_user }}"
|
||||||
|
postgresql_parameters: ¶meters
|
||||||
|
<<: *query_parameters
|
||||||
|
name: "{{ db_user1 }}"
|
||||||
|
|
||||||
block:
|
block:
|
||||||
- name: 'Check that PGOPTIONS environment variable is effective (1/2)'
|
- name: 'Check that PGOPTIONS environment variable is effective (1/2)'
|
||||||
|
@ -300,6 +302,97 @@
|
||||||
|
|
||||||
when: encrypted == 'no'
|
when: encrypted == 'no'
|
||||||
|
|
||||||
|
# start of block scram-sha-256
|
||||||
|
# scram-sha-256 password encryption type is supported since PostgreSQL 10
|
||||||
|
- when: postgres_version_resp.stdout is version('10', '>=')
|
||||||
|
block:
|
||||||
|
|
||||||
|
- name: 'Using cleartext password with scram-sha-256: resetting password'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: ""
|
||||||
|
encrypted: "{{ encrypted }}"
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
|
||||||
|
- name: 'Using cleartext password with scram-sha-256: check that password is changed when using cleartext password'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: "{{ db_password1 }}"
|
||||||
|
encrypted: "{{ encrypted }}"
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
# ansible postgresql_user module interface does not (yet) support forcing password_encryption
|
||||||
|
# type value, we'll have to hack it in env variable to force correct encryption
|
||||||
|
PGOPTIONS: "-c password_encryption=scram-sha-256"
|
||||||
|
|
||||||
|
- <<: *changed
|
||||||
|
|
||||||
|
- name: 'Using cleartext password with scram-sha-256: ensure password is properly encrypted'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_query:
|
||||||
|
<<: *query_parameters
|
||||||
|
query: select * from pg_authid where rolname=%s and rolpassword like %s
|
||||||
|
positional_args:
|
||||||
|
- '{{ db_user1 }}'
|
||||||
|
- 'SCRAM-SHA-256$%'
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.rowcount == 1
|
||||||
|
|
||||||
|
- name: 'Using cleartext password with scram-sha-256: check that password is not changed when using the same password'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: "{{ db_password1 }}"
|
||||||
|
encrypted: "{{ encrypted }}"
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
PGOPTIONS: "-c password_encryption=scram-sha-256"
|
||||||
|
|
||||||
|
- <<: *not_changed
|
||||||
|
|
||||||
|
- name: 'Using cleartext password with scram-sha-256: check that password is changed when using another cleartext password'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: "changed{{ db_password1 }}"
|
||||||
|
encrypted: "{{ encrypted }}"
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
PGOPTIONS: "-c password_encryption=scram-sha-256"
|
||||||
|
|
||||||
|
- <<: *changed
|
||||||
|
|
||||||
|
- name: 'Using cleartext password with scram-sha-256: check that password is changed when clearing the password'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: ''
|
||||||
|
encrypted: "{{ encrypted }}"
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
PGOPTIONS: "-c password_encryption=scram-sha-256"
|
||||||
|
|
||||||
|
- <<: *changed
|
||||||
|
|
||||||
|
- name: 'Using cleartext password with scram-sha-256: check that password is not changed when clearing the password again'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: ''
|
||||||
|
encrypted: "{{ encrypted }}"
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
PGOPTIONS: "-c password_encryption=scram-sha-256"
|
||||||
|
|
||||||
|
- <<: *not_changed
|
||||||
|
|
||||||
|
# end of block scram-sha-256
|
||||||
|
|
||||||
- name: Remove user
|
- name: Remove user
|
||||||
<<: *task_parameters
|
<<: *task_parameters
|
||||||
postgresql_user:
|
postgresql_user:
|
||||||
|
|
55
tests/unit/module_utils/test_saslprep.py
Normal file
55
tests/unit/module_utils/test_saslprep.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright: (c) 2019, Andrey Tuzhilin <andrei.tuzhilin@gmail.com>
|
||||||
|
# Copyright: (c) 2020, Andrew Klychkov (@Andersson007) <aaklychkov@mail.ru>
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.saslprep import saslprep
|
||||||
|
|
||||||
|
|
||||||
|
VALID = [
|
||||||
|
(u'', u''),
|
||||||
|
(u'\u00A0', u' '),
|
||||||
|
(u'a', u'a'),
|
||||||
|
(u'й', u'й'),
|
||||||
|
(u'\u30DE\u30C8\u30EA\u30C3\u30AF\u30B9', u'\u30DE\u30C8\u30EA\u30C3\u30AF\u30B9'),
|
||||||
|
(u'The\u00ADM\u00AAtr\u2168', u'TheMatrIX'),
|
||||||
|
(u'I\u00ADX', u'IX'),
|
||||||
|
(u'user', u'user'),
|
||||||
|
(u'USER', u'USER'),
|
||||||
|
(u'\u00AA', u'a'),
|
||||||
|
(u'\u2168', u'IX'),
|
||||||
|
(u'\u05BE\u00A0\u05BE', u'\u05BE\u0020\u05BE'),
|
||||||
|
]
|
||||||
|
|
||||||
|
INVALID = [
|
||||||
|
(None, TypeError),
|
||||||
|
(b'', TypeError),
|
||||||
|
(u'\u0221', ValueError),
|
||||||
|
(u'\u0007', ValueError),
|
||||||
|
(u'\u0627\u0031', ValueError),
|
||||||
|
(u'\uE0001', ValueError),
|
||||||
|
(u'\uE0020', ValueError),
|
||||||
|
(u'\uFFF9', ValueError),
|
||||||
|
(u'\uFDD0', ValueError),
|
||||||
|
(u'\u0000', ValueError),
|
||||||
|
(u'\u06DD', ValueError),
|
||||||
|
(u'\uFFFFD', ValueError),
|
||||||
|
(u'\uD800', ValueError),
|
||||||
|
(u'\u200E', ValueError),
|
||||||
|
(u'\u05BE\u00AA\u05BE', ValueError),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('source,target', VALID)
|
||||||
|
def test_saslprep_conversions(source, target):
|
||||||
|
assert saslprep(source) == target
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('source,exception', INVALID)
|
||||||
|
def test_saslprep_exceptions(source, exception):
|
||||||
|
with pytest.raises(exception) as ex:
|
||||||
|
saslprep(source)
|
Loading…
Reference in a new issue