Skip to content

FM-2287 Add Role Permissions ability #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions lib/puppet/parser/functions/sqlserver_upcase.rb
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
101 changes: 101 additions & 0 deletions manifests/role.pp
Original file line number Diff line number Diff line change
@@ -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,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you list the variables used in the templates in a comment?


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'],
}
}
}
}
74 changes: 74 additions & 0 deletions manifests/role/permissions.pp
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is before validate_array($permissions), what happens if $permissions is a string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we validate later if it is an array as well, so if it was an string of x numbers we would still fail after that as it would fail validate_array

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still upcase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A simple string, stdlib should work fine

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still upcase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A simple string, stdlib should work fine


$_grant_option = $with_grant_option ? {
true => '-WITH_GRANT_OPTION',
false => '',
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail with future parser/strict variables

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, will fix.

##
# 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'),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you list the variables used in the templates in a comment?

}

}
143 changes: 143 additions & 0 deletions spec/defines/role/permissions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
require 'spec_helper'

RSpec.describe 'sqlserver::role::permissions' do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this RSpec.describe? I don't think I've seen that before, usually see something along the lines of describe 'concat', :type => :define do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RSpec.describe is rspec 3 syntax. But only for top level items

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
Loading