Skip to content

Commit e542fe8

Browse files
authored
feat(AgDb): AddUser (#100)
* feat(AgDb): AddUser * feat(tests): AddUser * fix(format): user constructor * fix(null): _user field * fix(review): param and return type * fix(review): explicit create, code reuse, remove unecessary methods * fix(review): perm in Database * fix(review): format * feat(Add/DropUser): IAgDb
1 parent 83ec685 commit e542fe8

5 files changed

Lines changed: 124 additions & 18 deletions

File tree

src/AgDatabase.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ void Restore(IEnumerable<BackupMetadata> backupOrder, Func<int, TimeSpan> retryD
3939
IEnumerable<LoginProperties> AssociatedLogins();
4040
void DropLogin(LoginProperties login);
4141
void DropAllLogins();
42+
void AddUser(UserProperties user);
43+
void DropUser(UserProperties user);
4244
void AddRole(LoginProperties login, RoleProperties role);
4345
IEnumerable<RoleProperties> AssociatedRoles();
4446
void ContainsLogin(string loginName);
@@ -200,6 +202,16 @@ private void AddNewSqlLogin(LoginProperties login)
200202
Parallel.ForEach(_listener.Secondaries, server => server.AddLogin(login));
201203
}
202204

205+
public void AddUser(UserProperties user)
206+
{
207+
_listener.Primary.Database(Name).AddUser(user);
208+
}
209+
210+
public void DropUser(UserProperties user)
211+
{
212+
_listener.Primary.Database(Name)?.DropUser(user);
213+
}
214+
203215
public IEnumerable<RoleProperties> AssociatedRoles()
204216
{
205217
return _listener.Primary.Roles.Select(r => r.Properties());

src/SmoFacade/Database.cs

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,46 @@ public void DropAssociatedLogins()
4444
{
4545
var logins = Users.Where(u => u.Login != null && u.Login.Name != "sa")
4646
.Select(u => u.Login.Properties());
47-
foreach(var login in logins)
48-
_server.DropLogin(new LoginProperties {
47+
foreach (var login in logins)
48+
_server.DropLogin(new LoginProperties
49+
{
4950
Name = login.Name
5051
});
5152
}
5253

54+
public User AddUser(UserProperties userProperties)
55+
{
56+
var user = Users.SingleOrDefault(u => u.Name.Equals(userProperties.Name, StringComparison.InvariantCultureIgnoreCase));
57+
if (user == null)
58+
{
59+
var smoUser = new Microsoft.SqlServer.Management.Smo.User(_database, userProperties.Name)
60+
{ Login = userProperties.LoginName };
61+
smoUser.Create();
62+
user = new User(smoUser, _server);
63+
64+
foreach (var role in userProperties.Roles)
65+
{
66+
user.AddRole(role);
67+
}
68+
69+
GrantPermission(userProperties);
70+
}
71+
72+
return user;
73+
}
74+
75+
private void GrantPermission(UserProperties userProperties)
76+
{
77+
_database.Grant(userProperties.Permissions, userProperties.Name);
78+
}
79+
80+
public void DropUser(UserProperties userProperties)
81+
{
82+
var matchingUser =
83+
Users.SingleOrDefault(u => u.Name.Equals(userProperties.Name, StringComparison.InvariantCultureIgnoreCase));
84+
matchingUser?.Drop();
85+
}
86+
5387
/// <summary>
5488
/// Kills connections and deletes the database.
5589
/// </summary>
@@ -61,7 +95,8 @@ public void Drop()
6195
.Handle<FailedOperationException>()
6296
.WaitAndRetry(3, retryAttempt => TimeSpan.FromMilliseconds(Math.Pow(10, retryAttempt)));
6397

64-
policy.Execute(() => {
98+
policy.Execute(() =>
99+
{
65100
_database.Refresh();
66101
_database.Parent.KillDatabase(_database.Name);
67102
});
@@ -97,8 +132,9 @@ public List<BackupMetadata> MostRecentBackupChain()
97132
cmd.Parameters.Add(dbName);
98133

99134
using var reader = cmd.ExecuteReader();
100-
while(reader.Read())
101-
backups.Add(new BackupMetadata {
135+
while (reader.Read())
136+
backups.Add(new BackupMetadata
137+
{
102138
CheckpointLsn = (decimal)reader["checkpoint_lsn"],
103139
DatabaseBackupLsn = (decimal)reader["database_backup_lsn"],
104140
DatabaseName = (string)reader["database_name"],
@@ -129,7 +165,7 @@ public decimal MostRecentFullBackupLsn()
129165
cmd.Parameters.Add(dbName);
130166

131167
using var reader = cmd.ExecuteReader();
132-
if(!reader.Read())
168+
if (!reader.Read())
133169
throw new Exception("MostRecentFullBackup SQL found no results");
134170

135171
var lsnValue = reader["most_recent_full_backup_checkpoint_lsn"];
@@ -166,8 +202,9 @@ public List<BackupMetadata> BackupChainFromLsn(decimal checkpointLsn)
166202
cmd.Parameters.Add(lsnParam);
167203

168204
using var reader = cmd.ExecuteReader();
169-
while(reader.Read())
170-
backups.Add(new BackupMetadata {
205+
while (reader.Read())
206+
backups.Add(new BackupMetadata
207+
{
171208
CheckpointLsn = (decimal)reader["checkpoint_lsn"],
172209
DatabaseBackupLsn = (decimal)reader["database_backup_lsn"],
173210
DatabaseName = (string)reader["database_name"],
@@ -210,23 +247,26 @@ private void ThrowIfUnreadyToDrop()
210247
.WaitAndRetry(6, retryAttempt => TimeSpan.FromMilliseconds(Math.Pow(5, retryAttempt)));
211248

212249
// ensure database is not in AvailabilityGroup, WaitAndRetry loop for each instance to sync
213-
policyPrep.Execute(() => {
250+
policyPrep.Execute(() =>
251+
{
214252
_database.Refresh();
215-
if(Restoring)
253+
if (Restoring)
216254
return; // restoring state means we're good to drop
217255

218-
try {
219-
if(!string.IsNullOrEmpty(_database.AvailabilityGroupName))
256+
try
257+
{
258+
if (!string.IsNullOrEmpty(_database.AvailabilityGroupName))
220259
throw
221260
new Exception($"Cannot kill the database {Name} until it has been removed from the AvailabilityGroup");
222-
if(_database.AvailabilityDatabaseSynchronizationState >
261+
if (_database.AvailabilityDatabaseSynchronizationState >
223262
AvailabilityDatabaseSynchronizationState.NotSynchronizing)
224263
throw new
225264
Exception($"Cannot kill the database {Name} until AvailabilityDatabaseSynchronizationState is inaccessible");
226265
}
227-
catch(
228-
PropertyCannotBeRetrievedException) { } // good here, AvailabilityDatabaseSynchronizationState unavailable means it has no state
229-
catch(SmoException e) when(e.Message.Contains("Cannot access properties or methods")) { }
266+
catch (
267+
PropertyCannotBeRetrievedException)
268+
{ } // good here, AvailabilityDatabaseSynchronizationState unavailable means it has no state
269+
catch (SmoException e) when (e.Message.Contains("Cannot access properties or methods")) { }
230270
});
231271
}
232272
}

src/SmoFacade/User.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,39 @@
11
namespace AgDatabaseMove.SmoFacade
22
{
3+
using System.Collections.Generic;
4+
using Smo = Microsoft.SqlServer.Management.Smo;
5+
6+
public class UserProperties
7+
{
8+
public string Name { get; set; }
9+
public IEnumerable<RoleProperties> Roles { get; set; }
10+
public Smo.DatabasePermissionSet Permissions { get; set; }
11+
public string LoginName { get; set; }
12+
}
13+
314
public class User
415
{
516
private readonly Server _server;
6-
private readonly Microsoft.SqlServer.Management.Smo.User _user;
17+
private readonly Smo.User _user;
718

8-
public User(Microsoft.SqlServer.Management.Smo.User user, Server server)
19+
public User(Smo.User user, Server server)
920
{
1021
_user = user;
1122
_server = server;
1223
}
1324

1425
public string Name => _user.Name;
1526

27+
public void AddRole(RoleProperties role)
28+
{
29+
_user.AddToRole(role.Name);
30+
}
31+
32+
public void Drop()
33+
{
34+
_user.Drop();
35+
}
36+
1637
public Login Login
1738
{
1839
get

tests/AgDatabaseMove.Integration/Fixtures/TestAgDatabaseFixture.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public TestAgDatabaseFixture()
2828
DatabaseName = _agConfig.DatabaseName,
2929
CredentialName = _agConfig.CredentialName
3030
});
31+
3132
_agDatabase.AddLogin(new LoginProperties
3233
{
3334
Name = _loginConfig.LoginName,
@@ -36,6 +37,14 @@ public TestAgDatabaseFixture()
3637
DefaultDatabase = _loginConfig.DefaultDatabase
3738
});
3839

40+
_agDatabase.AddUser(new UserProperties
41+
{
42+
Name = _loginConfig.LoginName,
43+
LoginName = _loginConfig.LoginName,
44+
Roles = new[] { new RoleProperties { Name = "db_datareader" } },
45+
Permissions = new DatabasePermissionSet(DatabasePermission.Execute)
46+
});
47+
3948
_createdLogins = GetCreatedLogins();
4049
}
4150

@@ -56,6 +65,7 @@ public TestAgDatabaseFixture()
5665
public void Dispose()
5766
{
5867
_agDatabase?.DropLogin(new LoginProperties { Name = _loginConfig.LoginName });
68+
_agDatabase?.DropUser(new UserProperties { Name = _loginConfig.LoginName });
5969
_agDatabase?.Dispose();
6070
}
6171
}

tests/AgDatabaseMove.Integration/TestAgDatabase.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,28 @@ public void TestLoginSidsMatch()
2525
var sid = _fixture._createdLogins.First().Sid;
2626
Assert.All(_fixture._createdLogins.Select(l => l.Sid), s => Assert.Equal(s, sid));
2727
}
28+
29+
[Fact]
30+
public void TestUserExists()
31+
{
32+
var db = _fixture._agDatabase._listener.Primary.Database(_fixture._agConfig.DatabaseName);
33+
Assert.Contains(_fixture._loginConfig.LoginName, db.Users.Select(u => u.Name));
34+
}
35+
36+
[Fact]
37+
public void TestUserHasRoles()
38+
{
39+
var db = _fixture._agDatabase._listener.Primary.Database(_fixture._agConfig.DatabaseName)._database;
40+
var user = db.Users[_fixture._loginConfig.LoginName];
41+
Assert.True(user.EnumRoles().Contains("db_datareader"));
42+
}
43+
44+
[Fact]
45+
public void TestUserHasPermissions()
46+
{
47+
var db = _fixture._agDatabase._listener.Primary.Database(_fixture._loginConfig.DefaultDatabase)._database;
48+
var permissions = db.EnumDatabasePermissions(_fixture._loginConfig.LoginName);
49+
Assert.NotNull(permissions.FirstOrDefault(p => p.PermissionType.Execute));
50+
}
2851
}
2952
}

0 commit comments

Comments
 (0)