Skip to content

Commit 3e921d1

Browse files
committed
Merge pull request #76 from cyberious/RolePermissions
FM-2287 Add Role Permissions ability
2 parents 1f1503c + ff9ad36 commit 3e921d1

18 files changed

+617
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module Puppet::Parser::Functions
2+
newfunction(:sqlserver_upcase, :type => :rvalue, :arity => 1) do |arguments|
3+
4+
raise(Puppet::ParseError, "upcase(): Wrong number of arguments " +
5+
"given (#{arguments.size} for 1)") if arguments.size != 1
6+
7+
value = arguments[0]
8+
9+
unless value.is_a?(Array) || value.is_a?(Hash) || value.respond_to?(:upcase)
10+
raise(Puppet::ParseError, 'upcase(): Requires an ' +
11+
'array, hash or object that responds to upcase in order to work')
12+
end
13+
14+
if value.is_a?(Array)
15+
# Numbers in Puppet are often string-encoded which is troublesome ...
16+
result = value.collect { |i| function_sqlserver_upcase([i]) }
17+
elsif value.is_a?(Hash)
18+
result = {}
19+
value.each_pair do |k, v|
20+
result[function_sqlserver_upcase([k])] = function_sqlserver_upcase([v])
21+
end
22+
else
23+
result = value.upcase
24+
end
25+
26+
return result
27+
end
28+
end

lib/puppet/parser/functions/sqlserver_validate_instance_name.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ module Puppet::Parser::Functions
2020
end
2121
value = args[0]
2222
errors = []
23+
if value.length < 1 || value.empty?
24+
errors << "Instance name must be between 1 to 16 characters"
25+
end
2326
if value.length > 16
2427
errors << "Instance name can not be larger than 16 characters, you provided #{value}"
2528
end

manifests/role.pp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
##
2+
# == Define Resource Type: sqlserver::role::permissions
3+
#
4+
#
5+
# === Requirement/Dependencies:
6+
#
7+
# Requires defined type {sqlserver::config} in order to execute against the SQL Server instance
8+
#
9+
#
10+
# === Parameters
11+
#
12+
# [ensure]
13+
# Whether the role should be absent or present
14+
#
15+
# [role]
16+
# The name of the role for which the permissions will be manage.
17+
#
18+
# [instance]
19+
# The name of the instance where the role and database exists. Defaults to 'MSSQLSERVER'
20+
#
21+
# [authorization]
22+
# The database principal that should own the role
23+
#
24+
# [type]
25+
# Whether the Role is `SERVER` or `DATABASE`
26+
#
27+
# [database]
28+
# The name of the database the role exists on when specifying `type => 'DATABASE'`. Defaults to 'master'
29+
#
30+
# [permissions]
31+
# 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'] }
32+
#
33+
##
34+
define sqlserver::role(
35+
$ensure = present,
36+
$role = $title,
37+
$instance = 'MSSQLSERVER',
38+
$authorization = undef,
39+
$type = 'SERVER',
40+
$database = 'master',
41+
$permissions = { },
42+
){
43+
sqlserver_validate_instance_name($instance)
44+
sqlserver_validate_range($role, 1, 128, 'Role names must be between 1 and 128 characters')
45+
46+
validate_re($type, ['^SERVER$','^DATABASE$'], "Type must be either 'SERVER' or 'DATABASE', provided '${type}'")
47+
48+
sqlserver_validate_range($database, 1, 128, 'Database name must be between 1 and 128 characters')
49+
if $type == 'SERVER' and $database != 'master' {
50+
fail('Can not specify a database other than master when managing SERVER ROLES')
51+
}
52+
53+
$_create_delete = $ensure ? {
54+
present => 'create',
55+
absent => 'delete',
56+
}
57+
58+
sqlserver_tsql{ "role-${role}-${instance}":
59+
command => template("sqlserver/${_create_delete}/role.sql.erb"),
60+
onlyif => template('sqlserver/query/role_exists.sql.erb'),
61+
instance => $instance,
62+
}
63+
64+
if $ensure == present {
65+
validate_hash($permissions)
66+
$_upermissions = sqlserver_upcase($permissions)
67+
68+
Sqlserver::Role::Permissions{
69+
role => $role,
70+
instance => $instance,
71+
database => $database,
72+
type => $type,
73+
require => Sqlserver_tsql["role-${role}-${instance}"]
74+
}
75+
if has_key($_upermissions, 'GRANT') and is_array($_upermissions['GRANT']) {
76+
sqlserver::role::permissions{ "Sqlserver::Role[${title}]-GRANT-${role}":
77+
state => 'GRANT',
78+
permissions => $_upermissions['GRANT'],
79+
}
80+
}
81+
if has_key($_upermissions, 'DENY') and is_array($_upermissions['DENY']) {
82+
sqlserver::role::permissions{ "Sqlserver::Role[${title}]-DENY-${role}":
83+
state => 'DENY',
84+
permissions => $_upermissions['DENY'],
85+
}
86+
}
87+
if has_key($_upermissions, 'REVOKE') and is_array($_upermissions['REVOKE']) {
88+
sqlserver::role::permissions{ "Sqlserver::Role[${title}]-REVOKE-${role}":
89+
state => 'REVOKE',
90+
permissions => $_upermissions['REVOKE'],
91+
}
92+
}
93+
if has_key($_upermissions, 'GRANT_WITH_OPTION') and is_array($_upermissions['GRANT_WITH_OPTION']) {
94+
sqlserver::role::permissions{ "Sqlserver::Role[${title}]-GRANT-WITH_GRANT_OPTION-${role}":
95+
state => 'GRANT',
96+
with_grant_option => true,
97+
permissions => $_upermissions['GRANT_WITH_OPTION'],
98+
}
99+
}
100+
}
101+
}

