From b80ce9ed603aaa6ddfab68020a250d316218e20b Mon Sep 17 00:00:00 2001 From: Travis Fields Date: Wed, 11 Feb 2015 11:52:33 -0800 Subject: [PATCH] FM-1898 Add sqlserver::user::permission with Grant, Revoke and Deny --- .fixtures.yml | 3 +- .geppetto-rc.json | 9 -- Gemfile | 4 +- manifests/user/permission.pp | 56 +++++++ spec/defines/config_spec.rb | 18 +++ spec/defines/database_spec.rb | 7 +- spec/defines/manifest_shared_examples.rb | 12 +- spec/defines/user/permission_spec.rb | 140 ++++++++++++++++++ spec/spec_helper.rb | 12 +- templates/create/user_permission.sql.erb | 7 + .../query/user_permission_exists.sql.erb | 11 ++ 11 files changed, 259 insertions(+), 20 deletions(-) delete mode 100644 .geppetto-rc.json create mode 100644 manifests/user/permission.pp create mode 100644 spec/defines/config_spec.rb create mode 100644 spec/defines/user/permission_spec.rb create mode 100644 templates/create/user_permission.sql.erb create mode 100644 templates/query/user_permission_exists.sql.erb diff --git a/.fixtures.yml b/.fixtures.yml index 722a6871..0d87ecea 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -1,5 +1,6 @@ fixtures: forge_modules: stdlib: "puppetlabs/stdlib" + acl: "puppetlabs/acl" symlinks: - "sqlserver": "#{source_dir}" + sqlserver: "#{source_dir}" diff --git a/.geppetto-rc.json b/.geppetto-rc.json deleted file mode 100644 index 7df23298..00000000 --- a/.geppetto-rc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "excludes": [ - "**/contrib/**", - "**/examples/**", - "**/tests/**", - "**/spec/**", - "**/pkg/**" - ] -} diff --git a/Gemfile b/Gemfile index 6f5526c9..fe3741cf 100644 --- a/Gemfile +++ b/Gemfile @@ -4,11 +4,11 @@ group :development, :test do gem 'nokogiri' gem 'mime-types', '<2.0', :require => false gem 'rake', :require => false - gem 'rspec-puppet', '~>1.0', :require => false + gem 'rspec-puppet', '~>2.0', :require => false gem 'puppetlabs_spec_helper', :require => false gem 'puppet-lint', :require => false gem 'simplecov', :require => false - gem 'rspec', '~> 2.14.0', :require => false + gem 'rspec', :require => false gem 'beaker-rspec', :require => false gem 'yard', :require => false gem 'pry', :require => false diff --git a/manifests/user/permission.pp b/manifests/user/permission.pp new file mode 100644 index 00000000..40f37130 --- /dev/null +++ b/manifests/user/permission.pp @@ -0,0 +1,56 @@ +## +# == Define Resource Type: sqlserver::user::permission# +# +# === Requirement/Dependencies: +# +# Requires defined type {sqlserver::config} in order to execute against the SQL Server instance +# +# +# === Parameters +# [user] +# The username for which the permission will be manage. +# +# [database] +# The databaser you would like the permission managed on. +# +# [permission] +# The permission you would like managed. i.e. 'SELECT', 'INSERT', 'UPDATE', 'DELETE' +# +# [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. +# +# [instance] +# The name of the instance where the user and database exists. Defaults to 'MSSQLSERVER' +# +## +define sqlserver::user::permission ( + $user, + $database, + $permission = $title, + $state = 'GRANT', + $instance = 'MSSQLSERVER', +){ + sqlserver_validate_instance_name($instance) + +## Validate Permissions + $_permission = upcase($permission) + sqlserver_validate_range($_permission, 4, 128, 'Permission must be between 4 and 128 characters') + validate_re($_permission, '^([A-Z]|\s)+$','Permissions must be alphabetic only') + +## Validate state + $_state = upcase($state) + validate_re($_state,'^(GRANT|REVOKE|DENY)$',"State can only be of 'GRANT', 'REVOKE' or 'DENY' you passed ${state}") + + sqlserver_validate_range($database, 1, 128, 'Database must be between 1 and 128 characters') + + sqlserver_validate_range($user, 1, 128, 'User must be between 1 and 128 characters') + + sqlserver_tsql{ + "user-permissions-${instance}-${database}-${user}-${$_state}-${_permission}": + instance => $instance, + command => template("sqlserver/create/user_permission.sql.erb"), + onlyif => template('sqlserver/query/user_permission_exists.sql.erb'), + require => Sqlserver::Config[$instance], + } + +} diff --git a/spec/defines/config_spec.rb b/spec/defines/config_spec.rb new file mode 100644 index 00000000..3c2c7017 --- /dev/null +++ b/spec/defines/config_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' +require File.expand_path(File.join(File.dirname(__FILE__), 'manifest_shared_examples.rb')) + +RSpec.describe 'sqlserver::config', :type => :define do + let(:title) { 'MSSQLSERVER' } + let(:params) { { + :instance_name => 'MSSQLSERVER', + :admin_user => 'sa', + :admin_pass => 'Pupp3t1@', + } } + let(:facts) { {:osfamily => 'windows', :platform => :windows} } + describe 'compile' do + it { + should contain_file('C:/Program Files/Microsoft SQL Server/.puppet/.MSSQLSERVER.cfg') + should contain_file('C:/Program Files/Microsoft SQL Server/.puppet') + } + end +end diff --git a/spec/defines/database_spec.rb b/spec/defines/database_spec.rb index e4f26342..c30edf68 100644 --- a/spec/defines/database_spec.rb +++ b/spec/defines/database_spec.rb @@ -12,7 +12,12 @@ end describe 'Minimal Params' do - it_behaves_like 'sqlserver_tsql command' + let(:pre_condition) { <<-EOF + define sqlserver::config{} + sqlserver::config {'MSSQLSERVER': } + EOF + } + it_behaves_like 'compile' end describe 'Providing log filespec it should compile with valid log on params and' do diff --git a/spec/defines/manifest_shared_examples.rb b/spec/defines/manifest_shared_examples.rb index 1bc6fb5d..29974b0d 100644 --- a/spec/defines/manifest_shared_examples.rb +++ b/spec/defines/manifest_shared_examples.rb @@ -42,6 +42,7 @@ def convert_to_regexp(str) } end + shared_examples 'sqlserver_tsql without_command' do it { params.merge!(additional_params) @@ -50,16 +51,25 @@ def convert_to_regexp(str) end } end + shared_examples 'compile' do it { params.merge!(additional_params) should compile } end + shared_examples 'validation error' do it { params.merge!(additional_params) - expect { should compile }.to raise_error(error_class, convert_to_regexp(raise_error_check)) + expect { should contain_sqlserver_tsql(sqlserver_tsql_title) }.to raise_error(error_class, convert_to_regexp(raise_error_check)) } end end + +def random_string_of_size(size, include_numeric = true) + pool = [('a'..'z'), ('A'..'Z')] + pool << (0..9) if include_numeric + o = pool.map { |i| i.to_a }.flatten + (0...size).map { o[rand(o.length)] }.join +end diff --git a/spec/defines/user/permission_spec.rb b/spec/defines/user/permission_spec.rb new file mode 100644 index 00000000..7579185e --- /dev/null +++ b/spec/defines/user/permission_spec.rb @@ -0,0 +1,140 @@ +require 'spec_helper' +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'manifest_shared_examples.rb')) + +describe 'sqlserver::user::permission' do + let(:facts) { {:osfamily => 'windows'} } + context 'validation errors' do + include_context 'manifests' do + let(:title) { 'myTitle' } + let(:sqlserver_tsql_title) { 'user-permissions-MSSQLSERVER-loggingDb-loggingUser-GRANT-SELECT' } + end + context 'user =>' do + let(:params) { { + :permission => 'SELECT', + :database => 'loggingDb', + } } + let(:raise_error_check) { 'User must be between 1 and 128 characters' } + describe 'missing' do + let(:raise_error_check) { 'Must pass user to Sqlserver::User::Permission[myTitle]' } + it_behaves_like 'validation error' + end + describe 'empty' do + let(:additional_params) { {:user => ''} } + it_behaves_like 'validation error' + end + describe 'over limit' do + let(:additional_params) { {:user => random_string_of_size(129)} } + end + end + context 'permission' do + let(:params) { { + :user => 'loggingUser', + :database => 'loggingDb', + } } + let(:raise_error_check) { 'Permission must be between 4 and 128 characters' } + describe 'empty' do + let(:additional_params) { {:permission => ''} } + it_behaves_like 'validation error' + end + describe 'under limit' do + let(:additional_params) { {:permission => random_string_of_size(3, false)} } + it_behaves_like 'validation error' + end + describe 'over limit' do + let(:additional_params) { {:permission => random_string_of_size(129, false)} } + it_behaves_like 'validation error' + end + end + context 'state =>' do + let(:params) { { + :permission => 'SELECT', + :database => 'loggingDb', + :user => 'loggingUser' + } } + describe 'invalid' do + let(:additional_params) { {:state => 'invalide'} } + let(:raise_error_check) { "State can only be of 'GRANT', 'REVOKE' or 'DENY' you passed invalide" } + it_behaves_like 'validation error' + end + end + end + context 'successfully' do + include_context 'manifests' do + let(:title) { 'myTitle' } + let(:sqlserver_tsql_title) { 'user-permissions-MSSQLSERVER-loggingDb-loggingUser-GRANT-SELECT' } + let(:params) { { + :user => 'loggingUser', + :permission => 'SELECT', + :database => 'loggingDb', + } } + end + %w(revoke grant deny).each do |state| + context "state => '#{state}'" do + let(:sqlserver_tsql_title) { "user-permissions-MSSQLSERVER-loggingDb-loggingUser-#{state.upcase}-SELECT" } + let(:should_contain_command) { ["#{state.upcase} SELECT TO [loggingUser];", 'USE [loggingDb];'] } + describe "lowercase #{state}" do + let(:additional_params) { {:state => state} } + it_behaves_like 'sqlserver_tsql command' + end + state.capitalize! + describe "capitalized #{state}" do + let(:additional_params) { {:state => state} } + it_behaves_like 'sqlserver_tsql command' + end + end + end + + context 'permission' do + describe 'upper limit' do + permission =random_string_of_size(128, false) + let(:additional_params) { {:permission => permission} } + let(:sqlserver_tsql_title) { "user-permissions-MSSQLSERVER-loggingDb-loggingUser-GRANT-#{permission.upcase}" } + let(:should_contain_command) { ['USE [loggingDb];'] } + it_behaves_like 'sqlserver_tsql command' + end + describe 'alter' do + let(:additional_params) { {:permission => 'ALTER'} } + let(:should_contain_command) { ['USE [loggingDb];', 'GRANT ALTER TO [loggingUser];'] } + let(:sqlserver_tsql_title) { "user-permissions-MSSQLSERVER-loggingDb-loggingUser-GRANT-ALTER" } + it_behaves_like 'sqlserver_tsql command' + end + end + + describe 'Minimal Params' do + let(:pre_condition) { <<-EOF + define sqlserver::config{} + sqlserver::config {'MSSQLSERVER': } + EOF + } + let(:should_contain_command) { ['USE [loggingDb];'] } + it_behaves_like 'compile' + end + + end + + context 'command syntax' do + include_context 'manifests' do + let(:title) { 'myTitle' } + let(:sqlserver_tsql_title) { 'user-permissions-MSSQLSERVER-loggingDb-loggingUser-GRANT-SELECT' } + let(:params) { { + :user => 'loggingUser', + :permission => 'SELECT', + :database => 'loggingDb', + } } + describe '' do + let(:should_contain_command) { [ + 'USE [loggingDb];', + 'GRANT SELECT TO [loggingUser];', + /DECLARE @perm_state varchar\(250\)/, + /SET @perm_state = ISNULL\(\n\s+\(SELECT perm.state_desc FROM sys\.database_principals princ\n\s+JOIN sys\./, + /JOIN sys\.database_permissions perm ON perm\.grantee_principal_id = princ.principal_id\n\s+WHERE/, + /WHERE princ\.type in \('U','S','G'\) AND name = 'loggingUser' AND permission_name = 'SELECT' \),\n\s+'REVOKE'\);/, + /DECLARE @error_msg varchar\(250\);\nSET @error_msg = 'EXPECTED user \[loggingUser\] to have permission \[SELECT\] with GRANT but got ' \+ @perm_state;/, + /IF @perm_state != 'GRANT'\n\s+THROW 51000, @error_msg, 10/ + ] } + it_behaves_like 'sqlserver_tsql command' + end + end + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ccf6c531..9e1a07e8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,12 +1,12 @@ require 'simplecov' -require 'rspec' - +require 'rspec-puppet' require 'puppetlabs_spec_helper/module_spec_helper' -dir = File.expand_path(File.dirname(__FILE__)) -$LOAD_PATH.unshift File.join(dir, 'lib') -module PuppetSpec - FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR) +fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures')) + +RSpec.configure do |c| + c.module_path = File.join(fixture_path, 'modules') + c.manifest_dir = File.join(fixture_path, 'manifests') end SimpleCov.start do diff --git a/templates/create/user_permission.sql.erb b/templates/create/user_permission.sql.erb new file mode 100644 index 00000000..d4688b7f --- /dev/null +++ b/templates/create/user_permission.sql.erb @@ -0,0 +1,7 @@ +BEGIN + USE [<%= @database %>]; + <%= @_state %> <%= @_permission %> TO [<%= @user %>]; +END +BEGIN + <%= scope.function_template(['sqlserver/query/user_permission_exists.sql.erb']) %> +END diff --git a/templates/query/user_permission_exists.sql.erb b/templates/query/user_permission_exists.sql.erb new file mode 100644 index 00000000..40ecfa87 --- /dev/null +++ b/templates/query/user_permission_exists.sql.erb @@ -0,0 +1,11 @@ +USE [<%= @database %>]; +DECLARE @perm_state varchar(250); +SET @perm_state = ISNULL( + (SELECT perm.state_desc FROM sys.database_principals princ + JOIN sys.database_permissions perm ON perm.grantee_principal_id = princ.principal_id + WHERE princ.type in ('U','S','G') AND name = '<%= @user %>' AND permission_name = '<%= @_permission %>' ), + 'REVOKE'); +DECLARE @error_msg varchar(250); +SET @error_msg = 'EXPECTED user [<%= @user %>] to have permission [<%= @_permission %>] with <%= @_state %> but got ' + @perm_state; +IF @perm_state != '<%= @_state %>' + THROW 51000, @error_msg, 10