diff --git a/lib/puppet/parser/functions/sqlserver_upcase.rb b/lib/puppet/parser/functions/sqlserver_upcase.rb new file mode 100644 index 00000000..b51405d4 --- /dev/null +++ b/lib/puppet/parser/functions/sqlserver_upcase.rb @@ -0,0 +1,28 @@ +module Puppet::Parser::Functions + newfunction(:sqlserver_upcase, :type => :rvalue, :arity => 1) do |arguments| + + raise(Puppet::ParseError, "upcase(): Wrong number of arguments " + + "given (#{arguments.size} for 1)") if arguments.size != 1 + + value = arguments[0] + + unless value.is_a?(Array) || value.is_a?(Hash) || value.respond_to?(:upcase) + raise(Puppet::ParseError, 'upcase(): Requires an ' + + 'array, hash or object that responds to upcase in order to work') + end + + if value.is_a?(Array) + # Numbers in Puppet are often string-encoded which is troublesome ... + result = value.collect { |i| function_sqlserver_upcase([i]) } + elsif value.is_a?(Hash) + result = {} + value.each_pair do |k, v| + result[function_sqlserver_upcase([k])] = function_sqlserver_upcase([v]) + end + else + result = value.upcase + end + + return result + end +end diff --git a/lib/puppet/parser/functions/sqlserver_validate_instance_name.rb b/lib/puppet/parser/functions/sqlserver_validate_instance_name.rb index 74991c77..152876e2 100644 --- a/lib/puppet/parser/functions/sqlserver_validate_instance_name.rb +++ b/lib/puppet/parser/functions/sqlserver_validate_instance_name.rb @@ -20,6 +20,9 @@ module Puppet::Parser::Functions end value = args[0] errors = [] + if value.length < 1 || value.empty? + errors << "Instance name must be between 1 to 16 characters" + end if value.length > 16 errors << "Instance name can not be larger than 16 characters, you provided #{value}" end diff --git a/manifests/role.pp b/manifests/role.pp new file mode 100644 index 00000000..156203db --- /dev/null +++ b/manifests/role.pp @@ -0,0 +1,101 @@ +## +# == Define Resource Type: sqlserver::role::permissions +# +# +# === Requirement/Dependencies: +# +# Requires defined type {sqlserver::config} in order to execute against the SQL Server instance +# +# +# === Parameters +# +# [ensure] +# Whether the role should be absent or present +# +# [role] +# The name of the role for which the permissions will be manage. +# +# [instance] +# The name of the instance where the role and database exists. Defaults to 'MSSQLSERVER' +# +# [authorization] +# The database principal that should own the role +# +# [type] +# Whether the Role is `SERVER` or `DATABASE` +# +# [database] +# The name of the database the role exists on when specifying `type => 'DATABASE'`. Defaults to 'master' +# +# [permissions] +# A hash of permissions that should be managed for the role. Valid keys are 'GRANT', 'GRANT_WITH_OPTION', 'DENY' or 'REVOKE'. Valid values must be an array of Strings i.e. {'GRANT' => ['CONNECT', 'CREATE ANY DATABASE'] } +# +## +define sqlserver::role( + $ensure = present, + $role = $title, + $instance = 'MSSQLSERVER', + $authorization = undef, + $type = 'SERVER', + $database = 'master', + $permissions = { }, +){ + sqlserver_validate_instance_name($instance) + sqlserver_validate_range($role, 1, 128, 'Role names must be between 1 and 128 characters') + + validate_re($type, ['^SERVER$','^DATABASE$'], "Type must be either 'SERVER' or 'DATABASE', provided '${type}'") + + sqlserver_validate_range($database, 1, 128, 'Database name must be between 1 and 128 characters') + if $type == 'SERVER' and $database != 'master' { + fail('Can not specify a database other than master when managing SERVER ROLES') + } + + $_create_delete = $ensure ? { + present => 'create', + absent => 'delete', + } + + sqlserver_tsql{ "role-${role}-${instance}": + command => template("sqlserver/${_create_delete}/role.sql.erb"), + onlyif => template('sqlserver/query/role_exists.sql.erb'), + instance => $instance, + } + + if $ensure == present { + validate_hash($permissions) + $_upermissions = sqlserver_upcase($permissions) + + Sqlserver::Role::Permissions{ + role => $role, + instance => $instance, + database => $database, + type => $type, + require => Sqlserver_tsql["role-${role}-${instance}"] + } + if has_key($_upermissions, 'GRANT') and is_array($_upermissions['GRANT']) { + sqlserver::role::permissions{ "Sqlserver::Role[${title}]-GRANT-${role}": + state => 'GRANT', + permissions => $_upermissions['GRANT'], + } + } + if has_key($_upermissions, 'DENY') and is_array($_upermissions['DENY']) { + sqlserver::role::permissions{ "Sqlserver::Role[${title}]-DENY-${role}": + state => 'DENY', + permissions => $_upermissions['DENY'], + } + } + if has_key($_upermissions, 'REVOKE') and is_array($_upermissions['REVOKE']) { + sqlserver::role::permissions{ "Sqlserver::Role[${title}]-REVOKE-${role}": + state => 'REVOKE', + permissions => $_upermissions['REVOKE'], + } + } + if has_key($_upermissions, 'GRANT_WITH_OPTION') and is_array($_upermissions['GRANT_WITH_OPTION']) { + sqlserver::role::permissions{ "Sqlserver::Role[${title}]-GRANT-WITH_GRANT_OPTION-${role}": + state => 'GRANT', + with_grant_option => true, + permissions => $_upermissions['GRANT_WITH_OPTION'], + } + } + } +} diff --git a/manifests/role/permissions.pp b/manifests/role/permissions.pp new file mode 100644 index 00000000..cb80072c --- /dev/null +++ b/manifests/role/permissions.pp @@ -0,0 +1,74 @@ +## +# == Define Resource Type: sqlserver::role::permissions +# +# +# === Requirement/Dependencies: +# +# Requires defined type {sqlserver::config} in order to execute against the SQL Server instance +# +# +# === Parameters +# [role] +# The name of the role for which the permissions will be manage. +# +# [permissions] +# An array of permissions you want manged for the given role +# +# [state] +# The state you would like the permission in. Accepts 'GRANT', 'DENY', 'REVOKE' Please note that REVOKE equates to absent and will default to database and system level permissions. +# +# [with_grant_option] +# Whether to give the role the option to grant this permission to other principal objects, accepts true or false, defaults to false +# +# [type] +# Whether the Role is `SERVER` or `DATABASE` +# +# [database] +# The name of the database the role exists on when specifying `type => 'DATABASE'`. Defaults to 'master' +# +# [instance] +# The name of the instance where the role and database exists. Defaults to 'MSSQLSERVER' +# +## +define sqlserver::role::permissions ( + $role, + $permissions, + $state = 'GRANT', + $with_grant_option = false, + $type = 'SERVER', + $database = 'master', + $instance = 'MSSQLSERVER', +){ + validate_array($permissions) + if size($permissions) < 1 { + warning("Received an empty set of permissions for ${title}, no further action will be taken") + } else{ + sqlserver_validate_instance_name($instance) + #Validate state + $_state = upcase($state) + validate_re($_state,'^(GRANT|REVOKE|DENY)$',"State can only be of 'GRANT', 'REVOKE' or 'DENY' you passed ${state}") + validate_bool($with_grant_option) + + #Validate role + sqlserver_validate_range($role, 1, 128, 'Role names must be between 1 and 128 characters') + + #Validate permissions + sqlserver_validate_range($permissions, 4, 128, 'Permissions must be between 4 and 128 characters') + + $_upermissions = upcase($permissions) + + $_grant_option = $with_grant_option ? { + true => '-WITH_GRANT_OPTION', + false => '', + } + ## + # Parameters required in template are _state, role, _upermissions, database, type, with_grant_option + ## + sqlserver_tsql{ "role-permissions-${role}-${_state}${_grant_option}-${instance}": + instance => $instance, + command => template('sqlserver/create/role/permissions.sql.erb'), + onlyif => template('sqlserver/query/role/permission_exists.sql.erb'), + } + } + +} diff --git a/spec/defines/role/permissions_spec.rb b/spec/defines/role/permissions_spec.rb new file mode 100644 index 00000000..1a615478 --- /dev/null +++ b/spec/defines/role/permissions_spec.rb @@ -0,0 +1,143 @@ +require 'spec_helper' + +RSpec.describe 'sqlserver::role::permissions' do + include_context 'manifests' do + let(:title) { 'myTitle' } + let(:sqlserver_tsql_title) { 'role-permissions-myCustomRole-GRANT-MSSQLSERVER' } + let(:params) { { + :role => 'myCustomRole', + :permissions => %w(INSERT UPDATE DELETE SELECT), + } } + end + + context 'sql variables' do + let(:params) { { + :role => 'myCustomRole', + :permissions => %w(INSERT UPDATE DELETE SELECT), + } } + declare_variables = [ + "DECLARE + @perm_state varchar(250), + @error_msg varchar(250), + @permission varchar(250), + @princ_name varchar(50), + @princ_type varchar(50), + @state_desc varchar(50);", + "SET @princ_type = 'SERVER_ROLE';", + "SET @princ_name = 'myCustomRole';", + "SET @state_desc = 'GRANT';"] + let(:should_contain_command) { declare_variables } + let(:should_contain_onlyif) { declare_variables } + it_behaves_like 'sqlserver_tsql command' + it_behaves_like 'sqlserver_tsql onlyif' + end + + context 'type =>' do + shared_examples 'GRANT Permissions' do |type| + base_commands = [ + "SET @princ_type = '#{type.upcase}_ROLE';", + "ISNULL( + (SELECT state_desc FROM sys.#{type.downcase}_permissions prem + JOIN sys.#{type.downcase}_principals r ON r.principal_id = prem.grantee_principal_id + WHERE r.name = @princ_name AND r.type_desc = @princ_type + AND prem.permission_name = @permission), + 'REVOKE')", + "SET @permission = 'INSERT';", + "SET @permission = 'UPDATE';", + "SET @permission = 'DELETE';", + "SET @permission = 'SELECT';", + ] + should_commands = [ + "GRANT INSERT TO [myCustomRole];", + "GRANT UPDATE TO [myCustomRole];", + "GRANT DELETE TO [myCustomRole];", + "GRANT SELECT TO [myCustomRole];" + ] + let(:should_contain_command) { base_commands + should_commands } + let(:should_contain_onlyif) { base_commands } + it_behaves_like 'sqlserver_tsql command' + it_behaves_like 'sqlserver_tsql onlyif' + end + + describe 'DATABASE' do + let(:additional_params) { { + :type => 'DATABASE', + } } + it_behaves_like 'GRANT Permissions', 'database' + end + + describe 'SERVER' do + let(:additional_params) { { + :type => 'SERVER', + } } + it_behaves_like 'GRANT Permissions', 'server' + end + end + + context 'permissions =>' do + describe '[INSERT UPDATE DELETE SELECT]' do + declare_variables = [ + "SET @permission = 'INSERT';", + "SET @permission = 'UPDATE';", + "SET @permission = 'DELETE';", + "SET @permission = 'SELECT';", + ] + let(:should_contain_command) { declare_variables + + [ + "GRANT INSERT TO [myCustomRole];", + "GRANT UPDATE TO [myCustomRole];", + "GRANT DELETE TO [myCustomRole];", + "GRANT SELECT TO [myCustomRole];" + ] } + let(:should_contain_onlyif) { declare_variables } + it_behaves_like 'sqlserver_tsql command' + it_behaves_like 'sqlserver_tsql onlyif' + end + describe '[]' do + let(:params) { { + :role => 'myCustomRole', + :permissions => [] + } } + it { + should compile + should_not contain_sqlserver_tsql(sqlserver_tsql_title) + } + end + end + + context 'database =>' do + describe 'default' do + let(:should_contain_command) { ['USE [master];'] } + let(:should_contain_onlyif) { ['USE [master];'] } + it_behaves_like 'sqlserver_tsql command' + it_behaves_like 'sqlserver_tsql onlyif' + end + describe 'customDatabase' do + let(:additional_params) { {:database => 'customDatabase'} } + let(:should_contain_command) { ['USE [customDatabase];'] } + it_behaves_like 'sqlserver_tsql command' + let(:should_contain_onlyif) { ['USE [customDatabase];'] } + it_behaves_like 'sqlserver_tsql onlyif' + let(:should_contain_without_command) { ['USE [master];'] } + it_behaves_like 'sqlserver_tsql without_command' + let(:should_contain_without_onlyif) { ['USE [master];'] } + it_behaves_like 'sqlserver_tsql without_onlyif' + end + end + + context 'instance =>' do + ['MSSQLSERVER', 'MYINSTANCE'].each do |instance| + describe "should contain #{instance} for sqlserver_tsql" do + let(:params) { { + :role => 'myCustomRole', + :permissions => %w(INSERT UPDATE DELETE SELECT), + :instance => instance + } } + it { + should contain_sqlserver_tsql("role-permissions-myCustomRole-GRANT-#{instance}").with_instance(instance) + } + end + end + end + +end diff --git a/spec/defines/role_spec.rb b/spec/defines/role_spec.rb new file mode 100644 index 00000000..511f8f6f --- /dev/null +++ b/spec/defines/role_spec.rb @@ -0,0 +1,135 @@ +require 'spec_helper' +require File.expand_path(File.join(File.dirname(__FILE__), 'manifest_shared_examples.rb')) + +RSpec.describe 'sqlserver::role', :type => :define do + include_context 'manifests' do + let(:sqlserver_tsql_title) { 'role-myCustomRole-MSSQLSERVER' } + let(:title) { 'myCustomRole' } + end + + context 'type =>' do + describe 'invalid' do + let(:additional_params) { { + :type => 'invalid', + } } + let(:raise_error_check) { "Type must be either 'SERVER' or 'DATABASE', provided 'invalid'" } + it_behaves_like 'validation error' + end + describe 'SERVER' do + let(:should_contain_command) { [ + 'USE [master];', + 'CREATE SERVER ROLE [myCustomRole];', + /IF NOT EXISTS\(\n\s+SELECT name FROM sys\.server_principals WHERE type_desc = 'SERVER_ROLE' AND name = 'myCustomRole'\n\)/, + "THROW 51000, 'The SERVER ROLE [myCustomRole] does not exist', 10" + ] } + let(:should_contain_onlyif) { [ + /IF NOT EXISTS\(\n\s+SELECT name FROM sys\.server_principals WHERE type_desc = 'SERVER_ROLE' AND name = 'myCustomRole'\n\)/, + "THROW 51000, 'The SERVER ROLE [myCustomRole] does not exist', 10" + ] } + it_behaves_like 'sqlserver_tsql command' + it_behaves_like 'sqlserver_tsql onlyif' + end + describe 'DATABASE' do + let(:additional_params) { { + 'type' => 'DATABASE', + } } + let(:should_contain_command) { [ + 'USE [master];', + 'CREATE ROLE [myCustomRole];', + /IF NOT EXISTS\(\n\s+SELECT name FROM sys\.database_principals WHERE type_desc = 'DATABASE_ROLE' AND name = 'myCustomRole'\n\)/, + "THROW 51000, 'The DATABASE ROLE [myCustomRole] does not exist', 10" + ] } + let(:should_contain_onlyif) { [ + /IF NOT EXISTS\(\n\s+SELECT name FROM sys\.database_principals WHERE type_desc = 'DATABASE_ROLE' AND name = 'myCustomRole'\n\)/, + "THROW 51000, 'The DATABASE ROLE [myCustomRole] does not exist', 10", + ] } + + it_behaves_like 'sqlserver_tsql command' + it_behaves_like 'sqlserver_tsql onlyif' + + end + end + + context 'database =>' do + let(:additional_params) { { + 'database' => 'myCrazyDb', + } } + describe 'with server role type' do + let(:raise_error_check) { 'Can not specify a database other than master when managing SERVER ROLES' } + it_behaves_like 'validation error' + end + describe 'with database role type' do + let(:additional_params) { { + 'database' => 'myCrazyDb', + 'type' => 'DATABASE', + } } + let(:should_contain_command) { [ + 'USE [myCrazyDb];', + ] } + it_behaves_like 'sqlserver_tsql command' + end + end + + context 'instance =>' do + describe 'non default instance' do + let(:params) { {:instance => 'MYCUSTOM'} } + it { + should contain_sqlserver_tsql('role-myCustomRole-MYCUSTOM').with_instance('MYCUSTOM') + } + end + describe 'empty instance' do + let(:additional_params) { {'instance' => ''} } + let(:raise_error_check) { 'Instance name must be between 1 to 16 characters' } + it_behaves_like 'validation error' + end + end + + context 'authorization =>' do + describe 'undef' do + let(:should_not_contain_command) { [ + /AUTHORIZATION/i, + 'ALTER AUTHORIZATION ON ', + ] } + it_behaves_like 'sqlserver_tsql without_command' + end + describe 'myUser' do + let(:additional_params) { { + :authorization => 'myUser', + } } + let(:should_contain_command) { [ + 'CREATE SERVER ROLE [myCustomRole] AUTHORIZATION [myUser];', + 'ALTER AUTHORIZATION ON SERVER ROLE::[myCustomRole] TO [myUser];' + ] } + it_behaves_like 'sqlserver_tsql command' + end + describe 'myUser on Database' do + let(:additional_params) { { + :authorization => 'myUser', + :type => 'DATABASE', + } } + let(:should_contain_command) { [ + 'CREATE ROLE [myCustomRole] AUTHORIZATION [myUser];', + 'ALTER AUTHORIZATION ON ROLE::[myCustomRole] TO [myUser];' + ] } + it_behaves_like 'sqlserver_tsql command' + end + end + + context 'ensure =>' do + describe 'absent' do + let(:additional_params) { { + :ensure => 'absent', + } } + let(:should_contain_command) { [ + 'USE [master];', + 'DROP SERVER ROLE [myCustomRole];' + ] } + let(:should_contain_onlyif) { [ + 'IF EXISTS(', + ] } + it_behaves_like 'sqlserver_tsql command' + it_behaves_like 'sqlserver_tsql onlyif' + end + end + +end diff --git a/spec/functions/sqlserver_upcase_spec.rb b/spec/functions/sqlserver_upcase_spec.rb new file mode 100644 index 00000000..47312687 --- /dev/null +++ b/spec/functions/sqlserver_upcase_spec.rb @@ -0,0 +1,54 @@ +#! /usr/bin/env ruby -S rspec +require 'spec_helper' + +describe "the sqlserver_upcase function" do + let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + + it "should exist" do + expect(Puppet::Parser::Functions.function("sqlserver_upcase")).to eq("function_sqlserver_upcase") + end + + it "should upcase a string" do + result = scope.function_sqlserver_upcase(["abc"]) + expect(result).to(eq('ABC')) + end + + it "should do nothing if a string is already upcase" do + result = scope.function_sqlserver_upcase(["ABC"]) + expect(result).to(eq('ABC')) + end + + it "should accept objects which extend String" do + class AlsoString < String + end + + value = AlsoString.new('abc') + result = scope.function_sqlserver_upcase([value]) + result.should(eq('ABC')) + end + + it 'should accept hashes and return uppercase' do + expect( + scope.function_sqlserver_upcase([{'test' => %w(this that and other thing)}]) + ).to eq({'TEST' => %w(THIS THAT AND OTHER THING)}) + end + + if :test.respond_to?(:upcase) + it 'should accept hashes of symbols' do + expect( + scope.function_sqlserver_upcase([{:test => [:this, :that, :other]}]) + ).to eq({:TEST => [:THIS, :THAT, :OTHER]}) + end + it 'should return upcase symbol' do + expect( + scope.function_sqlserver_upcase([:test]) + ).to eq(:TEST) + end + it 'should return mixed objects in upcease' do + expect( + scope.function_sqlserver_upcase([[:test, 'woot']]) + ).to eq([:TEST, 'WOOT']) + + end + end +end diff --git a/spec/functions/sqlserver_validate_instance_name_spec.rb b/spec/functions/sqlserver_validate_instance_name_spec.rb index 5e180f03..ab394fd3 100644 --- a/spec/functions/sqlserver_validate_instance_name_spec.rb +++ b/spec/functions/sqlserver_validate_instance_name_spec.rb @@ -2,11 +2,17 @@ describe 'sqlserver_validate_instance_name function' do let(:scope) { PuppetlabsSpec::PuppetInternals.scope } + it 'should exist' do expect(Puppet::Parser::Functions.function("sqlserver_validate_instance_name")).to eq("function_sqlserver_validate_instance_name") end + it 'should fail with over 16 characters' do expect { scope.function_sqlserver_validate_instance_name('ABCDEFGHIJKLMNOPQRSTUVWXYZ') }.to raise_error end + it 'should fail empty string' do + expect { scope.function_sqlserver_validate_instance_name('') }.to raise_error + end + end diff --git a/templates/create/role.sql.erb b/templates/create/role.sql.erb new file mode 100644 index 00000000..57441b2a --- /dev/null +++ b/templates/create/role.sql.erb @@ -0,0 +1,10 @@ +USE [<%= @database %>]; +BEGIN + <%= scope.function_template(['sqlserver/snippets/role/exists.sql.erb']) %> + CREATE <% if @type == 'SERVER' %>SERVER <% end %>ROLE [<%= @role %>]<% if @authorization %> AUTHORIZATION [<%= @authorization %>]<% end %>; + <% if @authorization %> + <%= scope.function_template(['sqlserver/snippets/role/owner_check.sql.erb']) %> + ALTER AUTHORIZATION ON <% if @type =='SERVER' %>SERVER <% end %>ROLE::[<%= @role %>] TO [<%= @authorization %>]; + <% end %> +END +<%= scope.function_template(['sqlserver/query/role_exists.sql.erb']) %> diff --git a/templates/create/role/permissions.sql.erb b/templates/create/role/permissions.sql.erb new file mode 100644 index 00000000..42637064 --- /dev/null +++ b/templates/create/role/permissions.sql.erb @@ -0,0 +1,15 @@ +USE [<%= @database %>]; +<%= scope.function_template(['sqlserver/snippets/role/declare_and_set_variables.sql.erb']) -%> + + <%- @_upermissions.each do |permission| + permission.upcase! + -%> + SET @permission = '<%= permission %>'; + <% if @with_grant_option == false %> + IF 'GRANT_WITH_GRANT_OPTION' = <%= scope.function_template(['sqlserver/snippets/principal/permission/get_perm_state.sql.erb']) -%> + BEGIN + REVOKE GRANT OPTION FOR <%= permission %> TO [<%= @role %>] CASCADE; + END + <% end -%> + <%= @_state %> <%= permission %> TO [<%= @role %>]<% if @with_grant_option == true %> WITH GRANT OPTION<% end %>; + <% end %> diff --git a/templates/delete/role.sql.erb b/templates/delete/role.sql.erb new file mode 100644 index 00000000..925fe6d3 --- /dev/null +++ b/templates/delete/role.sql.erb @@ -0,0 +1,5 @@ +USE [<%= @database %>]; +BEGIN + DROP <% if @type == 'SERVER' %>SERVER <% end %>ROLE [<%= @role %>]; +END +<%= scope.function_template(['sqlserver/query/role_exists.sql.erb']) %> diff --git a/templates/query/role/permission_exists.sql.erb b/templates/query/role/permission_exists.sql.erb new file mode 100644 index 00000000..cd1feb22 --- /dev/null +++ b/templates/query/role/permission_exists.sql.erb @@ -0,0 +1,8 @@ +USE [<%= @database %>]; +<%= scope.function_template(['sqlserver/snippets/role/declare_and_set_variables.sql.erb']) -%> + +<% @permissions.each do |permission| + permission.upcase! -%> +SET @permission = '<%= permission %>'; +<%= scope.function_template(['sqlserver/snippets/principal/permission/exists.sql.erb']) -%> +<% end -%> diff --git a/templates/query/role_exists.sql.erb b/templates/query/role_exists.sql.erb new file mode 100644 index 00000000..61ed61a0 --- /dev/null +++ b/templates/query/role_exists.sql.erb @@ -0,0 +1,7 @@ +USE [<%= @database %>]; +<%= scope.function_template(['sqlserver/snippets/role/exists.sql.erb']) %> + THROW 51000, 'The <%= @type %> ROLE [<%= @role %>] does <% if @ensure == 'present' %>not<% end %> exist', 10 +<% if @ensure == 'present' && @authorization -%> + <%= scope.function_template(['sqlserver/snippets/role/owner_check.sql.erb']) %> + THROW 51000, 'The <%= @type %> ROLE [<%= @role %>] does not have the correct owner of [<%= @authorization %>]', 10 +<% end -%> diff --git a/templates/snippets/principal/permission/exists.sql.erb b/templates/snippets/principal/permission/exists.sql.erb new file mode 100644 index 00000000..6d609a4e --- /dev/null +++ b/templates/snippets/principal/permission/exists.sql.erb @@ -0,0 +1,4 @@ +SET @perm_state = <%= scope.function_template(['sqlserver/snippets/principal/permission/get_perm_state.sql.erb']) -%>; +SET @error_msg = 'EXPECTED [' + @princ_name + '] to have permission [' + @permission + '] with ' + @state_desc + ' but got ' + @perm_state; +IF @perm_state != @state_desc + THROW 51000, @error_msg, 10; diff --git a/templates/snippets/principal/permission/get_perm_state.sql.erb b/templates/snippets/principal/permission/get_perm_state.sql.erb new file mode 100644 index 00000000..73d0ec30 --- /dev/null +++ b/templates/snippets/principal/permission/get_perm_state.sql.erb @@ -0,0 +1,6 @@ +ISNULL( + (SELECT state_desc FROM sys.<%= @type.downcase %>_permissions prem + JOIN sys.<%= @type.downcase %>_principals r ON r.principal_id = prem.grantee_principal_id + WHERE r.name = @princ_name AND r.type_desc = @princ_type + AND prem.permission_name = @permission), + 'REVOKE') diff --git a/templates/snippets/role/declare_and_set_variables.sql.erb b/templates/snippets/role/declare_and_set_variables.sql.erb new file mode 100644 index 00000000..b92fad95 --- /dev/null +++ b/templates/snippets/role/declare_and_set_variables.sql.erb @@ -0,0 +1,11 @@ +DECLARE + @perm_state varchar(250), + @error_msg varchar(250), + @permission varchar(250), + @princ_name varchar(50), + @princ_type varchar(50), + @state_desc varchar(50); + +SET @princ_type = '<%= @type.upcase %>_ROLE'; +SET @princ_name = '<%= @role %>'; +SET @state_desc = '<% if @with_grant_option == true %>GRANT_WITH_GRANT_OPTION<% else %><%= @_state %><% end %>'; diff --git a/templates/snippets/role/exists.sql.erb b/templates/snippets/role/exists.sql.erb new file mode 100644 index 00000000..e3c7a18b --- /dev/null +++ b/templates/snippets/role/exists.sql.erb @@ -0,0 +1,3 @@ +IF <% if @ensure == 'present' %>NOT <% end %>EXISTS( + SELECT name FROM sys.<%= @type.downcase %>_principals WHERE type_desc = '<%= @type %>_ROLE' AND name = '<%= @role %>' +) diff --git a/templates/snippets/role/owner_check.sql.erb b/templates/snippets/role/owner_check.sql.erb new file mode 100644 index 00000000..1bc9a194 --- /dev/null +++ b/templates/snippets/role/owner_check.sql.erb @@ -0,0 +1,4 @@ +IF NOT EXISTS( + SELECT p.name,r.name FROM sys.<%= @type.downcase %>_principals r + JOIN sys.<%= @type.downcase %>_principals p ON p.principal_id = r.owning_principal_id + WHERE r.type_desc = '<%= @type.upcase %>_ROLE' AND p.name = '<%= @authorization %>' AND r.name = '<%= @role %>')