manifests/role/permissions.pp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
##
2+
# == Define Resource Type: sqlserver::role::permissions
3+
#
4+
#
5+
# === Requirement/Dependencies:
6+
#
7+
# Requires defined type {sqlserver::config} in order to execute against the SQL Server instance
8+
#
9+
#
10+
# === Parameters
11+
# [role]
12+
# The name of the role for which the permissions will be manage.
13+
#
14+
# [permissions]
15+
# An array of permissions you want manged for the given role
16+
#
17+
# [state]
18+
# 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.
19+
#
20+
# [with_grant_option]
21+
# Whether to give the role the option to grant this permission to other principal objects, accepts true or false, defaults to false
22+
#
23+
# [type]
24+
# Whether the Role is `SERVER` or `DATABASE`
25+
#
26+
# [database]
27+
# The name of the database the role exists on when specifying `type => 'DATABASE'`. Defaults to 'master'
28+
#
29+
# [instance]
30+
# The name of the instance where the role and database exists. Defaults to 'MSSQLSERVER'
31+
#
32+
##
33+
define sqlserver::role::permissions (
34+
$role,
35+
$permissions,
36+
$state = 'GRANT',
37+
$with_grant_option = false,
38+
$type = 'SERVER',
39+
$database = 'master',
40+
$instance = 'MSSQLSERVER',
41+
){
42+
validate_array($permissions)
43+
if size($permissions) < 1 {
44+
warning("Received an empty set of permissions for ${title}, no further action will be taken")
45+
} else{
46+
sqlserver_validate_instance_name($instance)
47+
#Validate state
48+
$_state = upcase($state)
49+
validate_re($_state,'^(GRANT|REVOKE|DENY)$',"State can only be of 'GRANT', 'REVOKE' or 'DENY' you passed ${state}")
50+
validate_bool($with_grant_option)
51+
52+
#Validate role
53+
sqlserver_validate_range($role, 1, 128, 'Role names must be between 1 and 128 characters')
54+
55+
#Validate permissions
56+
sqlserver_validate_range($permissions, 4, 128, 'Permissions must be between 4 and 128 characters')
57+
58+
$_upermissions = upcase($permissions)
59+
60+
$_grant_option = $with_grant_option ? {
61+
true => '-WITH_GRANT_OPTION',
62+
false => '',
63+
}
64+
##
65+
# Parameters required in template are _state, role, _upermissions, database, type, with_grant_option
66+
##
67+
sqlserver_tsql{ "role-permissions-${role}-${_state}${_grant_option}-${instance}":
68+
instance => $instance,
69+
command => template('sqlserver/create/role/permissions.sql.erb'),
70+
onlyif => template('sqlserver/query/role/permission_exists.sql.erb'),
71+
}
72+
}
73+
74+
}

