From 835a244f8aec71a6f299e826d2ce1a9490ddf4bc Mon Sep 17 00:00:00 2001 From: John McCall Date: Sun, 15 Dec 2024 13:46:25 -0500 Subject: [PATCH] feat: add user_role module --- galaxy.yml | 2 +- plugins/modules/user_role.ps1 | 123 ++++++++++++++++++ plugins/modules/user_role.py | 70 ++++++++++ tests/integration/targets/user_role/aliases | 2 + .../targets/user_role/meta/main.yml | 3 + .../targets/user_role/tasks/main.yml | 114 ++++++++++++++++ .../integration/targets/win_user_role/aliases | 5 + .../targets/win_user_role/meta/main.yml | 3 + 8 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 plugins/modules/user_role.ps1 create mode 100644 plugins/modules/user_role.py create mode 100644 tests/integration/targets/user_role/aliases create mode 100644 tests/integration/targets/user_role/meta/main.yml create mode 100644 tests/integration/targets/user_role/tasks/main.yml create mode 100644 tests/integration/targets/win_user_role/aliases create mode 100644 tests/integration/targets/win_user_role/meta/main.yml diff --git a/galaxy.yml b/galaxy.yml index 84b1eb80..7decbcac 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: lowlydba name: sqlserver -version: 2.3.6 +version: 2.4.0 readme: README.md authors: - John McCall (github.com/lowlydba) diff --git a/plugins/modules/user_role.ps1 b/plugins/modules/user_role.ps1 new file mode 100644 index 00000000..454c2b2b --- /dev/null +++ b/plugins/modules/user_role.ps1 @@ -0,0 +1,123 @@ +#!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="2.0.0" } + +$ErrorActionPreference = "Stop" + +$spec = @{ + supports_check_mode = $true + options = @{ + database = @{type = 'str'; required = $true } + username = @{type = 'str'; required = $true } + role = @{type = 'str'; required = $true } + 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 +$role = $module.Params.role +$state = $module.Params.state +$checkMode = $module.CheckMode + +$module.Result.changed = $false + +$getUserSplat = @{ + SqlInstance = $sqlInstance + SqlCredential = $sqlCredential + Database = $database + User = $username + EnableException = $true +} +$getRoleSplat = @{ + SqlInstance = $sqlInstance + SqlCredential = $sqlCredential + Database = $database + Role = $role + EnableException = $true +} +$getRoleMemberSplat = @{ + SqlInstance = $sqlInstance + SqlCredential = $sqlCredential + Database = $database + Role = $role + IncludeSystemUser = $true + EnableException = $true +} + +# Verify user and role exist, DBATools currently fails silently +$existingUser = Get-DbaDbUser @getUserSplat +if ($null -eq $existingUser) { + $module.FailJson("User [$username] does not exist in database [$database].") +} +$existingRole = Get-DbaDbRole @getRoleSplat +if ($null -eq $existingRole) { + $module.FailJson("Role [$role] does not exist in database [$database].") +} + +# Get role members +$existingRoleMembers = Get-DbaDbRoleMember @getRoleMemberSplat + +if ($state -eq "absent") { + if ($existingRoleMembers.username -contains $username) { + try { + $removeRoleMemberSplat = @{ + SqlInstance = $sqlInstance + SqlCredential = $sqlCredential + User = $username + Database = $database + Role = $role + EnableException = $true + WhatIf = $checkMode + Force = $true + Confirm = $false + } + $output = Remove-DbaDbRoleMember @removeRoleMemberSplat + $module.Result.changed = $true + } + catch { + $module.FailJson("Removing user [$username] from database role [$role] failed: $($_.Exception.Message)", $_) + } + } +} +elseif ($state -eq "present") { + # Add user to role + if ($existingRoleMembers.username -notcontains $username) { + try { + $addRoleMemberSplat = @{ + SqlInstance = $sqlInstance + SqlCredential = $sqlCredential + User = $username + Database = $database + Role = $role + EnableException = $true + WhatIf = $checkMode + Force = $true + Confirm = $false + } + $output = Add-DbaDbRoleMember @addRoleMemberSplat + $module.Result.changed = $true + } + catch { + $module.FailJson("Adding user [$username] to database role [$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/user_role.py b/plugins/modules/user_role.py new file mode 100644 index 00000000..f120ba74 --- /dev/null +++ b/plugins/modules/user_role.py @@ -0,0 +1,70 @@ +#!/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: user_role +short_description: Configures a user's role in a database. +description: + - Adds or removes a user's role in a database. +version_added: 2.4.0 +options: + database: + description: + - Database for the user. + type: str + required: true + username: + description: + - Name of the user. + type: str + required: true + role: + description: + - The database role for the user to be modified. + type: str + required: true +author: "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 user to a fixed db role + lowlydba.sqlserver.user_role: + sql_instance: sql-01.myco.io + username: TheIntern + database: InternProject1 + role: db_owner + +- name: Remove a user from a fixed db role + lowlydba.sqlserver.login: + sql_instance: sql-01.myco.io + username: TheIntern + database: InternProject1 + role: db_owner + state: absent + +- name: Add a user to a custom db role + lowlydba.sqlserver.login: + sql_instance: sql-01.myco.io + username: TheIntern + database: InternProject1 + role: db_intern + state: absent +''' + +RETURN = r''' +data: + description: Output from the C(Remove-DbaDbRoleMember), (Get-DbaDbRoleMember), or C(Add-DbaDbRoleMember) functions. + returned: success, but not in check_mode. + type: dict +''' diff --git a/tests/integration/targets/user_role/aliases b/tests/integration/targets/user_role/aliases new file mode 100644 index 00000000..4f4b6b91 --- /dev/null +++ b/tests/integration/targets/user_role/aliases @@ -0,0 +1,2 @@ +context/target +setup/once/setup_sqlserver diff --git a/tests/integration/targets/user_role/meta/main.yml b/tests/integration/targets/user_role/meta/main.yml new file mode 100644 index 00000000..a3309752 --- /dev/null +++ b/tests/integration/targets/user_role/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - setup_sqlserver_test_plugins diff --git a/tests/integration/targets/user_role/tasks/main.yml b/tests/integration/targets/user_role/tasks/main.yml new file mode 100644 index 00000000..870e37b0 --- /dev/null +++ b/tests/integration/targets/user_role/tasks/main.yml @@ -0,0 +1,114 @@ +--- +- name: Var block + vars: + login_name: "PhillipJFry" + 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: "PhillipJFry" + database: "master" + role: "db_owner" + module_defaults: + 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 + lowlydba.sqlserver.user_role: + sql_instance: "{{ sqlserver_instance }}" + sql_username: "{{ sqlserver_username }}" + sql_password: "{{ sqlserver_password }}" + database: "{{ database }}" + username: "{{ username }}" + role: "{{ role }}" + state: present + tags: ["sqlserver.user"] + 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 + - result.data.ComputerName != None + - result.data.InstanceName != None + - result.data.SqlInstance != None + - result.data.Database == "{{ database }}" + - result.data.DefaultSchema == "{{ default_schema }}" + - result.data.Login == "{{ login_name }}" + - result.data.Name == "{{ username }}" + + - name: Add user to database role + lowlydba.sqlserver.user_role: + register: result + - assert: + that: + - result is changed + + - name: Add user to non-existent database role + lowlydba.sqlserver.user_role: + role: db_IMadeThisOneUp + register: result + - assert: + that: + - result.failed == true + - "'Role [db_IMadeThisOneUp] does not exist in database' in result.msg" + + - name: Add non-existent user to database role + lowlydba.sqlserver.user_role: + username: NewUserWhoThis + register: result + - assert: + that: + - result.failed == true + - "'User [NewUserWhoThis] does not exist in database' in result.msg" + + - name: Add user again to database role + lowlydba.sqlserver.user_role: + register: result + - assert: + that: + - result is not changed + + - name: Remove user from database role + lowlydba.sqlserver.user_role: + register: result + - assert: + that: + - result is changed + + always: + - name: Drop user + lowlydba.sqlserver.user: + state: "absent" + - name: Drop login + lowlydba.sqlserver.login: + state: "absent" diff --git a/tests/integration/targets/win_user_role/aliases b/tests/integration/targets/win_user_role/aliases new file mode 100644 index 00000000..486f09cf --- /dev/null +++ b/tests/integration/targets/win_user_role/aliases @@ -0,0 +1,5 @@ +windows/all +windows/group/1 +context/target +setup/once/setup_win_sqlserver +needs/target/user_role diff --git a/tests/integration/targets/win_user_role/meta/main.yml b/tests/integration/targets/win_user_role/meta/main.yml new file mode 100644 index 00000000..5b743c06 --- /dev/null +++ b/tests/integration/targets/win_user_role/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - user_role