mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-26 21:59:38 -07:00 
			
		
		
		
	* fixed validation-modules for apache2_mod_proxy.py * fixed validation-modules for apache2_module.py * fixed validation-modules for deploy_helper.py The ignore lines were put back in place because add_file_common_args=True is used and the module inherits a number of options that do not show up in the documentation (nor should they). * fixed validation-modules for ejabberd_user.py * fixed validation-modules for gunicorn.py * fixed validation-modules for htpasswd.py * fixed validation-modules for jenkins_job.py * fixed validation-modules for jenkins_job_info.py * fixed validation-modules for jenkins_plugin.py * fixed validation-modules for jenkins_script.py * fixed validation-modules for jira.py * fixed validation-modules for nginx_status_facts.py * fixed validation-modules for rundeck_acl_policy.py * fixed validation-modules for rundeck_project.py * fixed validation-modules for supervisorctl.py * fixed validation-modules for taiga_issue.py * fixed pylint mistake in plugins/modules/web_infrastructure/jenkins_job_info.py * removed ignore lines for almost-all web_infrastructure modules * rolled back ignore lines for nested sophos_utm modules that were not adjusted * Removed doc-missing-type from ignore-2.11.txt for deploy_helper and jenkins_plugin * When adding lines back to ignore files, we added more than it was before. Removing. * Rolled back deprecation line in ignore-2.9.txt for nginx_status_facts * Rolled back yet another line in ignore-2.9.txt for nginx_status_facts * Fixed argument_spec and docs for crypt_scheme parameter in htpasswd, per PR * Added extends_documentation_fragment:files to deploy_helper and jenkins_plugin * Removed long-deprecated option params from jenkins_plugin, removed validate-modules lines from ignore files for that module * Update plugins/modules/web_infrastructure/htpasswd.py Co-authored-by: Felix Fontein <felix@fontein.de> Co-authored-by: Felix Fontein <felix@fontein.de>
		
			
				
	
	
		
			274 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
	
		
			8.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| # (c) 2013, Nimbis Services, Inc.
 | |
| # 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 = '''
 | |
| module: htpasswd
 | |
| short_description: manage user files for basic authentication
 | |
| description:
 | |
|   - Add and remove username/password entries in a password file using htpasswd.
 | |
|   - This is used by web servers such as Apache and Nginx for basic authentication.
 | |
| options:
 | |
|   path:
 | |
|     type: path
 | |
|     required: true
 | |
|     aliases: [ dest, destfile ]
 | |
|     description:
 | |
|       - Path to the file that contains the usernames and passwords
 | |
|   name:
 | |
|     type: str
 | |
|     required: true
 | |
|     aliases: [ username ]
 | |
|     description:
 | |
|       - User name to add or remove
 | |
|   password:
 | |
|     type: str
 | |
|     required: false
 | |
|     description:
 | |
|       - Password associated with user.
 | |
|       - Must be specified if user does not exist yet.
 | |
|   crypt_scheme:
 | |
|     type: str
 | |
|     required: false
 | |
|     default: "apr_md5_crypt"
 | |
|     description:
 | |
|       - Encryption scheme to be used.  As well as the four choices listed
 | |
|         here, you can also use any other hash supported by passlib, such as
 | |
|         md5_crypt and sha256_crypt, which are linux passwd hashes.  If you
 | |
|         do so the password file will not be compatible with Apache or Nginx
 | |
|       - 'Some of the available choices might be: C(apr_md5_crypt), C(des_crypt), C(ldap_sha1), C(plaintext)'
 | |
|   state:
 | |
|     type: str
 | |
|     required: false
 | |
|     choices: [ present, absent ]
 | |
|     default: "present"
 | |
|     description:
 | |
|       - Whether the user entry should be present or not
 | |
|   create:
 | |
|     required: false
 | |
|     type: bool
 | |
|     default: "yes"
 | |
|     description:
 | |
|       - Used with C(state=present). If specified, the file will be created
 | |
|         if it does not already exist. If set to "no", will fail if the
 | |
|         file does not exist
 | |
| notes:
 | |
|   - "This module depends on the I(passlib) Python library, which needs to be installed on all target systems."
 | |
|   - "On Debian, Ubuntu, or Fedora: install I(python-passlib)."
 | |
|   - "On RHEL or CentOS: Enable EPEL, then install I(python-passlib)."
 | |
