|
|
|
@ -147,6 +147,11 @@ options:
|
|
|
|
|
type: str
|
|
|
|
|
aliases: [ ssl_rootcert ]
|
|
|
|
|
version_added: '2.3'
|
|
|
|
|
groups:
|
|
|
|
|
description:
|
|
|
|
|
- The list of groups (roles) that need to be granted to the user.
|
|
|
|
|
type: list
|
|
|
|
|
version_added: '2.9'
|
|
|
|
|
notes:
|
|
|
|
|
- The module creates a user (role) with login privilege by default.
|
|
|
|
|
Use NOLOGIN role_attr_flags to change this behaviour.
|
|
|
|
@ -205,6 +210,13 @@ EXAMPLES = r'''
|
|
|
|
|
db: test
|
|
|
|
|
user: test
|
|
|
|
|
password: ""
|
|
|
|
|
|
|
|
|
|
- name: Create user test and grant group user_ro and user_rw to it
|
|
|
|
|
postgresql_user:
|
|
|
|
|
name: test
|
|
|
|
|
groups:
|
|
|
|
|
- user_ro
|
|
|
|
|
- user_rw
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
RETURN = r'''
|
|
|
|
@ -233,6 +245,7 @@ from ansible.module_utils.basic import AnsibleModule
|
|
|
|
|
from ansible.module_utils.database import pg_quote_identifier, SQLParseError
|
|
|
|
|
from ansible.module_utils.postgres import (
|
|
|
|
|
connect_to_db,
|
|
|
|
|
exec_sql,
|
|
|
|
|
get_conn_params,
|
|
|
|
|
postgres_common_argument_spec,
|
|
|
|
|
)
|
|
|
|
@ -750,6 +763,91 @@ def get_valid_flags_by_version(cursor):
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PgMembership():
|
|
|
|
|
def __init__(self, module, cursor, target_roles, groups, fail_on_role=True):
|
|
|
|
|
self.module = module
|
|
|
|
|
self.cursor = cursor
|
|
|
|
|
self.target_roles = [r.strip() for r in target_roles]
|
|
|
|
|
self.groups = groups
|
|
|
|
|
self.granted = {}
|
|
|
|
|
self.fail_on_role = fail_on_role
|
|
|
|
|
self.non_existent_roles = []
|
|
|
|
|
self.changed = False
|
|
|
|
|
self.__check_roles_exist()
|
|
|
|
|
|
|
|
|
|
def grant(self):
|
|
|
|
|
for group in self.groups:
|
|
|
|
|
self.granted[group] = []
|
|
|
|
|
|
|
|
|
|
for role in self.target_roles:
|
|
|
|
|
# If role is in a group now, pass:
|
|
|
|
|
if self.__check_membership(group, role):
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
query = "GRANT %s TO %s" % ((pg_quote_identifier(group, 'role'),
|
|
|
|
|
(pg_quote_identifier(role, 'role'))))
|
|
|
|
|
self.changed = exec_sql(self, query, ddl=True, add_to_executed=False)
|
|
|
|
|
executed_queries.append(query)
|
|
|
|
|
|
|
|
|
|
if self.changed:
|
|
|
|
|
self.granted[group].append(role)
|
|
|
|
|
|
|
|
|
|
return self.changed
|
|
|
|
|
|
|
|
|
|
def __check_membership(self, src_role, dst_role):
|
|
|
|
|
query = ("SELECT ARRAY(SELECT b.rolname FROM "
|
|
|
|
|
"pg_catalog.pg_auth_members m "
|
|
|
|
|
"JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid) "
|
|
|
|
|
"WHERE m.member = r.oid) "
|
|
|
|
|
"FROM pg_catalog.pg_roles r "
|
|
|
|
|
"WHERE r.rolname = '%s'" % dst_role)
|
|
|
|
|
|
|
|
|
|
res = exec_sql(self, query, add_to_executed=False)
|
|
|
|
|
membership = []
|
|
|
|
|
if res:
|
|
|
|
|
membership = res[0][0]
|
|
|
|
|
|
|
|
|
|
if not membership:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
if src_role in membership:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def __check_roles_exist(self):
|
|
|
|
|
for group in self.groups:
|
|
|
|
|
if not self.__role_exists(group):
|
|
|
|
|
if self.fail_on_role:
|
|
|
|
|
self.module.fail_json(msg="Role %s does not exist" % group)
|
|
|
|
|
else:
|
|
|
|
|
self.module.warn("Role %s does not exist, pass" % group)
|
|
|
|
|
self.non_existent_roles.append(group)
|
|
|
|
|
|
|
|
|
|
for role in self.target_roles:
|
|
|
|
|
if not self.__role_exists(role):
|
|
|
|
|
if self.fail_on_role:
|
|
|
|
|
self.module.fail_json(msg="Role %s does not exist" % role)
|
|
|
|
|
else:
|
|
|
|
|
self.module.warn("Role %s does not exist, pass" % role)
|
|
|
|
|
|
|
|
|
|
if role not in self.groups:
|
|
|
|
|
self.non_existent_roles.append(role)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
if self.fail_on_role:
|
|
|
|
|
self.module.exit_json(msg="Role role '%s' is a member of role '%s'" % (role, role))
|
|
|
|
|
else:
|
|
|
|
|
self.module.warn("Role role '%s' is a member of role '%s', pass" % (role, role))
|
|
|
|
|
|
|
|
|
|
# Update role lists, excluding non existent roles:
|
|
|
|
|
self.groups = [g for g in self.groups if g not in self.non_existent_roles]
|
|
|
|
|
|
|
|
|
|
self.target_roles = [r for r in self.target_roles if r not in self.non_existent_roles]
|
|
|
|
|
|
|
|
|
|
def __role_exists(self, role):
|
|
|
|
|
return exec_sql(self, "SELECT 1 FROM pg_roles WHERE rolname = '%s'" % role, add_to_executed=False)
|
|
|
|
|
|
|
|
|
|
# ===========================================
|
|
|
|
|
# Module execution.
|
|
|
|
|
#
|
|
|
|
@ -770,6 +868,7 @@ def main():
|
|
|
|
|
expires=dict(type='str', default=None),
|
|
|
|
|
conn_limit=dict(type='int', default=None),
|
|
|
|
|
session_role=dict(type='str'),
|
|
|
|
|
groups=dict(type='list'),
|
|
|
|
|
)
|
|
|
|
|
module = AnsibleModule(
|
|
|
|
|
argument_spec=argument_spec,
|
|
|
|
@ -791,6 +890,9 @@ def main():
|
|
|
|
|
expires = module.params["expires"]
|
|
|
|
|
conn_limit = module.params["conn_limit"]
|
|
|
|
|
role_attr_flags = module.params["role_attr_flags"]
|
|
|
|
|
groups = module.params["groups"]
|
|
|
|
|
if groups:
|
|
|
|
|
groups = [e.strip() for e in groups]
|
|
|
|
|
|
|
|
|
|
conn_params = get_conn_params(module, module.params, warn_db_default=False)
|
|
|
|
|
db_connection = connect_to_db(module, conn_params)
|
|
|
|
@ -826,6 +928,13 @@ def main():
|
|
|
|
|
changed = grant_privileges(cursor, user, privs) or changed
|
|
|
|
|
except SQLParseError as e:
|
|
|
|
|
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
|
|
|
|
|
|
|
|
|
|
if groups:
|
|
|
|
|
target_roles = []
|
|
|
|
|
target_roles.append(user)
|
|
|
|
|
pg_membership = PgMembership(module, cursor, target_roles, groups)
|
|
|
|
|
changed = pg_membership.grant()
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
if user_exists(cursor, user):
|
|
|
|
|
if module.check_mode:
|
|
|
|
|