spec/defines/role/permissions_spec.rb

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe 'sqlserver::role::permissions' do
4+
include_context 'manifests' do
5+
let(:title) { 'myTitle' }
6+
let(:sqlserver_tsql_title) { 'role-permissions-myCustomRole-GRANT-MSSQLSERVER' }
7+
let(:params) { {
8+
:role => 'myCustomRole',
9+
:permissions => %w(INSERT UPDATE DELETE SELECT),
10+
} }
11+
end
12+
13+
context 'sql variables' do
14+
let(:params) { {
15+
:role => 'myCustomRole',
16+
:permissions => %w(INSERT UPDATE DELETE SELECT),
17+
} }
18+
declare_variables = [
19+
"DECLARE
20+
@perm_state varchar(250),
21+
@error_msg varchar(250),
22+
@permission varchar(250),
23+
@princ_name varchar(50),
24+
@princ_type varchar(50),
25+
@state_desc varchar(50);",
26+
"SET @princ_type = 'SERVER_ROLE';",
27+
"SET @princ_name = 'myCustomRole';",
28+
"SET @state_desc = 'GRANT';"]
29+
let(:should_contain_command) { declare_variables }
30+
let(:should_contain_onlyif) { declare_variables }
31+
it_behaves_like 'sqlserver_tsql command'
32+
it_behaves_like 'sqlserver_tsql onlyif'
33+
end
34+
35+
context 'type =>' do
36+
shared_examples 'GRANT Permissions' do |type|
37+
base_commands = [
38+
"SET @princ_type = '#{type.upcase}_ROLE';",
39+
"ISNULL(
40+
(SELECT state_desc FROM sys.#{type.downcase}_permissions prem
41+
JOIN sys.#{type.downcase}_principals r ON r.principal_id = prem.grantee_principal_id
42+
WHERE r.name = @princ_name AND r.type_desc = @princ_type
43+
AND prem.permission_name = @permission),
44+
'REVOKE')",
45+
"SET @permission = 'INSERT';",
46+
"SET @permission = 'UPDATE';",
47+
"SET @permission = 'DELETE';",
48+
"SET @permission = 'SELECT';",
49+
]
50+
should_commands = [
51+
"GRANT INSERT TO [myCustomRole];",
52+
"GRANT UPDATE TO [myCustomRole];",
53+
"GRANT DELETE TO [myCustomRole];",
54+
"GRANT SELECT TO [myCustomRole];"
55+
]
56+
let(:should_contain_command) { base_commands + should_commands }
57+
let(:should_contain_onlyif) { base_commands }
58+
it_behaves_like 'sqlserver_tsql command'
59+
it_behaves_like 'sqlserver_tsql onlyif'
60+
end
61+
62+
describe 'DATABASE' do
63+
let(:additional_params) { {
64+
:type => 'DATABASE',
65+
} }
66+
it_behaves_like 'GRANT Permissions', 'database'
67+
end
68+
69+
describe 'SERVER' do
70+
let(:additional_params) { {
71+
:type => 'SERVER',
72+
} }
73+
it_behaves_like 'GRANT Permissions', 'server'
74+
end
75+
end
76+
77+
context 'permissions =>' do
78+
describe '[INSERT UPDATE DELETE SELECT]' do
79+
declare_variables = [
80+
"SET @permission = 'INSERT';",
81+
"SET @permission = 'UPDATE';",
82+
"SET @permission = 'DELETE';",
83+
"SET @permission = 'SELECT';",
84+
]
85+
let(:should_contain_command) { declare_variables +
86+
[
87+
"GRANT INSERT TO [myCustomRole];",
88+
"GRANT UPDATE TO [myCustomRole];",
89+
"GRANT DELETE TO [myCustomRole];",
90+
"GRANT SELECT TO [myCustomRole];"
91+
] }
92+
let(:should_contain_onlyif) { declare_variables }
93+
it_behaves_like 'sqlserver_tsql command'
94+
it_behaves_like 'sqlserver_tsql onlyif'
95+
end
96+
describe '[]' do
97+
let(:params) { {
98+
:role => 'myCustomRole',
99+
:permissions => []
100+
} }
101+
it {
102+
should compile
103+
should_not contain_sqlserver_tsql(sqlserver_tsql_title)
104+
}
105+
end
106+
end
107+
108+
context 'database =>' do
109+
describe 'default' do
110+
let(:should_contain_command) { ['USE [master];'] }
111+
let(:should_contain_onlyif) { ['USE [master];'] }
112+
it_behaves_like 'sqlserver_tsql command'
113+
it_behaves_like 'sqlserver_tsql onlyif'
114+
end
115+
describe 'customDatabase' do
116+
let(:additional_params) { {:database => 'customDatabase'} }
117+
let(:should_contain_command) { ['USE [customDatabase];'] }
118+
it_behaves_like 'sqlserver_tsql command'
119+
let(:should_contain_onlyif) { ['USE [customDatabase];'] }
120+
it_behaves_like 'sqlserver_tsql onlyif'
121+
let(:should_contain_without_command) { ['USE [master];'] }
122+
it_behaves_like 'sqlserver_tsql without_command'
123+
let(:should_contain_without_onlyif) { ['USE [master];'] }
124+
it_behaves_like 'sqlserver_tsql without_onlyif'
125+
end
126+
end
127+
128+
context 'instance =>' do
129+
['MSSQLSERVER', 'MYINSTANCE'].each do |instance|
130+
describe "should contain #{instance} for sqlserver_tsql" do
131+
let(:params) { {
132+
:role => 'myCustomRole',
133+
:permissions => %w(INSERT UPDATE DELETE SELECT),
134+
:instance => instance
135+
} }
136+
it {
137+
should contain_sqlserver_tsql("role-permissions-myCustomRole-GRANT-#{instance}").with_instance(instance)
138+
}
139+
end
140+
end
141+
end
142+
143+
end

0 commit comments

Comments
 (0)