| requirements: [ passlib>=1.6 ]
 | |
| author: "Ansible Core Team"
 | |
| extends_documentation_fragment: files
 | |
| '''
 | |
| 
 | |
| EXAMPLES = """
 | |
| - name: Add a user to a password file and ensure permissions are set
 | |
|   community.general.htpasswd:
 | |
|     path: /etc/nginx/passwdfile
 | |
|     name: janedoe
 | |
|     password: '9s36?;fyNp'
 | |
|     owner: root
 | |
|     group: www-data
 | |
|     mode: 0640
 | |
| 
 | |
| - name: Remove a user from a password file
 | |
|   community.general.htpasswd:
 | |
|     path: /etc/apache2/passwdfile
 | |
|     name: foobar
 | |
|     state: absent
 | |
| 
 | |
| - name: Add a user to a password file suitable for use by libpam-pwdfile
 | |
|   community.general.htpasswd:
 | |
|     path: /etc/mail/passwords
 | |
|     name: alex
 | |
|     password: oedu2eGh
 | |
|     crypt_scheme: md5_crypt
 | |
| """
 | |
| 
 | |
| 
 | |
| import os
 | |
| import tempfile
 | |
| import traceback
 | |
| from distutils.version import LooseVersion
 | |
| from ansible.module_utils.basic import AnsibleModule, missing_required_lib
 | |
| from ansible.module_utils._text import to_native
 | |
| 
 | |
| PASSLIB_IMP_ERR = None
 | |
| try:
 | |
|     from passlib.apache import HtpasswdFile, htpasswd_context
 | |
|     from passlib.context import CryptContext
 | |
|     import passlib
 | |
| except ImportError:
 | |
|     PASSLIB_IMP_ERR = traceback.format_exc()
 | |
|     passlib_installed = False
 | |
| else:
 | |
|     passlib_installed = True
 | |
| 
 | |
| apache_hashes = ["apr_md5_crypt", "des_crypt", "ldap_sha1", "plaintext"]
 | |
| 
 | |
| 
 | |
| def create_missing_directories(dest):
 | |
|     destpath = os.path.dirname(dest)
 | |
|     if not os.path.exists(destpath):
 | |
|         os.makedirs(destpath)
 | |
| 
 | |
| 
 | |
| def present(dest, username, password, crypt_scheme, create, check_mode):
 | |
|     """ Ensures user is present
 | |
| 
 | |
|     Returns (msg, changed) """
 | |
|     if crypt_scheme in apache_hashes:
 | |
|         context = htpasswd_context
 | |
|     else:
 | |
|         context = CryptContext(schemes=[crypt_scheme] + apache_hashes)
 | |
|     if not os.path.exists(dest):
 | |
|         if not create:
 | |
|             raise ValueError('Destination %s does not exist' % dest)
 | |
|         if check_mode:
 | |
|             return ("Create %s" % dest, True)
 | |
|         create_missing_directories(dest)
 | |
|         if LooseVersion(passlib.__version__) >= LooseVersion('1.6'):
 | |
|             ht = HtpasswdFile(dest, new=True, default_scheme=crypt_scheme, context=context)
 | |
|         else:
 | |
|             ht = HtpasswdFile(dest, autoload=False, default=crypt_scheme, context=context)
 | |
|         if getattr(ht, 'set_password', None):
 | |
|             ht.set_password(username, password)
 | |
|         else:
 | |
|             ht.update(username, password)
 | |
|         ht.save()
 | |
|         return ("Created %s and added %s" % (dest, username), True)
 | |
|     else:
 | |
|         if LooseVersion(passlib.__version__) >= LooseVersion('1.6'):
 | |
|             ht = HtpasswdFile(dest, new=False, default_scheme=crypt_scheme, context=context)
 | |
|         else:
 | |
|             ht = HtpasswdFile(dest, default=crypt_scheme, context=context)
 | |
| 
 | |
|         found = None
 | |
|         if getattr(ht, 'check_password', None):
 | |
|             found = ht.check_password(username, password)
 | |
|         else:
 | |
|             found = ht.verify(username, password)
 | |
| 
 | |
|         if found:
 | |
|             return ("%s already present" % username, False)
 | |
|         else:
 | |
|             if not check_mode:
 | |
|                 if getattr(ht, 'set_password', None):
 | |
|                     ht.set_password(username, password)
 | |
|                 else:
 | |
|                     ht.update(username, password)
 | |
|                 ht.save()
 | |
|             return ("Add/update %s" % username, True)
 | |
| 
 | |
| 
 | |
| def absent(dest, username, check_mode):
 | |
|     """ Ensures user is absent
 | |
| 
 | |
|     Returns (msg, changed) """
 | |
|     if LooseVersion(passlib.__version__) >= LooseVersion('1.6'):
 | |
|         ht = HtpasswdFile(dest, new=False)
 | |
|     else:
 | |
|         ht = HtpasswdFile(dest)
 | |
| 
 | |
|     if username not in ht.users():
 | |
|         return ("%s not present" % username, False)
 | |
|     else:
 | |
|         if not check_mode:
 | |
|             ht.delete(username)
 | |
|             ht.save()
 | |
|         return ("Remove %s" % username, True)
 | |
| 
 | |
| 
 | |
| def check_file_attrs(module, changed, message):
 | |
| 
 | |
|     file_args = module.load_file_common_arguments(module.params)
 | |
|     if module.set_fs_attributes_if_different(file_args, False):
 | |
| 
 | |
|         if changed:
 | |
|             message += " and "
 | |
|         changed = True
 | |
|         message += "ownership, perms or SE linux context changed"
 | |
| 
 | |
|     return message, changed
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     arg_spec = dict(
 | |
|         path=dict(type='path', required=True, aliases=["dest", "destfile"]),
 | |
|         name=dict(type='str', required=True, aliases=["username"]),
 | |
|         password=dict(type='str', required=False, default=None, no_log=True),
 | |
|         crypt_scheme=dict(type='str', required=False, default="apr_md5_crypt"),
 | |
|         state=dict(type='str', required=False, default="present", choices=["present", "absent"]),
 | |
|         create=dict(type='bool', default=True),
 | |
| 
 | |
|     )
 | |
|     module = AnsibleModule(argument_spec=arg_spec,
 | |
|                            add_file_common_args=True,
 | |
|                            supports_check_mode=True)
 | |
| 
 | |
|     path = module.params['path']
 | |
|     username = module.params['name']
 | |
|     password = module.params['password']
 | |
|     crypt_scheme = module.params['crypt_scheme']
 | |
|     state = module.params['state']
 | |
|     create = module.params['create']
 | |
|     check_mode = module.check_mode
 | |
| 
 | |
|     if not passlib_installed:
 | |
|         module.fail_json(msg=missing_required_lib("passlib"), exception=PASSLIB_IMP_ERR)
 | |
| 
 | |
|     # Check file for blank lines in effort to avoid "need more than 1 value to unpack" error.
 | |
|     try:
 | |
|         f = open(path, "r")
 | |
|     except IOError:
 | |
|         # No preexisting file to remove blank lines from
 | |
|         f = None
 | |
|     else:
 | |
|         try:
 | |
|             lines = f.readlines()
 | |
|         finally:
 | |
|             f.close()
 | |
| 
 | |
|         # If the file gets edited, it returns true, so only edit the file if it has blank lines
 | |
|         strip = False
 | |
|         for line in lines:
 | |
|             if not line.strip():
 | |
|                 strip = True
 | |
|                 break
 | |
| 
 | |
|         if strip:
 | |
|             # If check mode, create a temporary file
 | |
|             if check_mode:
 | |
|                 temp = tempfile.NamedTemporaryFile()
 | |
|                 path = temp.name
 | |
|             f = open(path, "w")
 | |
|             try:
 | |
|                 [f.write(line) for line in lines if line.strip()]
 | |
|             finally:
 | |
|                 f.close()
 | |
| 
 | |
|     try:
 | |
|         if state == 'present':
 | |
|             (msg, changed) = present(path, username, password, crypt_scheme, create, check_mode)
 | |
|         elif state == 'absent':
 | |
|             if not os.path.exists(path):
 | |
|                 module.exit_json(msg="%s not present" % username,
 | |
|                                  warnings="%s does not exist" % path, changed=False)
 | |
|             (msg, changed) = absent(path, username, check_mode)
 | |
|         else:
 | |
|             module.fail_json(msg="Invalid state: %s" % state)
 | |
| 
 | |
|         check_file_attrs(module, changed, msg)
 | |
|         module.exit_json(msg=msg, changed=changed)
 | |
|     except Exception as e:
 | |
|         module.fail_json(msg=to_native(e))
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |