Skip to content

Commit a3c800c

Browse files
committed
Merge pull request #77 from cyberious/RoleMembers
FM-2288 Role members
2 parents 3e921d1 + 02645b1 commit a3c800c

File tree

6 files changed

+212
-31
lines changed

6 files changed

+212
-31
lines changed

manifests/role.pp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
# [permissions]
3131
# 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'] }
3232
#
33+
# [members]
34+
# An array of users/logins that should be a member of the role
35+
#
36+
# [members_purge]
37+
# Whether we should purge any members not listed in the members parameter. Default: false
3338
##
3439
define sqlserver::role(
3540
$ensure = present,
@@ -39,6 +44,8 @@
3944
$type = 'SERVER',
4045
$database = 'master',
4146
$permissions = { },
47+
$members = [],
48+
$members_purge = false,
4249
){
4350
sqlserver_validate_instance_name($instance)
4451
sqlserver_validate_range($role, 1, 128, 'Role names must be between 1 and 128 characters')
@@ -97,5 +104,14 @@
97104
permissions => $_upermissions['GRANT_WITH_OPTION'],
98105
}
99106
}
107+
108+
validate_array($members)
109+
if size($members) > 0 or $members_purge == true {
110+
sqlserver_tsql{ "role-${role}-members":
111+
command => template('sqlserver/create/role/members.sql.erb'),
112+
onlyif => template('sqlserver/query/role/member_exists.sql.erb'),
113+
instance => $instance,
114+
}
115+
}
100116
}
101117
}

spec/defines/role_spec.rb

Lines changed: 132 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,38 @@
1010
context 'type =>' do
1111
describe 'invalid' do
1212
let(:additional_params) { {
13-
:type => 'invalid',
13+
:type => 'invalid',
1414
} }
1515
let(:raise_error_check) { "Type must be either 'SERVER' or 'DATABASE', provided 'invalid'" }
1616
it_behaves_like 'validation error'
1717
end
1818
describe 'SERVER' do
1919
let(:should_contain_command) { [
20-
'USE [master];',
21-
'CREATE SERVER ROLE [myCustomRole];',
22-
/IF NOT EXISTS\(\n\s+SELECT name FROM sys\.server_principals WHERE type_desc = 'SERVER_ROLE' AND name = 'myCustomRole'\n\)/,
23-
"THROW 51000, 'The SERVER ROLE [myCustomRole] does not exist', 10"
20+
'USE [master];',
21+
'CREATE SERVER ROLE [myCustomRole];',
22+
/IF NOT EXISTS\(\n\s+SELECT name FROM sys\.server_principals WHERE type_desc = 'SERVER_ROLE' AND name = 'myCustomRole'\n\)/,
23+
"THROW 51000, 'The SERVER ROLE [myCustomRole] does not exist', 10"
2424
] }
2525
let(:should_contain_onlyif) { [
26-
/IF NOT EXISTS\(\n\s+SELECT name FROM sys\.server_principals WHERE type_desc = 'SERVER_ROLE' AND name = 'myCustomRole'\n\)/,
27-
"THROW 51000, 'The SERVER ROLE [myCustomRole] does not exist', 10"
26+
/IF NOT EXISTS\(\n\s+SELECT name FROM sys\.server_principals WHERE type_desc = 'SERVER_ROLE' AND name = 'myCustomRole'\n\)/,
27+
"THROW 51000, 'The SERVER ROLE [myCustomRole] does not exist', 10"
2828
] }
2929
it_behaves_like 'sqlserver_tsql command'
3030
it_behaves_like 'sqlserver_tsql onlyif'
3131
end
3232
describe 'DATABASE' do
3333
let(:additional_params) { {
34-
'type' => 'DATABASE',
34+
'type' => 'DATABASE',
3535
} }
3636
let(:should_contain_command) { [
37-
'USE [master];',
38-
'CREATE ROLE [myCustomRole];',
39-
/IF NOT EXISTS\(\n\s+SELECT name FROM sys\.database_principals WHERE type_desc = 'DATABASE_ROLE' AND name = 'myCustomRole'\n\)/,
40-
"THROW 51000, 'The DATABASE ROLE [myCustomRole] does not exist', 10"
37+
'USE [master];',
38+
'CREATE ROLE [myCustomRole];',
39+
/IF NOT EXISTS\(\n\s+SELECT name FROM sys\.database_principals WHERE type_desc = 'DATABASE_ROLE' AND name = 'myCustomRole'\n\)/,
40+
"THROW 51000, 'The DATABASE ROLE [myCustomRole] does not exist', 10"
4141
] }
4242
let(:should_contain_onlyif) { [
43-
/IF NOT EXISTS\(\n\s+SELECT name FROM sys\.database_principals WHERE type_desc = 'DATABASE_ROLE' AND name = 'myCustomRole'\n\)/,
44-
"THROW 51000, 'The DATABASE ROLE [myCustomRole] does not exist', 10",
43+
/IF NOT EXISTS\(\n\s+SELECT name FROM sys\.database_principals WHERE type_desc = 'DATABASE_ROLE' AND name = 'myCustomRole'\n\)/,
44+
"THROW 51000, 'The DATABASE ROLE [myCustomRole] does not exist', 10",
4545
] }
4646

