diff --git a/README-permission.md b/README-permission.md
new file mode 100644
index 0000000000000000000000000000000000000000..5ab2af240ef435de0ac5786707361cad316a1a6c
--- /dev/null
+++ b/README-permission.md
@@ -0,0 +1,163 @@
+Permission module
+============
+
+Description
+-----------
+
+The permission module allows to ensure presence and absence of permissions and permission members.
+
+Features
+--------
+
+* Permission management
+
+
+Supported FreeIPA Versions
+--------------------------
+
+FreeIPA versions 4.4.0 and up are supported by the ipapermission module.
+
+
+Requirements
+------------
+
+**Controller**
+* Ansible version: 2.8+
+
+**Node**
+* Supported FreeIPA version (see above)
+
+
+Usage
+=====
+
+Example inventory file
+
+```ini
+[ipaserver]
+ipaserver.test.local
+```
+
+
+Example playbook to make sure permission "MyPermission" is present:
+
+```yaml
+---
+- name: Playbook to create an IPA permission.
+  hosts: ipaserver
+  become: yes
+
+  tasks:
+  - name: Ensure permission MyPermission is present
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: MyPermission
+      object_type: host
+      right: all
+```
+
+Example playbook to make sure permission "MyPermission" member "privilege" with value "User Administrators" is present:
+
+```yaml
+---
+- name: Permission add privilege to a permission
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Ensure permission MyPermission is present with the User Administrators privilege present
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: MyPermission
+      privilege: "User Administrators"
+      action: member
+```
+
+
+Example playbook to make sure permission "MyPermission" member "privilege" with value "User Administrators" is absent:
+
+
+```yaml
+---
+- name: Permission remove privilege from a permission
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Ensure permission MyPermission is present without the User Administrators privilege
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: MyPermission
+      privilege: "User Administrators"
+      action: member
+      state: absent
+```
+
+
+Example playbook to make sure permission "MyPermission" is absent:
+
+```yaml
+---
+- name: Playbook to manage IPA permission.
+  hosts: ipaserver
+  become: yes
+
+  tasks:
+  - ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: MyPermission
+      state: absent
+```
+
+Example playbook to make sure permission "MyPermission" is renamed to "MyNewPermission":
+
+```yaml
+---
+- name: Playbook to manage IPA permission.
+  hosts: ipaserver
+  become: yes
+
+  tasks:
+  - ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: MyPermission
+      rename: MyNewPermission
+      state: renamed
+```
+
+
+
+
+Variables
+---------
+
+ipapermission
+-------
+
+Variable | Description | Required
+-------- | ----------- | --------
+`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
+`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
+`name` \| `cn` | The permission name string. | yes
+`right` \| `ipapermright` | Rights to grant. It can be a list of one or more of `read`, `search`, `compare`, `write`, `add`, `delete`, and `all` default: `all` | no
+`attrs` | All attributes to which the permission applies | no
+`bindtype` \| `ipapermbindruletype` | Bind rule type. It can be one of `permission`, `all`, `self`, or `anonymous` defaults to `permission` for new permissions.| no
+`subtree` \| `ipapermlocation` | Subtree to apply permissions to | no
+`filter` \| `extratargetfilter` | Extra target filter | no
+`rawfilter` \| `ipapermtargetfilter` | All target filters | no
+`target` \| `ipapermtarget` | Optional DN to apply the permission to | no
+`targetto` \| `ipapermtargetto` | Optional DN subtree where an entry can be moved to | no
+`targetfrom` \| `ipapermtargetfrom` | Optional DN subtree from where an entry can be moved | no
+`memberof` | Target members of a group (sets memberOf targetfilter) | no
+`targetgroup` | User group to apply permissions to (sets target) | no
+`object_type` | Type of IPA object (sets subtree and objectClass targetfilter) | no
+`no_members` | Suppress processing of membership | no
+`rename` | Rename the permission object | no
+`privilege` | Member Privilege of Permission | no
+`action` | Work on permission or member level. It can be on of `member` or `permission` and defaults to `permission`. | no
+`state` | The state to ensure. It can be one of `present`, `absent`, or `renamed` default: `present`. | no
+
+Authors
+=======
+
+Seth Kress
diff --git a/playbooks/permission/permission-absent.yml b/playbooks/permission/permission-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3ab414e3f7bb401f623003564475feaaebb496af
--- /dev/null
+++ b/playbooks/permission/permission-absent.yml
@@ -0,0 +1,11 @@
+---
+- name: Permission absent example
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Ensure permission TestPerm1 is absent
+    ipapermission:
+      name: TestPerm1
+      state: absent
+
diff --git a/playbooks/permission/permission-allow-read-employeenum.yml b/playbooks/permission/permission-allow-read-employeenum.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d30218540f23145e27571b5d3c5e75d9f5dda500
--- /dev/null
+++ b/playbooks/permission/permission-allow-read-employeenum.yml
@@ -0,0 +1,15 @@
+---
+- name: Permission Allow Read Employee Number Example
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Ensure permission TestPerm2 is present with Read rights to employeenumber
+    ipapermission:
+      name: TestPerm2
+      object_type: user
+      perm_rights: 
+        - read
+        - search
+        - compare
+      attrs: employeenumber
diff --git a/playbooks/permission/permission-member-absent.yml b/playbooks/permission/permission-member-absent.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a3f2eedcfd41eec5c12b83a9f6a502d2cee1afbd
--- /dev/null
+++ b/playbooks/permission/permission-member-absent.yml
@@ -0,0 +1,12 @@
+---
+- name: Permission absent example
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Ensure privilege User Administrators privilege is absent on Permission TestPerm1
+    ipapermission:
+      name: TestPerm1
+      privilege: "User Administrators"
+      action: member
+      state: absent
diff --git a/playbooks/permission/permission-member-present.yml b/playbooks/permission/permission-member-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..23ad2783637f3c4e08abbd78ccff135646281b41
--- /dev/null
+++ b/playbooks/permission/permission-member-present.yml
@@ -0,0 +1,11 @@
+---
+- name: Permission member present example
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Ensure permission TestPerm1 is present with the User Administrators privilege present
+    ipapermission:
+      name: TestPerm1
+      privilege: "User Administrators"
+      action: member
diff --git a/playbooks/permission/permission-present.yml b/playbooks/permission/permission-present.yml
new file mode 100644
index 0000000000000000000000000000000000000000..72293ca742d87556fed6fe1e018aa4018236ca42
--- /dev/null
+++ b/playbooks/permission/permission-present.yml
@@ -0,0 +1,11 @@
+---
+- name: Permission present example
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Ensure permission TestPerm1 is present
+    ipapermission:
+      name: TestPerm1
+      object_type: host
+      perm_rights: all
diff --git a/playbooks/permission/permission-renamed.yml b/playbooks/permission/permission-renamed.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f9753d2803436287911a30c0bd8c0731c1b1dd29
--- /dev/null
+++ b/playbooks/permission/permission-renamed.yml
@@ -0,0 +1,11 @@
+---
+- name: Permission present example
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: Ensure permission TestPerm1 is present
+    ipapermission:
+      name: TestPerm1
+      rename: TestPermRenamed
+      state: renamed
diff --git a/plugins/modules/ipapermission.py b/plugins/modules/ipapermission.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f91af518f107a5f0576a2be9223c19e30e42e13
--- /dev/null
+++ b/plugins/modules/ipapermission.py
@@ -0,0 +1,506 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Authors:
+#   Seth Kress <kresss@gmail.com>
+#
+# Copyright (C) 2020 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ANSIBLE_METADATA = {
+    "metadata_version": "1.0",
+    "supported_by": "community",
+    "status": ["preview"],
+}
+
+DOCUMENTATION = """
+---
+module: ipapermission
+short description: Manage FreeIPA permission
+description: Manage FreeIPA permission and permission members
+options:
+  ipaadmin_principal:
+    description: The admin principal.
+    default: admin
+  ipaadmin_password:
+    description: The admin password.
+    required: false
+  name:
+    description: The permission name string.
+    required: true
+    aliases: ["cn"]
+  right:
+    description: Rights to grant
+    required: false
+    choices: ["read", "search", "compare", "write", "add", "delete", "all"]
+    type: list
+    aliases: ["ipapermright"]
+  attrs:
+    description: All attributes to which the permission applies
+    required: false
+    type: list
+  bindtype:
+    description: Bind rule type
+    required: false
+    choices: ["permission", "all", "anonymous"]
+    aliases: ["ipapermbindruletype"]
+  subtree:
+    description: Subtree to apply permissions to
+    required: false
+    aliases: ["ipapermlocation"]
+  filter:
+    description: Extra target filter
+    required: false
+    type: list
+    aliases: ["extratargetfilter"]
+  rawfilter
+    description: All target filters
+    required: false
+    type: list
+    aliases: ["ipapermtargetfilter"]
+  target:
+    description: Optional DN to apply the permission to
+    required: false
+    aliases: ["ipapermtarget"]
+  targetto:
+    description: Optional DN subtree where an entry can be moved to
+    required: false
+    aliases: ["ipapermtargetto"]
+  targetfrom:
+    description: Optional DN subtree from where an entry can be moved
+    required: false
+    aliases: ["ipapermtargetfrom"]
+  memberof:
+    description: Target members of a group (sets memberOf targetfilter)
+    required: false
+    type: list
+  targetgroup:
+    description: User group to apply permissions to (sets target)
+    required: false
+    aliases: ["targetgroup"]
+  object_type:
+    description: Type of IPA object (sets subtree and objectClass targetfilter)
+    required: false
+    aliases: ["type"]
+  no_members:
+    description: Suppress processing of membership
+    required: false
+    type: bool
+  rename:
+    description: Rename the permission object
+    required: false
+  privilege:
+    description: Member Privilege of Permission
+    required: false
+    type: list
+  action:
+    description: Work on permission or member privilege level.
+    choices: ["permission", "member"]
+    default: permission
+    required: false
+  state:
+    description: The state to ensure.
+    choices: ["present", "absent", "renamed"]
+    default: present
+    required: true
+"""
+
+EXAMPLES = """
+# Ensure permission NAME is present
+- ipapermission:
+    name: manage-my-hostgroup
+    right: all
+    bindtype: permission
+    object_type: host
+
+# Ensure permission "NAME" member privilege VALUE is present
+- ipapermission:
+    name: "Add Automember Rebuild Membership Task"
+    privilege: "Automember Task Administrator"
+    action: member
+
+# Ensure permission "NAME" member privilege VALUE is absent
+- ipapermission:
+    name: "Add Automember Rebuild Membership Task"
+    privilege: "IPA Masters Readers"
+    action: member
+    state: absent
+
+# Ensure permission NAME is absent
+- ipapermission:
+    name: "Removed Permission Name"
+    state: absent
+"""
+
+RETURN = """
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ansible_freeipa_module import \
+    temp_kinit, temp_kdestroy, valid_creds, api_connect, api_command, \
+    compare_args_ipa, module_params_get, gen_add_del_lists
+import six
+
+if six.PY3:
+    unicode = str
+
+
+def find_permission(module, name):
+    """Find if a permission with the given name already exist."""
+    try:
+        _result = api_command(module, "permission_show", name, {"all": True})
+    except Exception:  # pylint: disable=broad-except
+        # An exception is raised if permission name is not found.
+        return None
+    else:
+        return _result["result"]
+
+
+def gen_args(right, attrs, bindtype, subtree,
+             extra_target_filter, rawfilter, target,
+             targetto, targetfrom, memberof, targetgroup,
+             object_type, no_members, rename):
+    _args = {}
+    if right is not None:
+        _args["ipapermright"] = right
+    if attrs is not None:
+        _args["attrs"] = attrs
+    if bindtype is not None:
+        _args["ipapermbindruletype"] = bindtype
+    if subtree is not None:
+        _args["ipapermlocation"] = subtree
+    if extra_target_filter is not None:
+        _args["extratargetfilter"] = extra_target_filter
+    if rawfilter is not None:
+        _args["ipapermtargetfilter"] = rawfilter
+    if target is not None:
+        _args["ipapermtarget"] = target
+    if targetto is not None:
+        _args["ipapermtargetto"] = targetto
+    if targetfrom is not None:
+        _args["ipapermtargetfrom"] = targetfrom
+    if memberof is not None:
+        _args["memberof"] = memberof
+    if targetgroup is not None:
+        _args["targetgroup"] = targetgroup
+    if object_type is not None:
+        _args["type"] = object_type
+    if no_members is not None:
+        _args["no_members"] = no_members
+    if rename is not None:
+        _args["rename"] = rename
+    return _args
+
+
+def gen_member_args(privilege):
+    _args = {}
+    if privilege is not None:
+        _args["privilege"] = privilege
+    return _args
+
+
+def main():
+    ansible_module = AnsibleModule(
+        argument_spec=dict(
+            # general
+            ipaadmin_principal=dict(type="str", default="admin"),
+            ipaadmin_password=dict(type="str", required=False, no_log=True),
+
+            name=dict(type="list", aliases=["cn"],
+                      default=None, required=True),
+            # present
+            right=dict(type="list", aliases=["ipapermright"], default=None,
+                       required=False,
+                       choices=["read", "search", "compare", "write", "add",
+                                "delete", "all"]),
+            attrs=dict(type="list", default=None, required=False),
+            # Note: bindtype has a default of permission for Adds.
+            bindtype=dict(type="str", aliases=["ipapermbindruletype"],
+                          default=None, require=False, choices=["permission",
+                          "all", "anonymous", "self"]),
+            subtree=dict(type="str", aliases=["ipapermlocation"], default=None,
+                         required=False),
+            extra_target_filter=dict(type="list", aliases=["filter",
+                                     "extratargetfilter"], default=None,
+                                     required=False),
+            rawfilter=dict(type="list", aliases=["ipapermtargetfilter"],
+                           default=None, required=False),
+            target=dict(type="str", aliases=["ipapermtarget"], default=None,
+                        required=False),
+            targetto=dict(type="str", aliases=["ipapermtargetto"],
+                          default=None, required=False),
+            targetfrom=dict(type="str", aliases=["ipapermtargetfrom"],
+                            default=None, required=False),
+            memberof=dict(type="list", default=None, required=False),
+            targetgroup=dict(type="str", default=None, required=False),
+            object_type=dict(type="str", aliases=["type"], default=None,
+                             required=False),
+            no_members=dict(type=bool, default=None, require=False),
+            rename=dict(type="str", default=None, required=False),
+            privilege=dict(type="list", default=None, required=False),
+
+            action=dict(type="str", default="permission",
+                        choices=["member", "permission"]),
+            # state
+            state=dict(type="str", default="present",
+                       choices=["present", "absent", "renamed"]),
+        ),
+        supports_check_mode=True,
+    )
+
+    ansible_module._ansible_debug = True
+
+    # Get parameters
+
+    # general
+    ipaadmin_principal = module_params_get(ansible_module,
+                                           "ipaadmin_principal")
+    ipaadmin_password = module_params_get(ansible_module, "ipaadmin_password")
+    names = module_params_get(ansible_module, "name")
+
+    # present
+    right = module_params_get(ansible_module, "right")
+    attrs = module_params_get(ansible_module, "attrs")
+    bindtype = module_params_get(ansible_module, "bindtype")
+    subtree = module_params_get(ansible_module, "subtree")
+    extra_target_filter = module_params_get(ansible_module,
+                                            "extra_target_filter")
+    rawfilter = module_params_get(ansible_module, "rawfilter")
+    target = module_params_get(ansible_module, "target")
+    targetto = module_params_get(ansible_module, "targetto")
+    targetfrom = module_params_get(ansible_module, "targetfrom")
+    memberof = module_params_get(ansible_module, "memberof")
+    targetgroup = module_params_get(ansible_module, "targetgroup")
+    object_type = module_params_get(ansible_module, "object_type")
+    no_members = module_params_get(ansible_module, "no_members")
+    rename = module_params_get(ansible_module, "rename")
+    privilege = module_params_get(ansible_module, "privilege")
+    action = module_params_get(ansible_module, "action")
+
+    # state
+    state = module_params_get(ansible_module, "state")
+
+    # Check parameters
+
+    invalid = []
+
+    if state == "present":
+        if len(names) != 1:
+            ansible_module.fail_json(
+                msg="Only one permission can be added at a time.")
+        if action == "member":
+            invalid = ["right", "attrs", "bindtype", "subtree",
+                       "extra_target_filter", "rawfilter", "target",
+                       "targetto", "targetfrom", "memberof", "targetgroup",
+                       "object_type", "rename"]
+
+    if state == "renamed":
+        if len(names) != 1:
+            ansible_module.fail_json(
+                msg="Only one permission can be renamed at a time.")
+        if action == "member":
+            ansible_module.fail_json(
+                msg="Member Privileges cannot be renamed")
+        invalid = ["right", "attrs", "bindtype", "subtree",
+                   "extra_target_filter", "rawfilter", "target", "targetto",
+                   "targetfrom", "memberof", "targetgroup", "object_type",
+                   "no_members"]
+
+    if state == "absent":
+        if len(names) < 1:
+            ansible_module.fail_json(msg="No name given.")
+        invalid = ["right", "attrs", "bindtype", "subtree",
+                   "extra_target_filter", "rawfilter", "target", "targetto",
+                   "targetfrom", "memberof", "targetgroup", "object_type",
+                   "no_members", "rename"]
+        if action == "permission":
+            invalid.append("privilege")
+
+    for x in invalid:
+        if vars()[x] is not None:
+            ansible_module.fail_json(
+                msg="Argument '%s' can not be used with action "
+                "'%s' and state '%s'" % (x, action, state))
+
+    # Init
+
+    changed = False
+    exit_args = {}
+    ccache_dir = None
+    ccache_name = None
+    try:
+        if not valid_creds(ansible_module, ipaadmin_principal):
+            ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
+                                                 ipaadmin_password)
+        api_connect()
+
+        commands = []
+        for name in names:
+            # Make sure permission exists
+            res_find = find_permission(ansible_module, name)
+
+            # Create command
+            if state == "present":
+                # Generate args
+                args = gen_args(right, attrs, bindtype, subtree,
+                                extra_target_filter, rawfilter, target,
+                                targetto, targetfrom, memberof, targetgroup,
+                                object_type, no_members, rename)
+
+                no_members_value = False
+
+                if no_members is not None:
+                    no_members_value = no_members
+
+                if action == "permission":
+                    # Found the permission
+                    if res_find is not None:
+                        # For all settings is args, check if there are
+                        # different settings in the find result.
+                        # If yes: modify
+                        if not compare_args_ipa(ansible_module, args,
+                                                res_find):
+                            commands.append([name, "permission_mod", args])
+                    else:
+                        commands.append([name, "permission_add", args])
+
+                    member_args = gen_member_args(privilege)
+                    if not compare_args_ipa(ansible_module, member_args,
+                                            res_find):
+
+                        # Generate addition and removal lists
+                        privilege_add, privilege_del = gen_add_del_lists(
+                                privilege, res_find.get("member_privilege"))
+
+                        # Add members
+                        if len(privilege_add) > 0:
+                            commands.append([name, "permission_add_member",
+                                             {
+                                                 "privilege": privilege_add,
+                                                 "no_members": no_members_value
+                                             }])
+                        # Remove members
+                        if len(privilege_del) > 0:
+                            commands.append([name, "permission_remove_member",
+                                             {
+                                                 "privilege": privilege_del,
+                                                 "no_members": no_members_value
+                                             }])
+                elif action == "member":
+                    if res_find is None:
+                        ansible_module.fail_json(
+                            msg="No permission '%s'" % name)
+
+                    if privilege is None:
+                        ansible_module.fail_json(msg="No privilege given")
+
+                    commands.append([name, "permission_add_member",
+                                     {
+                                         "privilege": privilege,
+                                         "no_members": no_members_value
+                                     }])
+                else:
+                    ansible_module.fail_json(
+                        msg="Unknown action '%s'" % action)
+            elif state == "renamed":
+                if action == "permission":
+                    # Generate args
+                    # Note: Only valid arg for rename is rename.
+                    args = gen_args(right, attrs, bindtype, subtree,
+                                    extra_target_filter, rawfilter, target,
+                                    targetto, targetfrom, memberof,
+                                    targetgroup, object_type, no_members,
+                                    rename)
+
+                    # Found the permission
+                    if res_find is not None:
+                        # For all settings is args, check if there are
+                        # different settings in the find result.
+                        # If yes: modify
+                        if not compare_args_ipa(ansible_module, args,
+                                                res_find):
+                            commands.append([name, "permission_mod", args])
+                    else:
+                        ansible_module.fail_json(
+                            msg="Permission not found, cannot rename")
+                else:
+                    ansible_module.fail_json(
+                        msg="Unknown action '%s'" % action)
+            elif state == "absent":
+                if action == "permission":
+                    if res_find is not None:
+                        commands.append([name, "permission_del", {}])
+
+                elif action == "member":
+                    if res_find is None:
+                        ansible_module.fail_json(
+                            msg="No permission '%s'" % name)
+
+                    if privilege is None:
+                        ansible_module.fail_json(msg="No privilege given")
+
+                    commands.append([name, "permission_remove_member",
+                                     {
+                                         "privilege": privilege,
+                                     }])
+
+            else:
+                ansible_module.fail_json(msg="Unknown state '%s'" % state)
+
+        # Execute commands
+
+        for name, command, args in commands:
+            try:
+                result = api_command(ansible_module, command, name,
+                                     args)
+                if "completed" in result:
+                    if result["completed"] > 0:
+                        changed = True
+                else:
+                    changed = True
+            except Exception as e:
+                ansible_module.fail_json(msg="%s: %s: %s" % (command, name,
+                                                             str(e)))
+            # Get all errors
+            # All "already a member" and "not a member" failures in the
+            # result are ignored. All others are reported.
+            errors = []
+            for failed_item in result.get("failed", []):
+                failed = result["failed"][failed_item]
+                for member_type in failed:
+                    for member, failure in failed[member_type]:
+                        if "already a member" in failure \
+                           or "not a member" in failure:
+                            continue
+                        errors.append("%s: %s %s: %s" % (
+                            command, member_type, member, failure))
+            if len(errors) > 0:
+                ansible_module.fail_json(msg=", ".join(errors))
+
+    except Exception as e:
+        ansible_module.fail_json(msg=str(e))
+
+    finally:
+        temp_kdestroy(ccache_dir, ccache_name)
+
+    # Done
+
+    ansible_module.exit_json(changed=changed, **exit_args)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/permission/test_permission.yml b/tests/permission/test_permission.yml
new file mode 100644
index 0000000000000000000000000000000000000000..eea112b45209dde1002068c64b5cc45eb532d391
--- /dev/null
+++ b/tests/permission/test_permission.yml
@@ -0,0 +1,114 @@
+---
+- name: Test permission
+  hosts: ipaserver
+  become: true
+
+  tasks:
+
+  # CLEANUP TEST ITEMS
+
+  - name: Ensure permission perm-test-1 is absent
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-1
+      state: absent
+   
+  # TESTS
+
+  - name: Ensure permission perm-test-1 is present
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-1
+      object_type: host
+      right: all
+    register: result
+    failed_when: not result.changed or result.failed
+
+  - name: Ensure permission perm-test-1 is present again
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-1
+      object_type: host
+      right: all
+    register: result
+    failed_when: result.changed or result.failed
+
+  - name: Ensure permission perm-test-1 member User Administrators privilege is present
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-1
+      privilege: "User Administrators"
+      action: member      
+    register: result
+    failed_when: not result.changed or result.failed
+
+  - name: Ensure permission perm-test-1 member User Administrators privilege is present again
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-1
+      privilege: "User Administrators"
+      action: member
+    register: result
+    failed_when: result.changed or result.failed
+
+  - name: Ensure permission perm-test-1 member User Administrators privilege is absent
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-1
+      privilege: "User Administrators"
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed or result.failed
+
+  # NOTE: We use the "User Administrators" Privilege here since we don't have a module
+  # to make one. A test privilege should be used in the future.
+  - name: Ensure permission perm-test-1 member User Administrators privilege is absent again
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-1
+      privilege: "User Administrators"
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed or result.failed
+
+  - name: Rename permission perm-test-1 to perm-test-renamed
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-1
+      rename: perm-test-renamed
+      state: renamed
+    register: result
+    failed_when: not result.changed or result.failed
+
+  - name: Ensure permission perm-test-1 is absent
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-1
+      state: absent
+    register: result
+    failed_when: result.changed or result.failed
+    
+  - name: Ensure permission perm-test-renamed is present
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-renamed
+      object_type: host
+      right: all
+    register: result
+    failed_when: result.changed or result.failed
+
+  # CLEANUP TEST ITEMS
+
+  - name: Ensure permission perm-test-1 is absent
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-1
+      state: absent
+
+  - name: Ensure permission perm-test-renamed is absent
+    ipapermission:
+      ipaadmin_password: SomeADMINpassword
+      name: perm-test-renamed
+      state: absent