diff --git a/changelogs/fragments/add_roles_module.yml b/changelogs/fragments/add_roles_module.yml new file mode 100644 index 00000000..a66ec203 --- /dev/null +++ b/changelogs/fragments/add_roles_module.yml @@ -0,0 +1,3 @@ +minor_changes: + - New module to add/remove database role members. + - New module to get role member information. diff --git a/plugins/modules/role_member.ps1 b/plugins/modules/role_member.ps1 new file mode 100644 index 00000000..de86e197 --- /dev/null +++ b/plugins/modules/role_member.ps1 @@ -0,0 +1,112 @@ +#!powershell +# -*- coding: utf-8 -*- + +# (c) 2022, John McCall (@lowlydba) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#AnsibleRequires -CSharpUtil Ansible.Basic +#AnsibleRequires -PowerShell ansible_collections.lowlydba.sqlserver.plugins.module_utils._SqlServerUtils +#Requires -Modules @{ ModuleName="dbatools"; ModuleVersion="1.1.112" } + +$ErrorActionPreference = "Stop" + +$spec = @{ + supports_check_mode = $true + options = @{ + database = @{type = 'str'; required = $true } + username = @{type = 'str'; required = $true } + roles = @{type = 'list'; elements = 'str'; required = $false } + state = @{type = 'str'; required = $false; default = 'present'; choices = @('present', 'absent') } + } +} + +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec, @(Get-LowlyDbaSqlServerAuthSpec)) +$sqlInstance, $sqlCredential = Get-SqlCredential -Module $module +$username = $module.Params.username +$database = $module.Params.database +$roles = $module.Params.roles +$state = $module.Params.state +$checkMode = $module.CheckMode + +$module.Result.changed = $false + +$getRoleSplat = @{ + SqlInstance = $sqlInstance + SqlCredential = $sqlCredential + Database = $database + EnableException = $true +} +$module.Result.roles = $roles +$existingRoleObjects = Get-DbaDbRoleMember @getRoleSplat | Where-Object { $_.UserName -eq $username } +$existingRoles = @() +# build an array of roles for the selected user +foreach ($roleObject in $existingRoleObjects) { + $existingRoles += $roleObject.role +} + +if ($state -eq "absent") { + # loop through all roles to remove and see if any are assigned to the user + $removeRoles = @() + foreach ($roleObject in $existingRoleObjects) { + if ($roles.Contains($roleObject.role)) { + $removeRoles += $roleObject.role + } + } + + $module.Result.removeRoles = $removeRoles + if ($removeRoles) { + try { + $removeRolesSplat = @{ + SqlInstance = $sqlInstance + SqlCredential = $sqlCredential + User = $username + Database = $database + Role = $removeRoles + EnableException = $true + WhatIf = $checkMode + Confirm = $false + Verbose = $true + } + $output = Remove-DbaDbRoleMember @removeRolesSplat + $module.Result.changed = $true + } + catch { + $module.FailJson("Removing role failed: $($_.Exception.Message)", $_) + } + } +} +elseif ($state -eq "present") { + # compare the list of roles to add vs the existing roles for the user and get the difference + $addRoles = $roles | Where-Object { $existingRoles -NotContains $_ } + $module.Result.addRoles = $addRoles + if ($null -ne $addRoles) { + try { + $addRolesSplat = @{ + SqlInstance = $sqlInstance + SqlCredential = $sqlCredential + User = $username + Database = $database + Role = $addRoles + EnableException = $true + WhatIf = $checkMode + Confirm = $false + Verbose = $true + } + $output = Add-DbaDbRoleMember @addRolesSplat + $module.Result.changed = $true + } + catch { + $module.FailJson("Adding role failed: $($_.Exception.Message)", $_) + } + } +} +try { + if ($null -ne $output) { + $resultData = ConvertTo-SerializableObject -InputObject $output + $module.Result.data = $resultData + } + $module.ExitJson() +} +catch { + $module.FailJson("Failure: $($_.Exception.Message)", $_) +} diff --git a/plugins/modules/role_member.py b/plugins/modules/role_member.py new file mode 100644 index 00000000..0beb30c7 --- /dev/null +++ b/plugins/modules/role_member.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2022, John McCall (@lowlydba) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = r''' +--- +module: role_member +short_description: Add or remove one or more roles for a given user in a specific database. +description: + - Add or remove one or more roles for a given user in a specific database. +version_added: 1.4.0 +options: + username: + description: + - Name of the user + type: str + required: true + database: + description: + - Database for the user + type: str + required: true + roles: + description: + - Specifies a comma separated list of one or more roles to add or remove + type: list + elements: str + required: false + +author: + - "Joe Krilov (@joey40)" + - "John McCall (@lowlydba)" +requirements: + - L(dbatools,https://www.powershellgallery.com/packages/dbatools/) PowerShell module +extends_documentation_fragment: + - lowlydba.sqlserver.sql_credentials + - lowlydba.sqlserver.attributes.check_mode + - lowlydba.sqlserver.attributes.platform_all + - lowlydba.sqlserver.state +''' + +EXAMPLES = r''' +- name: Add a single role for a user + lowlydba.sqlserver.role_member: + sql_instance: sql-01.myco.io + username: TheIntern + database: InternProject1 + role: db_datareader + +- name: Add multiple roles for a user + lowlydba.sqlserver.role_member: + sql_instance: sql-01.myco.io + username: TheIntern + database: InternProject1 + role: db_datareader, db_datawriter + +- name: Remove roles for a user + lowlydba.sqlserver.role_member: + sql_instance: sql-01.myco.io + username: TheIntern + database: InternProject1 + role: db_datareader, db_datawriter + state: absent +''' + +RETURN = r''' +data: + description: Output from the C(Add-DbaDbRoleMember), C(Get-DbaDbRoleMember), or C(Remove-DbaDbRoleMember) function. + returned: success, but not in check_mode. + type: dict +''' diff --git a/plugins/modules/role_member_info.ps1 b/plugins/modules/role_member_info.ps1 new file mode 100644 index 00000000..0462ec26 --- /dev/null +++ b/plugins/modules/role_member_info.ps1 @@ -0,0 +1,59 @@ +#!powershell +# -*- coding: utf-8 -*- + +# (c) 2022, John McCall (@lowlydba) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#AnsibleRequires -CSharpUtil Ansible.Basic +#AnsibleRequires -PowerShell ansible_collections.lowlydba.sqlserver.plugins.module_utils._SqlServerUtils +#Requires -Modules @{ ModuleName="dbatools"; ModuleVersion="1.1.112" } + +$ErrorActionPreference = "Stop" + +$spec = @{ + supports_check_mode = $true + options = @{ + database = @{type = 'str'; required = $false } + username = @{type = 'str'; required = $false } + roles = @{type = 'list'; elements = 'str'; required = $false } + } +} + +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec, @(Get-LowlyDbaSqlServerAuthSpec)) +$sqlInstance, $sqlCredential = Get-SqlCredential -Module $module +$username = $module.Params.username +$database = $module.Params.database +$roles = $module.Params.roles + +$module.Result.changed = $false + +try { + $getRoleSplat = @{ + SqlInstance = $sqlInstance + SqlCredential = $sqlCredential + EnableException = $true + } + if ($null -ne $roles) { + $getRoleSplat.Add("Role", $roles) + } + if ($null -ne $database) { + $getRoleSplat.Add("Database", $database) + } + if ($null -ne $username) { + $output = Get-DbaDbRoleMember @getRoleSplat | Where-Object { $_.UserName -eq $username } + } + else { + $output = Get-DbaDbRoleMember @getRoleSplat + } + + if ($null -ne $output) { + $resultData = ConvertTo-SerializableObject -InputObject $output + $module.Result.data = $resultData + } + + $module.ExitJson() + +} +catch { + $module.FailJson("Failure: $($_.Exception.Message)", $_) +} diff --git a/plugins/modules/role_member_info.py b/plugins/modules/role_member_info.py new file mode 100644 index 00000000..1603ee1c --- /dev/null +++ b/plugins/modules/role_member_info.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2022, John McCall (@lowlydba) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = r''' +--- +module: role_member_info +short_description: Returns basic information about a role or roles +description: + - Returns basic information about a role or roles. +version_added: 1.4.0 +options: + username: + description: + - Name of the user + type: str + required: false + database: + description: + - Database for the user + type: str + required: false + roles: + description: + - Specifies a comma separated list of one or more roles + type: list + elements: str + required: false +author: + - "Joe Krilov (@joey40)" + - "John McCall (@lowlydba)" +requirements: + - L(dbatools,https://www.powershellgallery.com/packages/dbatools/) PowerShell module +extends_documentation_fragment: + - lowlydba.sqlserver.sql_credentials + - lowlydba.sqlserver.attributes.check_mode_read_only + - lowlydba.sqlserver.attributes.platform_all +''' + +EXAMPLES = r''' +- name: Return member of the db_datareader and db_datawriter role on the 'InternProject1' DB + lowlydba.sqlserver.role_member_info: + sql_instance: sql-01.myco.io + database: InternProject1 + role: db_datareader, db_datawriter + +- name: Return all roles for user 'TheIntern' on the 'InternProject1' DB + lowlydba.sqlserver.role_member_info: + sql_instance: sql-01.myco.io + username: TheIntern + database: InternProject1 +''' + +RETURN = r''' +data: + description: Output from the C(Get-DbaDbRoleMember) function. + returned: always + type: dict +''' diff --git a/tests/integration/targets/role_member/aliases b/tests/integration/targets/role_member/aliases new file mode 100644 index 00000000..4f4b6b91 --- /dev/null +++ b/tests/integration/targets/role_member/aliases @@ -0,0 +1,2 @@ +context/target +setup/once/setup_sqlserver diff --git a/tests/integration/targets/role_member/meta/main.yml b/tests/integration/targets/role_member/meta/main.yml new file mode 100644 index 00000000..a3309752 --- /dev/null +++ b/tests/integration/targets/role_member/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_sqlserver_test_plugins diff --git a/tests/integration/targets/role_member/tasks/main.yml b/tests/integration/targets/role_member/tasks/main.yml new file mode 100644 index 00000000..212be3eb --- /dev/null +++ b/tests/integration/targets/role_member/tasks/main.yml @@ -0,0 +1,137 @@ +--- +- name: Var block + vars: + login_name: "MrRoleTest" + plain_password: "P0pS3cret!23$%" + password_expiration_enabled: false + password_policy_enforced: false + password_must_change: false + enabled: false + default_database: "master" + language: "us_english" + default_schema: "dbo" + username: "MrRoleTest" + database: "master" + module_defaults: + lowlydba.sqlserver.role_member: + sql_instance: "{{ sqlserver_instance }}" + sql_username: "{{ sqlserver_username }}" + sql_password: "{{ sqlserver_password }}" + username: "{{ username }}" + database: "{{ database }}" + lowlydba.sqlserver.login: + sql_instance: "{{ sqlserver_instance }}" + sql_username: "{{ sqlserver_username }}" + sql_password: "{{ sqlserver_password }}" + default_database: "{{ default_database }}" + login: "{{ login_name }}" + password: "{{ plain_password }}" + password_expiration_enabled: "{{ password_expiration_enabled }}" + password_must_change: "{{ password_must_change }}" + enabled: "{{ enabled }}" + language: "{{ language }}" + state: present + lowlydba.sqlserver.user: + sql_instance: "{{ sqlserver_instance }}" + sql_username: "{{ sqlserver_username }}" + sql_password: "{{ sqlserver_password }}" + database: "{{ database }}" + login: "{{ login_name }}" + username: "{{ username }}" + default_schema: "{{ default_schema }}" + state: present + tags: ["role_member"] + block: + - name: Create login + lowlydba.sqlserver.login: + register: result + - assert: + that: + - result.data != None + + - name: Create user + lowlydba.sqlserver.user: + register: result + - assert: + that: + - result.data != None + + - name: Add roles for the user + lowlydba.sqlserver.role_member: + roles: db_datareader, db_datawriter + register: result + - assert: + that: + - result is changed + - result.data.SqlInstance != None + - result.data.Database == "{{ database }}" + - result.data.UserName == "{{ username }}" + + - name: Add roles for the user in checkmode + lowlydba.sqlserver.role_member: + roles: db_datareader, db_datawriter, db_ddladmin + register: result + check_mode: true + - assert: + that: + - result is changed + + - name: Verify no changes from checkmode + lowlydba.sqlserver.role_member: + roles: db_ddladmin + state: "absent" + register: result + - assert: + that: + - result is not changed + + - name: Get a list of roles without making changes + lowlydba.sqlserver.role_member: + register: result + - assert: + that: + - result is not changed + - result.existingRoles != None + + - name: Drop roles for the user + lowlydba.sqlserver.role_member: + roles: db_datareader, db_datawriter + state: "absent" + register: result + - assert: + that: + - result is changed + + - name: Verify drop roles for the user + lowlydba.sqlserver.role_member: + roles: db_datareader, db_datawriter + state: "absent" + register: result + - assert: + that: + - result is not changed + - result.noRoles != None + + - name: Drop user + lowlydba.sqlserver.user: + state: "absent" + register: result + - assert: + that: + - result.data != None + + - name: Drop login + lowlydba.sqlserver.login: + state: "absent" + register: result + - assert: + that: + - result.data != None + + always: + - name: Drop user + lowlydba.sqlserver.user: + state: "absent" + - name: Drop login + lowlydba.sqlserver.login: + state: "absent" diff --git a/tests/integration/targets/role_member_info/aliases b/tests/integration/targets/role_member_info/aliases new file mode 100644 index 00000000..4f4b6b91 --- /dev/null +++ b/tests/integration/targets/role_member_info/aliases @@ -0,0 +1,2 @@ +context/target +setup/once/setup_sqlserver diff --git a/tests/integration/targets/role_member_info/meta/main.yml b/tests/integration/targets/role_member_info/meta/main.yml new file mode 100644 index 00000000..a3309752 --- /dev/null +++ b/tests/integration/targets/role_member_info/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_sqlserver_test_plugins diff --git a/tests/integration/targets/role_member_info/tasks/main.yml b/tests/integration/targets/role_member_info/tasks/main.yml new file mode 100644 index 00000000..970d9dfe --- /dev/null +++ b/tests/integration/targets/role_member_info/tasks/main.yml @@ -0,0 +1,120 @@ +--- +- name: Var block + vars: + login_name: "MrRoleTest" + plain_password: "P0pS3cret!23$%" + password_expiration_enabled: false + password_policy_enforced: false + password_must_change: false + enabled: false + default_database: "master" + language: "us_english" + default_schema: "dbo" + username: "MrRoleTest" + database: "master" + module_defaults: + lowlydba.sqlserver.role_member_info: + sql_instance: "{{ sqlserver_instance }}" + sql_username: "{{ sqlserver_username }}" + sql_password: "{{ sqlserver_password }}" + database: "{{ database }}" + lowlydba.sqlserver.role_member: + sql_instance: "{{ sqlserver_instance }}" + sql_username: "{{ sqlserver_username }}" + sql_password: "{{ sqlserver_password }}" + username: "{{ username }}" + database: "{{ database }}" + lowlydba.sqlserver.login: + sql_instance: "{{ sqlserver_instance }}" + sql_username: "{{ sqlserver_username }}" + sql_password: "{{ sqlserver_password }}" + default_database: "{{ default_database }}" + login: "{{ login_name }}" + password: "{{ plain_password }}" + password_expiration_enabled: "{{ password_expiration_enabled }}" + password_must_change: "{{ password_must_change }}" + enabled: "{{ enabled }}" + language: "{{ language }}" + state: present + lowlydba.sqlserver.user: + sql_instance: "{{ sqlserver_instance }}" + sql_username: "{{ sqlserver_username }}" + sql_password: "{{ sqlserver_password }}" + database: "{{ database }}" + login: "{{ login_name }}" + username: "{{ username }}" + default_schema: "{{ default_schema }}" + state: present + tags: ["role_member"] + block: + - name: Create login + lowlydba.sqlserver.login: + register: result + - assert: + that: + - result.data != None + + - name: Create user + lowlydba.sqlserver.user: + register: result + - assert: + that: + - result.data != None + + - name: Add roles for the user + lowlydba.sqlserver.role_member: + roles: db_datareader, db_datawriter + register: result + - assert: + that: + - result is changed + + + - name: Get roles for the user + lowlydba.sqlserver.role_member_info: + username: "{{ username }}" + register: result + - assert: + that: + - result is not changed + + - name: Get member of db_datareader and db_datawriter roles + lowlydba.sqlserver.role_member_info: + roles: db_datareader, db_datawriter + register: result + - assert: + that: + - result is not changed + + - name: Drop roles for the user + lowlydba.sqlserver.role_member: + roles: db_datareader, db_datawriter + state: "absent" + register: result + - assert: + that: + - result is changed + + - name: Drop user + lowlydba.sqlserver.user: + state: "absent" + register: result + - assert: + that: + - result.data != None + + - name: Drop login + lowlydba.sqlserver.login: + state: "absent" + register: result + - assert: + that: + - result.data != None + + always: + - name: Drop user + lowlydba.sqlserver.user: + state: "absent" + - name: Drop login + lowlydba.sqlserver.login: + state: "absent" diff --git a/tests/integration/targets/win_role_member/aliases b/tests/integration/targets/win_role_member/aliases new file mode 100644 index 00000000..544c089c --- /dev/null +++ b/tests/integration/targets/win_role_member/aliases @@ -0,0 +1,5 @@ +windows/all +windows/group/1 +context/target +setup/once/setup_win_sqlserver +needs/target/role_member diff --git a/tests/integration/targets/win_role_member/meta/main.yml b/tests/integration/targets/win_role_member/meta/main.yml new file mode 100644 index 00000000..7d1f5d9c --- /dev/null +++ b/tests/integration/targets/win_role_member/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - role_member diff --git a/tests/integration/targets/win_role_member_info/aliases b/tests/integration/targets/win_role_member_info/aliases new file mode 100644 index 00000000..77bf7d1a --- /dev/null +++ b/tests/integration/targets/win_role_member_info/aliases @@ -0,0 +1,5 @@ +windows/all +windows/group/1 +context/target +setup/once/setup_win_sqlserver +needs/target/role_member_info diff --git a/tests/integration/targets/win_role_member_info/meta/main.yml b/tests/integration/targets/win_role_member_info/meta/main.yml new file mode 100644 index 00000000..065d01a6 --- /dev/null +++ b/tests/integration/targets/win_role_member_info/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - role_member_info