4747
it_behaves_like 'sqlserver_tsql command'
@@ -52,19 +52,19 @@
5252

5353
context 'database =>' do
5454
let(:additional_params) { {
55-
'database' => 'myCrazyDb',
55+
'database' => 'myCrazyDb',
5656
} }
5757
describe 'with server role type' do
5858
let(:raise_error_check) { 'Can not specify a database other than master when managing SERVER ROLES' }
5959
it_behaves_like 'validation error'
6060
end
6161
describe 'with database role type' do
6262
let(:additional_params) { {
63-
'database' => 'myCrazyDb',
64-
'type' => 'DATABASE',
63+
'database' => 'myCrazyDb',
64+
'type' => 'DATABASE',
6565
} }
6666
let(:should_contain_command) { [
67-
'USE [myCrazyDb];',
67+
'USE [myCrazyDb];',
6868
] }
6969
it_behaves_like 'sqlserver_tsql command'
7070
end
@@ -87,29 +87,29 @@
8787
context 'authorization =>' do
8888
describe 'undef' do
8989
let(:should_not_contain_command) { [
90-
/AUTHORIZATION/i,
91-
'ALTER AUTHORIZATION ON ',
90+
/AUTHORIZATION/i,
91+
'ALTER AUTHORIZATION ON ',
9292
] }
9393
it_behaves_like 'sqlserver_tsql without_command'
9494
end
9595
describe 'myUser' do
9696
let(:additional_params) { {
97-
:authorization => 'myUser',
97+
:authorization => 'myUser',
9898
} }
9999
let(:should_contain_command) { [
100-
'CREATE SERVER ROLE [myCustomRole] AUTHORIZATION [myUser];',
101-
'ALTER AUTHORIZATION ON SERVER ROLE::[myCustomRole] TO [myUser];'
100+
'CREATE SERVER ROLE [myCustomRole] AUTHORIZATION [myUser];',
101+
'ALTER AUTHORIZATION ON SERVER ROLE::[myCustomRole] TO [myUser];'
102102
] }
103103
it_behaves_like 'sqlserver_tsql command'
104104
end
105105
describe 'myUser on Database' do
106106
let(:additional_params) { {
107-
:authorization => 'myUser',
108-
:type => 'DATABASE',
107+
:authorization => 'myUser',
108+
:type => 'DATABASE',
109109
} }
110110
let(:should_contain_command) { [
111-
'CREATE ROLE [myCustomRole] AUTHORIZATION [myUser];',
112-
'ALTER AUTHORIZATION ON ROLE::[myCustomRole] TO [myUser];'
111+
'CREATE ROLE [myCustomRole] AUTHORIZATION [myUser];',
112+
'ALTER AUTHORIZATION ON ROLE::[myCustomRole] TO [myUser];'
113113
] }
114114
it_behaves_like 'sqlserver_tsql command'
115115
end
@@ -118,18 +118,119 @@
118118
context 'ensure =>' do
119119
describe 'absent' do
120120
let(:additional_params) { {
121-
:ensure => 'absent',
121+
:ensure => 'absent',
122122
} }
123123
let(:should_contain_command) { [
124-
'USE [master];',
125-
'DROP SERVER ROLE [myCustomRole];'
124+
'USE [master];',
125+
'DROP SERVER ROLE [myCustomRole];'
126126
] }
127127
let(:should_contain_onlyif) { [
128-
'IF EXISTS(',
128+
'IF EXISTS(',
129129
] }
130130
it_behaves_like 'sqlserver_tsql command'
131131
it_behaves_like 'sqlserver_tsql onlyif'
132132
end
133133
end
134134

