File: //lib/python3.9/site-packages/ansible_collections/community/sap/plugins/modules/sap_user.py
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Rainer Leber <[email protected]> <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: sap_user
short_description: This module will manage a user entities in a SAP S4/HANA environment
version_added: "1.0.0"
description:
- The M(community.sap.sap_user) module depends on C(pyrfc) Python library (version 2.4.0 and upwards).
Depending on distribution you are using, you may need to install additional packages to
have these available.
- This module will use the following user BAPIs to manage user entities.
- C(BAPI_USER_GET_DETAIL)
- C(BAPI_USER_DELETE)
- C(BAPI_USER_CREATE1)
- C(BAPI_USER_CHANGE)
- C(BAPI_USER_ACTGROUPS_ASSIGN)
- C(BAPI_USER_PROFILES_ASSIGN)
- C(BAPI_USER_UNLOCK)
- C(BAPI_USER_LOCK)
options:
state:
description:
- The decision what to do with the user.
default: 'present'
choices:
- 'present'
- 'absent'
- 'lock'
- 'unlock'
required: false
type: str
force:
description:
- Must be C('True') if the password or type should be overwritten.
default: False
required: false
type: bool
conn_username:
description: The required username for the SAP system.
required: true
type: str
conn_password:
description: The required password for the SAP system.
required: true
type: str
host:
description: The required host for the SAP system. Can be either an FQDN or IP Address.
required: true
type: str
sysnr:
description:
- The system number of the SAP system.
- You must quote the value to ensure retaining the leading zeros.
default: '00'
type: str
client:
description:
- The client number to connect to.
- You must quote the value to ensure retaining the leading zeros.
default: '000'
type: str
username:
description:
- The username.
type: str
required: true
firstname:
description:
- The Firstname of the user in the SAP system.
type: str
required: false
lastname:
description:
- The lastname of the user in the SAP system.
type: str
required: false
email:
description:
- The email address of the user in the SAP system.
type: str
required: false
password:
description:
- The password for the user in the SAP system.
type: str
required: false
useralias:
description:
- The alias for the user in the SAP system.
type: str
required: false
user_type:
description:
- The type for the user in the SAP system.
- C('A') Dialog user, C('B') System User, C('C') Communication User,
C('S') Service User, C('L') Reference User.
- Must be in uppercase.
type: str
required: false
default: 'A'
choices: ['A', 'B', 'C', 'S', 'L']
company:
description:
- The specific company the user belongs to.
- The company name must be available in the SAP system.
type: str
required: false
profiles:
description:
- Assign profiles to the user.
- Should be in uppercase, for example C('SAP_NEW') or C('SAP_ALL').
type: list
elements: str
default: ['']
required: false
roles:
description:
- Assign roles to the user.
type: list
elements: str
default: ['']
required: false
requirements:
- pyrfc >= 2.4.0
author:
- Rainer Leber (@rainerleber)
notes:
- Does not support C(check_mode).
'''
EXAMPLES = r'''
- name: Create SAP User
community.sap.sap_user:
conn_username: 'DDIC'
conn_password: 'Test123'
host: 192.168.1.150
sysnr: '01'
client: '000'
state: present
username: ADMIN
firstname: first_admin
lastname: last_admin
email: [email protected]
password: Test123456
useralias: ADMIN
company: DEFAULT_COMPANY
roles:
- "SAP_ALL"
- name: Force change SAP User
community.sap.sap_user:
conn_username: 'DDIC'
conn_password: 'Test123'
host: 192.168.1.150
sysnr: '01'
client: '000'
state: present
force: true
username: ADMIN
firstname: first_admin
lastname: last_admin
email: [email protected]
password: Test123456
useralias: ADMIN
company: DEFAULT_COMPANY
roles:
- "SAP_ALL"
- name: Delete SAP User
community.sap.sap_user:
conn_username: 'DDIC'
conn_password: 'Test123'
host: 192.168.1.150
sysnr: '01'
client: '000'
state: absent
force: true
username: ADMIN
- name: Unlock SAP User
community.sap.sap_user:
conn_username: 'DDIC'
conn_password: 'Test123'
host: 192.168.1.150
sysnr: '01'
client: '000'
state: unlock
force: true
username: ADMIN
'''
RETURN = r'''
msg:
description: A small execution description about the user action.
type: str
returned: always
sample: 'User ADMIN created'
out:
description: A detailed description about the user action.
type: list
elements: dict
returned: on success
sample: [...,{
"RETURN": [
{
"FIELD": "BNAME",
"ID": "01",
"LOG_MSG_NO": "000000",
"LOG_NO": "",
"MESSAGE": "User ADMIN created",
"MESSAGE_V1": "ADMIN",
"MESSAGE_V2": "",
"MESSAGE_V3": "",
"MESSAGE_V4": "",
"NUMBER": "102",
"PARAMETER": "",
"ROW": 0,
"SYSTEM": "",
"TYPE": "S"
}
],
"SAPUSER_UUID_HIST": []}]
'''
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
import traceback
import datetime
try:
from pyrfc import Connection
except ImportError:
HAS_PYRFC_LIBRARY = False
PYRFC_LIBRARY_IMPORT_ERROR = traceback.format_exc()
else:
HAS_PYRFC_LIBRARY = True
def add_to_dict(target_dict, target_key, value):
# Adds the given value to a dict as the key
# check if the given key is in the given dict yet
if target_key in target_dict:
return False
target_dict[target_key] = value
return True
def call_rfc_method(connection, method_name, kwargs):
# PyRFC call function
return connection.call(method_name, **kwargs)
def build_rfc_user_params(username, firstname, lastname, email, raw_password,
useralias, user_type, raw_company, user_change, force):
"""Creates RFC parameters for Creating users"""
# define dicts in batch
params = dict()
address = dict()
password = dict()
alias = dict()
logondata = dict()
company = dict()
# for change parameters
addressx = dict()
passwordx = dict()
logondatax = dict()
companyx = dict()
# define username
add_to_dict(params, 'USERNAME', username)
# define Address
add_to_dict(address, 'FIRSTNAME', firstname)
add_to_dict(address, 'LASTNAME', lastname)
add_to_dict(address, 'E_MAIL', email)
# define Password
add_to_dict(password, 'BAPIPWD', raw_password)
# define Alias
add_to_dict(alias, 'USERALIAS', useralias)
# define LogonData
add_to_dict(logondata, 'GLTGV', datetime.date.today())
add_to_dict(logondata, 'GLTGB', '20991231')
add_to_dict(logondata, 'USTYP', user_type)
# define company
add_to_dict(company, 'COMPANY', raw_company)
params['LOGONDATA'] = logondata
params['ADDRESS'] = address
params['COMPANY'] = company
params['ALIAS'] = alias
params['PASSWORD'] = password
# add change if user exists
if user_change and force:
add_to_dict(addressx, 'FIRSTNAME', 'X')
add_to_dict(addressx, 'LASTNAME', 'X')
add_to_dict(addressx, 'E_MAIL', 'X')
# define Password
add_to_dict(passwordx, 'BAPIPWD', 'X')
# define LogonData
add_to_dict(logondatax, 'USTYP', 'X')
# define company
add_to_dict(companyx, 'COMPANY', 'X')
params['LOGONDATAX'] = logondatax
params['ADDRESSX'] = addressx
params['COMPANYX'] = companyx
params['PASSWORDX'] = passwordx
return params
def user_role_assignment_build_rfc_params(roles, username):
rfc_table = []
for role_name in roles:
table_row = {'AGR_NAME': role_name}
add_to_dict(table_row, 'FROM_DAT', datetime.date.today())
add_to_dict(table_row, 'TO_DAT', '20991231')
rfc_table.append(table_row)
return {
'USERNAME': username,
'ACTIVITYGROUPS': rfc_table
}
def user_profile_assignment_build_rfc_params(profiles, username):
rfc_table = []
for profile_name in profiles:
table_row = {'BAPIPROF': profile_name}
rfc_table.append(table_row)
return {
'USERNAME': username,
'PROFILES': rfc_table
}
def check_user(user_detail):
if len(user_detail['RETURN']) > 0:
for sub in user_detail['RETURN']:
if sub['NUMBER'] == '124':
return False
return True
def return_analysis(raw):
change = False
failed = False
for state in raw['RETURN']:
if state['TYPE'] == "E":
if state['NUMBER'] == '224' or state['NUMBER'] == '124':
change = False
else:
failed = True
if state['TYPE'] == "S":
if state['NUMBER'] != '029':
change = True
if state['TYPE'] == "W":
if state['NUMBER'] == '049' or state['NUMBER'] == '047':
change = True
if state['NUMBER'] == '255':
change = True
return [{"change": change}, {"failed": failed}]
def run_module():
module = AnsibleModule(
argument_spec=dict(
# logical values
state=dict(default='present', choices=[
'absent', 'present', 'lock', 'unlock']),
force=dict(type='bool', default=False),
# values for connection
conn_username=dict(type='str', required=True),
conn_password=dict(type='str', required=True, no_log=True),
host=dict(type='str', required=True),
sysnr=dict(type='str', default="00"),
client=dict(type='str', default="000"),
# values for the new or existing user
username=dict(type='str', required=True),
firstname=dict(type='str', required=False),
lastname=dict(type='str', required=False),
email=dict(type='str', required=False),
password=dict(type='str', required=False, no_log=True),
useralias=dict(type='str', required=False),
user_type=dict(default="A",
choices=['A', 'B', 'C', 'S', 'L']),
company=dict(type='str', required=False),
# values for profile must a list
# Example ["SAP_NEW", "SAP_ALL"]
profiles=dict(type='list', elements='str', default=[""]),
# values for roles must a list
roles=dict(type='list', elements='str', default=[""]),
),
supports_check_mode=False,
required_if=[('state', 'present', ['useralias', 'company'])]
)
result = dict(changed=False, msg='', out='')
count = 0
raw = ""
params = module.params
state = params['state']
conn_username = (params['conn_username']).upper()
conn_password = params['conn_password']
host = params['host']
sysnr = params['sysnr']
client = params['client']
username = (params['username']).upper()
firstname = params['firstname']
lastname = params['lastname']
email = params['email']
password = params['password']
force = params['force']
if not params['useralias'] is None:
useralias = (params['useralias']).upper()
user_type = (params['user_type']).upper()
company = params['company']
profiles = params['profiles']
roles = params['roles']
if not HAS_PYRFC_LIBRARY:
module.fail_json(
msg=missing_required_lib('pyrfc'),
exception=PYRFC_LIBRARY_IMPORT_ERROR)
# basic RFC connection with pyrfc
try:
conn = Connection(user=conn_username, passwd=conn_password, ashost=host, sysnr=sysnr, client=client)
except Exception as err:
result['error'] = str(err)
result['msg'] = 'Something went wrong connecting to the SAP system.'
module.fail_json(**result)
# user details
user_detail = call_rfc_method(conn, 'BAPI_USER_GET_DETAIL', {'USERNAME': username})
user_exists = check_user(user_detail)
if state == "absent":
if user_exists:
raw = call_rfc_method(conn, 'BAPI_USER_DELETE', {'USERNAME': username})
if state == "present":
user_params = build_rfc_user_params(username, firstname, lastname, email, password, useralias, user_type, company, user_exists, force)
if not user_exists:
raw = call_rfc_method(conn, 'BAPI_USER_CREATE1', user_params)
if user_exists:
# check for address changes when user exists
user_no_changes = all((user_detail.get('ADDRESS')).get(k) == v for k, v in (user_params.get('ADDRESS')).items())
if not user_no_changes or force:
raw = call_rfc_method(conn, 'BAPI_USER_CHANGE', user_params)
call_rfc_method(conn, 'BAPI_USER_ACTGROUPS_ASSIGN', user_role_assignment_build_rfc_params(roles, username))
call_rfc_method(conn, 'BAPI_USER_PROFILES_ASSIGN', user_profile_assignment_build_rfc_params(profiles, username))
if state == "unlock":
if user_exists:
raw = call_rfc_method(conn, 'BAPI_USER_UNLOCK', {'USERNAME': username})
if state == "lock":
if user_exists:
raw = call_rfc_method(conn, 'BAPI_USER_LOCK', {'USERNAME': username})
# analyse return value
if raw != '':
analysed = return_analysis(raw)
result['out'] = raw
result['changed'] = analysed[0]['change']
for msgs in raw['RETURN']:
if count > 0:
result['msg'] = result['msg'] + '\n'
result['msg'] = result['msg'] + msgs['MESSAGE']
count = count + 1
if analysed[1]['failed']:
module.fail_json(**result)
else:
result['msg'] = "No changes where made."
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()