diff --git a/lib/SimpleRestServices.dll b/lib/SimpleRestServices.dll index b3543a6d2..72fbc3e4c 100644 Binary files a/lib/SimpleRestServices.dll and b/lib/SimpleRestServices.dll differ diff --git a/lib/SimpleRestServices.pdb b/lib/SimpleRestServices.pdb index 8dfbd2966..67f679f22 100644 Binary files a/lib/SimpleRestServices.pdb and b/lib/SimpleRestServices.pdb differ diff --git a/src/corelib/Core/Domain/NewUser.cs b/src/corelib/Core/Domain/NewUser.cs new file mode 100644 index 000000000..63e959ba0 --- /dev/null +++ b/src/corelib/Core/Domain/NewUser.cs @@ -0,0 +1,23 @@ +using System.Runtime.Serialization; + +namespace net.openstack.Core.Domain +{ + [DataContract] + public class NewUser + { + [DataMember(Name = "OS-KSADM:password")] + public string Password { get; set; } + + [DataMember(Name = "id", EmitDefaultValue = true)] + public string Id { get; set; } + + [DataMember(Name = "username")] + public string Username { get; set; } + + [DataMember(Name = "email")] + public string Email { get; set; } + + [DataMember(Name = "enabled")] + public bool Enabled { get; set; } + } +} diff --git a/src/corelib/Core/Domain/User.cs b/src/corelib/Core/Domain/User.cs index 0b04fa783..a82b66fb9 100644 --- a/src/corelib/Core/Domain/User.cs +++ b/src/corelib/Core/Domain/User.cs @@ -6,21 +6,19 @@ namespace net.openstack.Core.Domain [DataContract] public class User { - [DataMember(Name = "RAX-AUTH:defaultRegion")] public string DefaultRegion { get; set; } - [DataMember] + [DataMember(Name="id", EmitDefaultValue = true)] public string Id { get; set; } - [DataMember] + [DataMember(Name="username")] public string Username { get; set; } - [DataMember] + [DataMember(Name="email")] public string Email { get; set; } - [DataMember] + [DataMember(Name = "enabled")] public bool Enabled { get; set; } - } } \ No newline at end of file diff --git a/src/corelib/Core/IIdentityProvider.cs b/src/corelib/Core/IIdentityProvider.cs index 897c0a565..c19b64381 100644 --- a/src/corelib/Core/IIdentityProvider.cs +++ b/src/corelib/Core/IIdentityProvider.cs @@ -13,7 +13,7 @@ public interface IIdentityProvider User[] ListUsers(CloudIdentity identity); User GetUserByName(CloudIdentity identity, string name); User GetUser(CloudIdentity identity, string id); - User AddUser(CloudIdentity identity, User user); + NewUser AddUser(CloudIdentity identity, NewUser user); User UpdateUser(CloudIdentity identity, User user); bool DeleteUser(CloudIdentity identity, string userId); diff --git a/src/corelib/Providers/Rackspace/GeographicalIdentityProvider.cs b/src/corelib/Providers/Rackspace/GeographicalIdentityProvider.cs index c10bc68e0..13e2d0657 100644 --- a/src/corelib/Providers/Rackspace/GeographicalIdentityProvider.cs +++ b/src/corelib/Providers/Rackspace/GeographicalIdentityProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SimpleRestServices.Client; @@ -215,18 +216,27 @@ public User GetUser(CloudIdentity identity, string userId) return response.Data.User; } - public User AddUser(CloudIdentity identity, User user) + public NewUser AddUser(CloudIdentity identity, NewUser newUser) { - var response = ExecuteRESTRequest(identity, "/v2.0/users", HttpMethod.POST, new AddUserRequest { User = user }); + newUser.Id = null; + + var response = ExecuteRESTRequest(identity, "/v2.0/users", HttpMethod.POST, new AddUserRequest { User = newUser }); if (response == null || response.Data == null) return null; - return response.Data.User; + // If the user specifies a password, then the password will not be in the response, so we need to fill it in on the return object. + if (string.IsNullOrWhiteSpace(response.Data.NewUser.Password)) + response.Data.NewUser.Password = newUser.Password; + + return response.Data.NewUser; } public User UpdateUser(CloudIdentity identity, User user) { + if(user == null || string.IsNullOrWhiteSpace(user.Id)) + throw new ArgumentException("The User or User.Id values cannot be null."); + var urlPath = string.Format("v2.0/users/{0}", user.Id); var updateUserRequest = new UpdateUserRequest { User = user }; @@ -378,7 +388,7 @@ protected virtual Response ExecuteRESTRequest(CloudIdentity identity, string url bodyStr = JsonConvert.SerializeObject(body, new JsonSerializerSettings{NullValueHandling = NullValueHandling.Ignore}); } - var response = _restService.Execute(url, method, bodyStr, headers, queryStringParameter, new JsonRequestSettings() { RetryCount = retryCount, RetryDelayInMS = retryDelay, Non200SuccessCodes = new[] { 401, 409 } }); + var response = _restService.Execute(url, method, bodyStr, headers, queryStringParameter, new JsonRequestSettings() { RetryCount = retryCount, RetryDelayInMS = retryDelay, Non200SuccessCodes = new[] { 401, 409 }, UserAgent = ProviderBase.GetUserAgentHeaderValue()}); // on errors try again 1 time. if (response.StatusCode == 401 && !isRetry && !isTokenRequest) diff --git a/src/corelib/Providers/Rackspace/IdentityProvider.cs b/src/corelib/Providers/Rackspace/IdentityProvider.cs index 1ecb11afa..df5b4a483 100644 --- a/src/corelib/Providers/Rackspace/IdentityProvider.cs +++ b/src/corelib/Providers/Rackspace/IdentityProvider.cs @@ -67,10 +67,10 @@ public User GetUser(CloudIdentity identity, string userId) return provider.GetUser(identity, userId); } - public User AddUser(CloudIdentity identity, User user) + public NewUser AddUser(CloudIdentity identity, NewUser newUser) { var provider = GetProvider(identity); - return provider.AddUser(identity, user); + return provider.AddUser(identity, newUser); } public User UpdateUser(CloudIdentity identity, User user) @@ -168,7 +168,7 @@ private IExtendedIdentityProvider GetProvider(CloudIdentity identity) var rackspaceCloudIdentity = identity as RackspaceCloudIdentity; if (rackspaceCloudIdentity == null) - throw new InvalidCloudIdentityException(string.Format("Invalid Identity object. Rackspace Identoty service requires an instance of type: {0}", typeof(RackspaceCloudIdentity))); + _factory.Get(CloudInstance.Default); return _factory.Get(rackspaceCloudIdentity.CloudInstance); } diff --git a/src/corelib/Providers/Rackspace/Objects/Request/AddUserRequest.cs b/src/corelib/Providers/Rackspace/Objects/Request/AddUserRequest.cs index 7db0470b9..4d1386982 100644 --- a/src/corelib/Providers/Rackspace/Objects/Request/AddUserRequest.cs +++ b/src/corelib/Providers/Rackspace/Objects/Request/AddUserRequest.cs @@ -7,6 +7,6 @@ namespace net.openstack.Providers.Rackspace.Objects.Request internal class AddUserRequest { [DataMember(Name = "user")] - public User User { get; set; } + public NewUser User { get; set; } } } diff --git a/src/corelib/Providers/Rackspace/Objects/Request/UpdateUserRequest.cs b/src/corelib/Providers/Rackspace/Objects/Request/UpdateUserRequest.cs index abc41881c..a7b7b64de 100644 --- a/src/corelib/Providers/Rackspace/Objects/Request/UpdateUserRequest.cs +++ b/src/corelib/Providers/Rackspace/Objects/Request/UpdateUserRequest.cs @@ -1,13 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using System.Text; using net.openstack.Core.Domain; namespace net.openstack.Providers.Rackspace.Objects.Request { + [DataContract] internal class UpdateUserRequest { + [DataMember(Name = "user")] public User User { get; set; } } } diff --git a/src/corelib/Providers/Rackspace/Objects/Response/NewUserResponse.cs b/src/corelib/Providers/Rackspace/Objects/Response/NewUserResponse.cs new file mode 100644 index 000000000..481340f3c --- /dev/null +++ b/src/corelib/Providers/Rackspace/Objects/Response/NewUserResponse.cs @@ -0,0 +1,12 @@ +using System.Runtime.Serialization; +using net.openstack.Core.Domain; + +namespace net.openstack.Providers.Rackspace.Objects.Response +{ + [DataContract] + internal class NewUserResponse + { + [DataMember(Name = "user")] + public NewUser NewUser { get; set; } + } +} \ No newline at end of file diff --git a/src/corelib/Providers/Rackspace/Objects/Response/UserResponse.cs b/src/corelib/Providers/Rackspace/Objects/Response/UserResponse.cs index 5d000d527..b75ff0007 100644 --- a/src/corelib/Providers/Rackspace/Objects/Response/UserResponse.cs +++ b/src/corelib/Providers/Rackspace/Objects/Response/UserResponse.cs @@ -1,9 +1,12 @@ +using System.Runtime.Serialization; using net.openstack.Core.Domain; namespace net.openstack.Providers.Rackspace.Objects.Response { + [DataContract] internal class UserResponse { + [DataMember(Name = "user")] public User User { get; set; } } } \ No newline at end of file diff --git a/src/corelib/Providers/Rackspace/ProviderBase.cs b/src/corelib/Providers/Rackspace/ProviderBase.cs index 479d02ba1..2787eabec 100644 --- a/src/corelib/Providers/Rackspace/ProviderBase.cs +++ b/src/corelib/Providers/Rackspace/ProviderBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SimpleRestServices.Client; @@ -16,6 +17,7 @@ public class ProviderBase { private readonly IIdentityProvider _identityProvider; private readonly IRestService _restService; + private static Version _currentVersion; protected ProviderBase(IIdentityProvider identityProvider, IRestService restService) { @@ -42,6 +44,9 @@ protected Response ExecuteRESTRequest(CloudIdentity identity, Uri absolute bodyStr = JsonConvert.SerializeObject(body, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); } + if (string.IsNullOrWhiteSpace(requestSettings.UserAgent)) + requestSettings.UserAgent = GetUserAgentHeaderValue(); + var response = _restService.Execute(absoluteUri, method, bodyStr, headers, queryStringParameter, requestSettings); // on errors try again 1 time. @@ -77,6 +82,9 @@ protected Response ExecuteRESTRequest(CloudIdentity identity, Uri absoluteUri, H bodyStr = JsonConvert.SerializeObject(body, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); } + if (string.IsNullOrWhiteSpace(requestSettings.UserAgent)) + requestSettings.UserAgent = GetUserAgentHeaderValue(); + var response = _restService.Execute(absoluteUri, method, bodyStr, headers, queryStringParameter, requestSettings); // on errors try again 1 time. @@ -99,7 +107,7 @@ internal JsonRequestSettings BuildDefaultRequestSettings(IEnumerable non200 if(non200SuccessCodes != null) non200SuccessCodesAggregate.AddRange(non200SuccessCodes); - return new JsonRequestSettings { RetryCount = 2, RetryDelayInMS = 200, Non200SuccessCodes = non200SuccessCodesAggregate}; + return new JsonRequestSettings { RetryCount = 2, RetryDelayInMS = 200, Non200SuccessCodes = non200SuccessCodesAggregate, UserAgent = GetUserAgentHeaderValue()}; } protected virtual string GetServiceEndpoint(CloudIdentity identity, string serviceName, string region = null) @@ -150,5 +158,13 @@ internal static void CheckResponse(Response response) throw new ServiceUnavailableException(response); } } + + internal static string GetUserAgentHeaderValue() + { + if (_currentVersion == null) + _currentVersion = Assembly.GetExecutingAssembly().GetName().Version; + + return string.Format("openstack.net/{0}", _currentVersion.ToString()); + } } } diff --git a/src/corelib/corelib.csproj b/src/corelib/corelib.csproj index 7d62c1994..3dcbca409 100644 --- a/src/corelib/corelib.csproj +++ b/src/corelib/corelib.csproj @@ -58,6 +58,7 @@ + @@ -101,6 +102,7 @@ + diff --git a/src/testing/integration/Providers/Rackspace/ComputeTests.cs b/src/testing/integration/Providers/Rackspace/ComputeTests.cs index 07d201faf..59ec45514 100644 --- a/src/testing/integration/Providers/Rackspace/ComputeTests.cs +++ b/src/testing/integration/Providers/Rackspace/ComputeTests.cs @@ -333,12 +333,22 @@ public void Test025_Should_Successfully_To_And_Login_With_New_Password() { var provider = new net.openstack.Providers.Rackspace.ComputeProvider(); var serverDetails = provider.GetDetails(_testIdentity, _testServer.Id); - using(var client = new Renci.SshNet.SshClient(serverDetails.AccessIPv4, "root", NewPassword)) + bool sucess = false; + for (int i = 0; i < 10; i++ ) { - client.Connect(); + using (var client = new Renci.SshNet.SshClient(serverDetails.AccessIPv4, "root", NewPassword)) + { + client.Connect(); - Assert.IsTrue(client.IsConnected); + sucess = client.IsConnected; + + if (sucess) + break; + } + Thread.Sleep(1000); } + + Assert.IsTrue(sucess); } [TestMethod] diff --git a/src/testing/integration/Providers/Rackspace/IdentityFull.orderedtest b/src/testing/integration/Providers/Rackspace/IdentityFull.orderedtest index 3e6a1f03b..63d214885 100644 --- a/src/testing/integration/Providers/Rackspace/IdentityFull.orderedtest +++ b/src/testing/integration/Providers/Rackspace/IdentityFull.orderedtest @@ -1,12 +1,13 @@  - + + - + @@ -16,5 +17,26 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/testing/integration/Providers/Rackspace/IdentityTests.cs b/src/testing/integration/Providers/Rackspace/IdentityTests.cs index 22142e8d7..bf867a5ff 100644 --- a/src/testing/integration/Providers/Rackspace/IdentityTests.cs +++ b/src/testing/integration/Providers/Rackspace/IdentityTests.cs @@ -15,7 +15,9 @@ public class IdentityTests private static RackspaceCloudIdentity _testAdminIdentity; private static User _userDetails; private static User _adminUserDetails; - private const string NewPassword = "my_new_password"; + private static NewUser _newTestUser1; + private const string NewUserPassword = "My_n3wuser2_p@$$ssw0rd"; + private const string NewPassword = "My_n3w_p@$$ssw0rd"; /// ///Gets or sets the test context which provides @@ -125,13 +127,13 @@ public void Should_Throw_Error_When_Authenticating_With_Invalid_Username() } [TestMethod] - public void Should_List_Only_Self_When_Retrieving_List_Of_Users_With_Non_Admin_Account() + public void Should_List_Only_User_In_Account_When_Retrieving_List_Of_Users_With_User_Admin_Account() { IIdentityProvider serviceProvider = new IdentityProvider(); var users = serviceProvider.ListUsers(_testIdentity); - Assert.IsTrue(users.Count() == 1); + Assert.IsTrue(users.Any()); Assert.AreEqual(_testIdentity.Username, users[0].Username); } @@ -142,7 +144,7 @@ public void Should_List_Multiple_Users_When_Retrieving_List_Of_Users_With_Admin_ var users = serviceProvider.ListUsers(_testAdminIdentity); - Assert.IsTrue(users.Count() > 1); + Assert.IsTrue(users.Any()); } [TestMethod] @@ -244,5 +246,159 @@ public void Should_Throw_Exception_When_Trying_To_Get_Details_Of_A_Different_Use Assert.IsTrue(true); } } + + [TestMethod] + public void Should_Add_New_User_Without_Specifying_A_Password_Or_Default_Region_To_Account_When_Requesting_As_User_Admin() + { + IIdentityProvider provider = new IdentityProvider(); + + _newTestUser1 = provider.AddUser(_testIdentity, new NewUser { Username = "openstacknettestuser1", Email = "newuser@me.com", Enabled = true }); + + Assert.IsNotNull(_newTestUser1); + Assert.AreEqual("openstacknettestuser1", _newTestUser1.Username); + Assert.AreEqual("newuser@me.com", _newTestUser1.Email); + Assert.AreEqual(true, _newTestUser1.Enabled); + Assert.IsFalse(string.IsNullOrWhiteSpace(_newTestUser1.Password)); + } + + [TestMethod] + public void Should_Authenticate_NewUser() + { + Assert.IsNotNull(_newTestUser1); + + IIdentityProvider provider = new IdentityProvider(); + + var userAccess = + provider.Authenticate(new RackspaceCloudIdentity + {Username = _newTestUser1.Username, Password = _newTestUser1.Password}); + + Assert.IsNotNull(userAccess); + } + + [TestMethod] + public void Should_Update_NewUser_Username_And_Email_When_Requesting_As_User_Admin() + { + IIdentityProvider provider = new IdentityProvider(); + + var user = new User + { + Id = _newTestUser1.Id, + Username = "openstacknettestuser12", + Email = "newuser2@me.com", + Enabled = true + }; + var updatedUser = provider.UpdateUser(_testIdentity, user); + + Assert.IsNotNull(updatedUser); + Assert.AreEqual("openstacknettestuser12", updatedUser.Username); + Assert.AreEqual("newuser2@me.com", updatedUser.Email); + Assert.AreEqual(true, updatedUser.Enabled); + Assert.IsTrue(string.IsNullOrWhiteSpace(updatedUser.DefaultRegion)); + } + + [TestMethod] + public void Should_Delete_NewUser_When_Requesting_As_User_Admin() + { + IIdentityProvider provider = new IdentityProvider(); + + var response = provider.DeleteUser(_testIdentity, _newTestUser1.Id); + + Assert.IsTrue(response); + } + + [TestMethod] + public void Should_Throw_Exception_When_Requesting_The_NewUser_After_It_Has_Been_Deleted_When_Requesting_As_User_Admin() + { + IIdentityProvider provider = new IdentityProvider(); + + try + { + provider.GetUser(_testIdentity, _newTestUser1.Id); + + throw new Exception("This code path is invalid, exception was expected."); + } + catch(Exception ex) + { + Assert.IsTrue(true); + } + } + + [TestMethod] + public void Should_Add_New_User_With_Specifying_A_Password_But_Not_Default_Region_To_Account_When_Requesting_As_User_Admin() + { + IIdentityProvider provider = new IdentityProvider(); + + _newTestUser1 = provider.AddUser(_testIdentity, new NewUser { Username = "openstacknettestuser2", Email = "newuser2@me.com", Enabled = true, Password = NewUserPassword }); + + Assert.IsNotNull(_newTestUser1); + Assert.AreEqual("openstacknettestuser2", _newTestUser1.Username); + Assert.AreEqual("newuser2@me.com", _newTestUser1.Email); + Assert.AreEqual(true, _newTestUser1.Enabled); + Assert.AreEqual(NewUserPassword, _newTestUser1.Password); + Assert.IsFalse(string.IsNullOrWhiteSpace(_newTestUser1.Password)); + } + + [TestMethod] + public void Should_Update_NewUser_Username_And_Email_And_Default_Region_When_Requesting_As_User_Admin() + { + IIdentityProvider provider = new IdentityProvider(); + + var user = new User + { + Id = _newTestUser1.Id, + Username = "openstacknettestuser32", + Email = "newuser32@me.com", + Enabled = true, + DefaultRegion = "DFW" + }; + var updatedUser = provider.UpdateUser(_testIdentity, user); + + Assert.IsNotNull(updatedUser); + Assert.AreEqual("openstacknettestuser32", updatedUser.Username); + Assert.AreEqual("newuser32@me.com", updatedUser.Email); + Assert.AreEqual(true, updatedUser.Enabled); + Assert.AreEqual("DFW", updatedUser.DefaultRegion); + } + + [TestMethod] + public void Should_Get_NewUser_When_Requesting_As_Self() + { + IIdentityProvider provider = new IdentityProvider(); + + var user = provider.GetUser(new RackspaceCloudIdentity { Username = _newTestUser1.Username, Password = _newTestUser1.Password }, _newTestUser1.Id); + + Assert.IsNotNull(user); + } + + [TestMethod] + public void Should_Update_NewUser_Username_And_Email_When_Requesting_As_Self() + { + IIdentityProvider provider = new IdentityProvider(); + + var user = new User + { + Id = _newTestUser1.Id, + Username = "openstacknettestuser42", + Email = "newuser42@me.com", + Enabled = true, + }; + var updatedUser = provider.UpdateUser(new RackspaceCloudIdentity { Username = _newTestUser1.Username, Password = _newTestUser1.Password }, user); + + Assert.IsNotNull(updatedUser); + Assert.AreEqual("openstacknettestuser42", updatedUser.Username); + Assert.AreEqual("newuser42@me.com", updatedUser.Email); + Assert.AreEqual(true, updatedUser.Enabled); + } + + [TestMethod] + public void Should_List_Only_Self_When_Retrieving_List_Of_Users_With_Non_Admin_Account() + { + IIdentityProvider serviceProvider = new IdentityProvider(); + + var users = serviceProvider.ListUsers(new RackspaceCloudIdentity { Username = _newTestUser1.Username, Password = _newTestUser1.Password }); + + Assert.IsTrue(users.Count() == 1); + Assert.AreEqual(_newTestUser1.Username, users[0].Username); + } } }