135+
context 'members =>' do
136+
let(:sqlserver_tsql_title) { 'role-myCustomRole-members' }
137+
describe '[test these users]' do
138+
let(:additional_params) { {
139+
:members => %w(test these users),
140+
} }
141+
let(:should_contain_command) { [
142+
'ALTER SERVER ROLE [myCustomRole] ADD MEMBER [test];',
143+
'ALTER SERVER ROLE [myCustomRole] ADD MEMBER [these];',
144+
'ALTER SERVER ROLE [myCustomRole] ADD MEMBER [users];',
145+
] }
146+
let(:should_contain_onlyif) { [
147+
] }
148+
it_behaves_like 'sqlserver_tsql command'
149+
it_behaves_like 'sqlserver_tsql onlyif'
150+
end
151+
describe 'empty' do
152+
it {
153+
should_not contain_sqlserver_tsql(sqlserver_tsql_title)
154+
}
155+
end
156+
end
157+
context 'members_purge =>' do
158+
let(:sqlserver_tsql_title) { 'role-myCustomRole-members' }
159+
context 'true' do
160+
describe 'type => SERVER and members => []' do
161+
let(:additional_params) { {
162+
:members_purge => true,
163+
} }
164+
let(:should_contain_command) { [
165+
"WHILE(@row <= @row_count)
166+
BEGIN
167+
SET @sql = 'ALTER SERVER ROLE [myCustomRole] DROP MEMBER [' + (SELECT member FROM @purge_members WHERE ID = @row) + '];'
168+
EXEC(@sql)
169+
SET @row += 1
170+
END"
171+
] }
172+
let(:should_contain_onlyif) { [
173+
"DECLARE @purge_members TABLE (
174+
ID int IDENTITY(1,1),
175+
member varchar(128)
176+
)",
177+
"INSERT INTO @purge_members (member) (
178+
SELECT m.name FROM sys.server_role_members rm
179+
JOIN sys.server_principals r ON rm.role_principal_id = r.principal_id
180+
JOIN sys.server_principals m ON rm.member_principal_id = m.principal_id
181+
WHERE r.name = 'myCustomRole'",
182+
"IF 0 != (SELECT COUNT(*) FROM @purge_members)
183+
THROW 51000, 'Unlisted Members in Role, will be purged', 10",
184+
] }
185+
it_behaves_like 'sqlserver_tsql command'
186+
it_behaves_like 'sqlserver_tsql onlyif'
187+
end
188+
189+
describe 'type => DATABASE and members => []' do
190+
let(:additional_params) { {
191+
:type => 'DATABASE',
192+
:members_purge => true,
193+
} }
194+
let(:should_contain_command) { [
195+
"WHILE(@row <= @row_count)
196+
BEGIN
197+
SET @sql = 'ALTER ROLE [myCustomRole] DROP MEMBER [' + (SELECT member FROM @purge_members WHERE ID = @row) + '];'
198+
EXEC(@sql)
199+
SET @row += 1
200+
END"
201+
] }
202+
let(:should_contain_onlyif) { [
203+
"DECLARE @purge_members TABLE (
204+
ID int IDENTITY(1,1),
205+
member varchar(128)
206+
)",
207+
"INSERT INTO @purge_members (member) (
208+
SELECT m.name FROM sys.database_role_members rm
209+
JOIN sys.database_principals r ON rm.role_principal_id = r.principal_id
210+
JOIN sys.database_principals m ON rm.member_principal_id = m.principal_id
211+
WHERE r.name = 'myCustomRole'",
212+
"IF 0 != (SELECT COUNT(*) FROM @purge_members)
213+
THROW 51000, 'Unlisted Members in Role, will be purged', 10",
214+
] }
215+
it_behaves_like 'sqlserver_tsql command'
216+
it_behaves_like 'sqlserver_tsql onlyif'
217+
end
218+
end
219+
describe '[test these users]' do
220+
let(:additional_params) { {
221+
:members_purge => true,
222+
:members => %w(test these users),
223+
} }
224+
let(:should_contain_command) { [
225+
/WHERE r\.name = 'myCustomRole'\n\s+AND m\.name NOT IN \(/,
226+
"NOT IN ('test','these','users')"
227+
] }
228+
let(:should_contain_onlyif) { [
229+
/WHERE r\.name = 'myCustomRole'\n\s+AND m\.name NOT IN \(/,
230+
"NOT IN ('test','these','users')"
231+
] }
232+
it_behaves_like 'sqlserver_tsql command'
233+
it_behaves_like 'sqlserver_tsql onlyif'
234+
end
235+
end
135236
end

templates/create/role/members.sql.erb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
USE [<%= @database %>];
2+
DECLARE
3+
@role varchar(128) = '<%= @role %>',
4+
@member varchar(128),
5+
@error_msg varchar(250);
6+
7+
<%- @members.each do |member| -%>
8+
BEGIN
9+
SET @member = '<%= member %>';
10+
<%= scope.function_template(['sqlserver/snippets/role/member_exists.sql.erb']) -%>
11+
ALTER <% if @type == 'SERVER' %>SERVER <% end %>ROLE [<%= @role %>] ADD MEMBER [<%= member %>];
12+
13+
<%= scope.function_template(['sqlserver/snippets/role/member_exists.sql.erb']) -%>
14+
THROW 51000, 'Failed to add member [<%= member %>] to Role [<%= @role %>]', 10
15+
END
16+
<% end -%>
17+
18+
<% if @members_purge %>
19+
<%= scope.function_template(['sqlserver/snippets/role/populate_purge_members.sql.erb']) -%>
20+
21+
DECLARE @sql varchar(250), @row int = 1, @row_count int;
22+
SET @row_count = (SELECT COUNT(*) FROM @purge_members);
23+
24+
WHILE(@row <= @row_count)
25+
BEGIN
26+
SET @sql = 'ALTER <% if @type == 'SERVER' %>SERVER <% end %>ROLE [<%= @role %>] DROP MEMBER [' + (SELECT member FROM @purge_members WHERE ID = @row) + '];'
27+
EXEC(@sql)
28+
SET @row += 1
29+
END
30+
<% end %>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
USE [<%= @database %>];
2+
DECLARE
3+
@role varchar(128) = '<%= @role %>',
4+
@member varchar(128),
5+
@error_msg varchar(250);
6+
7+
<% @members.each do |member| %>
8+
SET @member = '<%= member %>';
9+
SET @error_msg = 'The member [<%= member %>] is <% if @ensure == 'present'%>not <% end %>a member of the role [<%=@role %>]';
10+
<%= scope.function_template(['sqlserver/snippets/role/member_exists.sql.erb']) -%>
11+
THROW 51000, @error_msg, 10
12+
<% end %>
13+
14+
<% if @members_purge %>
15+
<%= scope.function_template(['sqlserver/snippets/role/populate_purge_members.sql.erb']) %>
16+
IF 0 != (SELECT COUNT(*) FROM @purge_members)
17+
THROW 51000, 'Unlisted Members in Role, will be purged', 10
18+
<% end %>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
IF NOT EXISTS (
2+
SELECT r.name [Role], m.name [Member] FROM sys.<%= @type.downcase %>_role_members rm
3+
JOIN sys.<%= @type.downcase %>_principals r ON rm.role_principal_id = r.principal_id
4+
JOIN sys.<%= @type.downcase %>_principals m ON rm.member_principal_id = m.principal_id
5+
WHERE r.name = @role AND m.name = @member)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
DECLARE @purge_members TABLE (
2+
ID int IDENTITY(1,1),
3+
member varchar(128)
4+
)
5+
INSERT INTO @purge_members (member) (
6+
SELECT m.name FROM sys.<%= @type.downcase %>_role_members rm
7+
JOIN sys.<%= @type.downcase %>_principals r ON rm.role_principal_id = r.principal_id
8+
JOIN sys.<%= @type.downcase %>_principals m ON rm.member_principal_id = m.principal_id
9+
WHERE r.name = '<%= @role %>'
10+
<% if [email protected]? %>AND m.name NOT IN (<%= @members.collect{|m| "'#{m}'"}.join(',') %>)<% end %>
11+
);

0 commit comments

Comments
 (0)