diff --git a/build.json b/build.json index a3273984..8081227a 100644 --- a/build.json +++ b/build.json @@ -18,6 +18,7 @@ "MainProjects": [ "Microsoft.SqlTools.ServiceLayer", "Microsoft.SqlTools.Credentials", - "Microsoft.Sqltools.Serialization" + "Microsoft.SqlTools.Serialization", + "Microsoft.SqlTools.ResourceProvider" ] } diff --git a/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj b/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj index 961fecaa..5075a8a8 100644 --- a/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj +++ b/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj @@ -35,7 +35,4 @@ - - - diff --git a/src/Microsoft.SqlTools.Hosting/Extensibility/ExtensionServiceProvider.cs b/src/Microsoft.SqlTools.Hosting/Extensibility/ExtensionServiceProvider.cs index 94f60df7..d39f92cf 100644 --- a/src/Microsoft.SqlTools.Hosting/Extensibility/ExtensionServiceProvider.cs +++ b/src/Microsoft.SqlTools.Hosting/Extensibility/ExtensionServiceProvider.cs @@ -38,9 +38,19 @@ namespace Microsoft.SqlTools.Extensibility "microsoftsqltoolsservicelayer.dll" }; + return CreateFromAssembliesInDirectory(inclusionList); + } + + /// + /// Creates a service provider by loading a set of named assemblies, expected to be in the current working directory + /// + /// full DLL names, as a string enumerable + /// instance + public static ExtensionServiceProvider CreateFromAssembliesInDirectory(IEnumerable inclusionList) + { string assemblyPath = typeof(ExtensionStore).GetTypeInfo().Assembly.Location; string directory = Path.GetDirectoryName(assemblyPath); - + AssemblyLoadContext context = new AssemblyLoader(directory); var assemblyPaths = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly); @@ -49,20 +59,20 @@ namespace Microsoft.SqlTools.Extensibility { // skip DLL files not in inclusion list bool isInList = false; - foreach (var item in inclusionList) + foreach (var item in inclusionList) { - if (path.EndsWith(item, StringComparison.OrdinalIgnoreCase)) + if (path.EndsWith(item, StringComparison.OrdinalIgnoreCase)) { isInList = true; break; } } - if (!isInList) + if (!isInList) { continue; - } - + } + try { assemblies.Add( diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/GeneralRequestDetails.cs b/src/Microsoft.SqlTools.Hosting/Utility/GeneralRequestDetails.cs similarity index 69% rename from src/Microsoft.SqlTools.ServiceLayer/Utility/GeneralRequestDetails.cs rename to src/Microsoft.SqlTools.Hosting/Utility/GeneralRequestDetails.cs index 4612976c..a407dc6e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Utility/GeneralRequestDetails.cs +++ b/src/Microsoft.SqlTools.Hosting/Utility/GeneralRequestDetails.cs @@ -6,9 +6,8 @@ using System; using System.Collections.Generic; using System.Globalization; -using Microsoft.SqlTools.Utility; -namespace Microsoft.SqlTools.ServiceLayer.Utility +namespace Microsoft.SqlTools.Utility { public class GeneralRequestDetails { @@ -17,7 +16,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility Options = new Dictionary(); } - internal T GetOptionValue(string name) + public T GetOptionValue(string name) { T result = default(T); if (Options != null && Options.ContainsKey(name)) @@ -37,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility return result; } - internal static T GetValueAs(object value) + public static T GetValueAs(object value) { T result = default(T); @@ -54,7 +53,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility else if (typeof(T).IsEnum) { object enumValue; - if (Enum.TryParse(typeof(T), value.ToString(), out enumValue)) + if (TryParseEnum(typeof(T), value.ToString(), out enumValue)) { value = (T)enumValue; } @@ -63,7 +62,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility else if (value != null && (typeof(T).IsEnum)) { object enumValue; - if (Enum.TryParse(typeof(T), value.ToString(), out enumValue)) + if (TryParseEnum(typeof(T), value.ToString(), out enumValue)) { value = enumValue; } @@ -73,6 +72,26 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility return result; } + /// + /// This method exists because in NetStandard the Enum.TryParse methods that accept in a type + /// are not present, and the generic TryParse method requires the type T to be non-nullable which + /// is hard to check. This is different to the NetCore definition for some reason. + /// It seems easier to implement our own than work around this. + /// + private static bool TryParseEnum(Type t, string value, out object enumValue) + { + try + { + enumValue = Enum.Parse(t, value); + return true; + } + catch(Exception) + { + enumValue = default(T); + return false; + } + } + protected void SetOptionValue(string name, T value) { Options = Options ?? new Dictionary(); diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/AccountOptionsHelper.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/AccountOptionsHelper.cs new file mode 100644 index 00000000..d355840e --- /dev/null +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/AccountOptionsHelper.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + + +namespace Microsoft.SqlTools.ResourceProvider.Core +{ + internal class AccountOptionsHelper + { + /// + /// The name of the IsMsAccount option + /// + internal const string IsMsAccount = "IsMsAccount"; + /// + /// The name of the Tenants option + /// + internal const string Tenants = "Tentants"; + } +} diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/AccountTokenWrapper.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/AccountTokenWrapper.cs new file mode 100644 index 00000000..08f65a52 --- /dev/null +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/AccountTokenWrapper.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + + +using System.Collections.Generic; +using Microsoft.SqlTools.ResourceProvider.Core.Contracts; + +namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication +{ + /// + /// Contains an account and related token information usable for login purposes + /// + public class AccountTokenWrapper + { + public AccountTokenWrapper(Account account, Dictionary securityTokenMappings) + { + Account = account; + SecurityTokenMappings = securityTokenMappings; + } + /// + /// Account defining a connected service login + /// + public Account Account { get; private set; } + /// + /// Token mappings from tentant ID to their access token + /// + public Dictionary SecurityTokenMappings { get; private set; } + } +} diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureTenant.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureTenant.cs new file mode 100644 index 00000000..917edcc7 --- /dev/null +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureTenant.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication +{ + /// + /// User account authentication information to be used by + /// + public interface IAzureTenant + { + /// + /// The unique Id for the tenant + /// + string TenantId + { + get; + } + + /// + /// Display ID + /// + string AccountDisplayableId + { + get; + } + + } +} diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccount.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccount.cs index c02e049d..e82befa0 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccount.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Authentication/IAzureUserAccount.cs @@ -3,13 +3,14 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication { /// /// Contains information about an Azure user account /// - public interface IAzureUserAccount : IEquatable, IUserAccount + public interface IAzureUserAccount : IEquatable, IUserAccount { /// /// User Account Display Info @@ -20,11 +21,19 @@ namespace Microsoft.SqlTools.ResourceProvider.Core.Authentication } /// - /// Tenant Id + /// Primary Tenant Id /// string TenantId { get; - } + } + + /// + /// All tenant IDs + /// + IList AllTenants + { + get; + } } } diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/AuthenticationService.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/AuthenticationService.cs new file mode 100644 index 00000000..9ed88d42 --- /dev/null +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/AuthenticationService.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Threading.Tasks; +using Microsoft.SqlTools.Extensibility; +using Microsoft.SqlTools.Hosting; +using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.ResourceProvider.Core; +using Microsoft.SqlTools.ResourceProvider.Core.Authentication; +using Microsoft.SqlTools.ResourceProvider.Core.Contracts; +using Microsoft.SqlTools.ResourceProvider.Core.Extensibility; +using Microsoft.SqlTools.ResourceProvider.Core.Firewall; +using Microsoft.SqlTools.Utility; + +namespace Microsoft.SqlTools.ResourceProvider.Core +{ + + [Export(typeof(IHostedService))] + public class AuthenticationService : HostedService, IComposableService + { + /// + /// The default constructor is required for MEF-based composable services + /// + public AuthenticationService() + { + } + + public override void InitializeService(IProtocolEndpoint serviceHost) + { + Logger.Write(LogLevel.Verbose, "AuthenticationService initialized"); + } + + public async Task SetCurrentAccountAsync(Account account, Dictionary securityTokenMappings) + { + var authManager = ServiceProvider.GetService(); + // Ideally in the future, would have a factory to create the user account and tenant info without knowing + // which implementation is which. For expediency, will directly define these in this instance. + return await authManager.SetCurrentAccountAsync(new AccountTokenWrapper(account, securityTokenMappings)); + } + } + +} diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Contracts/Account.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Contracts/Account.cs new file mode 100644 index 00000000..6f85964d --- /dev/null +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Contracts/Account.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; + +namespace Microsoft.SqlTools.ResourceProvider.Core.Contracts +{ + /// + /// An object, usable in s and other messages + /// + public class Account + { + /// + /// The key that identifies the account + /// + public AccountKey Key { get; set; } + + /// + /// Display information for the account + /// + public AccountDisplayInfo DisplayInfo { get; set; } + + /// + /// Customizable properties, which will include the access token or similar authentication support + /// + public AccountProperties Properties { get; set; } + + /// + /// Indicates if the account needs refreshing + /// + public bool IsStale { get; set; } + + } + + /// + /// Azure-specific properties. Note that ideally with would reuse GeneralRequestDetails but + /// this isn't feasible right now as that is specific to having an Options property to hang it off + /// + public class AccountProperties + { + + /// + /// Is this a Microsoft account, such as live.com, or not? + /// + internal bool IsMsAccount + { + get; + set; + } + + /// + /// Tenants for each object + /// + public IEnumerable Tenants + { + get; + set; + } + + } + + /// + /// Represents a key that identifies an account. + /// + public class AccountKey + { + /// + /// Identifier of the provider + /// + public string ProviderId { get; set; } + + // Note: ignoring ProviderArgs as it's not relevant + + /// + /// Identifier for the account, unique to the provider + /// + public string AccountId { get; set; } + } + + /// + /// Represents display information for an account. + /// + public class AccountDisplayInfo + { + /// + /// A display name that offers context for the account, such as "Contoso". + /// + + public string ContextualDisplayName { get; set; } + + // Note: ignoring ContextualLogo as it's not needed + + /// + /// A display name that identifies the account, such as "user@contoso.com". + /// + /// Represents a tenant (an Azure Active Directory instance) to which a user has access + /// + public class Tenant + { + /// + /// Globally unique identifier of the tenant + /// + public string Id { get; set; } + /// + /// Display name of the tenant + /// + public string DisplayName { get; set; } + /// + /// Identifier of the user in the tenant + /// + public string UserId { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Contracts/AccountSecurityToken.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Contracts/AccountSecurityToken.cs new file mode 100644 index 00000000..25bfbd96 --- /dev/null +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Contracts/AccountSecurityToken.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; + +namespace Microsoft.SqlTools.ResourceProvider.Core.Contracts +{ + /// + /// Contains key information about a Token used to log in to a resource provider + /// + public class AccountSecurityToken + { + /// + /// Expiration time for the token + /// + public string ExpiresOn { get; set; } + + /// + /// URI defining the root for resource lookup + /// + public string Resource { get; set; } + + /// + /// The actual token + /// + public string Token { get; set; } + + /// + /// The type of token being sent - for example "Bearer" for most resource queries + /// + public string TokenType { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Contracts/FirewallRule.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Contracts/FirewallRule.cs new file mode 100644 index 00000000..28de4b42 --- /dev/null +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Contracts/FirewallRule.cs @@ -0,0 +1,102 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using Microsoft.SqlTools.Hosting.Protocol.Contracts; + +namespace Microsoft.SqlTools.ResourceProvider.Core.Contracts +{ + /// + /// A request to open up a firewall rule + /// + public class CreateFirewallRuleRequest + { + public static readonly + RequestType Type = + RequestType.Create("resource/createFirewallRule"); + } + + /// + /// A FirewallRule object, usable in s and other messages + /// + public class CreateFirewallRuleParams + { + /// + /// Account information to use in connecting to Azure + /// + public Account Account { get; set; } + /// + /// Per-tenant token mappings. Ideally would be set independently of this call, but for + /// now this allows us to get the tokens necessary to find a server and open a firewall rule + /// + public Dictionary SecurityTokenMappings { get; set; } + + /// + /// Fully qualified name of the server to create a new firewall rule on + /// + public string ServerName { get; set; } + + /// + /// Start of the IP address range + /// + public string StartIpAddress { get; set; } + + /// + /// End of the IP address range + /// + public string EndIpAddress { get; set; } + + } + + public class CreateFirewallRuleResponse + { + public bool Result { get; set; } + public string ErrorMessage { get; set; } + } + + public class CanHandleFirewallRuleRequest + { + public static readonly + RequestType Type = + RequestType.Create("resource/handleFirewallRule"); + } + + public class HandleFirewallRuleParams + { + /// + /// The error code used to defined the error type + /// + public int ErrorCode { get; set; } + /// + /// The error message from which to parse the IP address + /// + public string ErrorMessage { get; set; } + /// + /// The connection type, for example MSSQL + /// + public string ConnectionTypeId { get; set; } + } + /// + /// Response to the check for Firewall rule support given an error message + /// + public class HandleFirewallRuleResponse + { + /// + /// Can this be handled? + /// + public bool Result { get; set; } + /// + /// If not, why? + /// + public string ErrorMessage { get; set; } + /// + /// If it can be handled, is there a default IP address to send back so users + /// can tell what their blocked IP is? + /// + public string IpAddress { get; set; } + } + + +} diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResourceManager.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResourceManager.cs index 507825fb..3940392e 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResourceManager.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Discovery/IAzureResourceManager.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.SqlTools.ResourceProvider.Core.Authentication; -using Microsoft.SqlTools.ResourceProvider.Core.FirewallRule; +using Microsoft.SqlTools.ResourceProvider.Core.Firewall; using Microsoft.SqlTools.ResourceProvider.Core.Extensibility; namespace Microsoft.SqlTools.ResourceProvider.Core @@ -49,5 +49,13 @@ namespace Microsoft.SqlTools.ResourceProvider.Core ); Task CreateSessionAsync(IAzureUserAccountSubscriptionContext subscriptionContext); + + + /// + /// Gets all subscription contexts under a specific user account. Queries all tenants for the account and uses these to log in + /// and retrieve subscription information as needed + /// Account whose subscriptions should be queried + /// + Task> GetSubscriptionContextsAsync(IAzureUserAccount userAccount); } } diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallErrorParser.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallErrorParser.cs similarity index 98% rename from src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallErrorParser.cs rename to src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallErrorParser.cs index 44af4eae..cd2dfda1 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallErrorParser.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallErrorParser.cs @@ -7,7 +7,7 @@ using System.Data.SqlClient; using System.Net; using System.Text.RegularExpressions; -namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule +namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall { internal interface IFirewallErrorParser { diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallParserResponse.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallParserResponse.cs similarity index 94% rename from src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallParserResponse.cs rename to src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallParserResponse.cs index 724f7f39..de422ebe 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallParserResponse.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallParserResponse.cs @@ -4,7 +4,7 @@ using System.Net; -namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule +namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall { /// /// The response that's created by firewall rule parser diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleException.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleException.cs similarity index 98% rename from src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleException.cs rename to src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleException.cs index 772854b5..420600b3 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleException.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleException.cs @@ -6,7 +6,7 @@ using System; using System.Net; using System.Runtime.Serialization; -namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule +namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall { /// /// Exception used by firewall service to indicate when firewall rule operation fails diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleRequest.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleRequest.cs similarity index 94% rename from src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleRequest.cs rename to src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleRequest.cs index f0a46325..ee87e991 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleRequest.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleRequest.cs @@ -6,7 +6,7 @@ using System; using System.Globalization; using System.Net; -namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule +namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall { /// /// Includes all the information needed to create a firewall rule diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResource.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleResource.cs similarity index 93% rename from src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResource.cs rename to src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleResource.cs index 6681385e..97daa029 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResource.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleResource.cs @@ -4,7 +4,7 @@ using Microsoft.SqlTools.ResourceProvider.Core.Authentication; -namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule +namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall { /// /// Includes azure resource and subscription needed to create firewall rule diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResponse.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleResponse.cs similarity index 92% rename from src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResponse.cs rename to src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleResponse.cs index c90a2461..f5922899 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleResponse.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleResponse.cs @@ -2,7 +2,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule +namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall { /// /// The response that's created when the firewall rule creation request is complete diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleService.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleService.cs similarity index 96% rename from src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleService.cs rename to src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleService.cs index 9acf18a6..6d92cc75 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/FirewallRule/FirewallRuleService.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Firewall/FirewallRuleService.cs @@ -9,7 +9,7 @@ using System.Net; using System.Threading.Tasks; using Microsoft.SqlTools.ResourceProvider.Core.Authentication; -namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule +namespace Microsoft.SqlTools.ResourceProvider.Core.Firewall { public interface IFirewallRuleService @@ -89,7 +89,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule } catch (Exception ex) { - throw new FirewallRuleException(SR.FirewallRuleCreationFailed, ex); + throw new FirewallRuleException(string.Format(CultureInfo.CurrentCulture, SR.FirewallRuleCreationFailedWithError, ex.Message), ex); } } @@ -135,7 +135,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core.FirewallRule } catch (Exception ex) { - throw new FirewallRuleException(SR.FirewallRuleCreationFailed, ex); + throw new FirewallRuleException(string.Format(CultureInfo.CurrentCulture, SR.FirewallRuleCreationFailedWithError, ex.Message), ex); } return new FirewallRuleResponse() diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.cs old mode 100644 new mode 100755 index 1533eed7..0064be60 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.cs @@ -1,104 +1,120 @@ -// WARNING: -// This file was generated by the Microsoft DataWarehouse String Resource Tool 1.37.0.0 -// from information in sr.strings -// DO NOT MODIFY THIS FILE'S CONTENTS, THEY WILL BE OVERWRITTEN -// -namespace Microsoft.SqlTools.ResourceProvider.Core -{ - using System; - using System.Reflection; - using System.Resources; - using System.Globalization; +// WARNING: +// This file was generated by the Microsoft DataWarehouse String Resource Tool 1.37.0.0 +// from information in sr.strings +// DO NOT MODIFY THIS FILE'S CONTENTS, THEY WILL BE OVERWRITTEN +// +namespace Microsoft.SqlTools.ResourceProvider.Core +{ + using System; + using System.Reflection; + using System.Resources; + using System.Globalization; + + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SR + { + protected SR() + { } + + public static CultureInfo Culture + { + get + { + return Keys.Culture; + } + set + { + Keys.Culture = value; + } + } - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class SR - { - protected SR() - { } - - public static CultureInfo Culture - { - get - { - return Keys.Culture; - } - set - { - Keys.Culture = value; - } + + public static string AzureServerNotFound + { + get + { + return Keys.GetString(Keys.AzureServerNotFound); + } } - - - public static string AzureServerNotFound - { - get - { - return Keys.GetString(Keys.AzureServerNotFound); - } + + public static string AzureSubscriptionFailedErrorMessage + { + get + { + return Keys.GetString(Keys.AzureSubscriptionFailedErrorMessage); + } } - - public static string AzureSubscriptionFailedErrorMessage - { - get - { - return Keys.GetString(Keys.AzureSubscriptionFailedErrorMessage); - } + + public static string DatabaseDiscoveryFailedErrorMessage + { + get + { + return Keys.GetString(Keys.DatabaseDiscoveryFailedErrorMessage); + } } - - public static string DatabaseDiscoveryFailedErrorMessage - { - get - { - return Keys.GetString(Keys.DatabaseDiscoveryFailedErrorMessage); - } + + public static string FirewallRuleAccessForbidden + { + get + { + return Keys.GetString(Keys.FirewallRuleAccessForbidden); + } } - - public static string FirewallRuleAccessForbidden - { - get - { - return Keys.GetString(Keys.FirewallRuleAccessForbidden); - } + + public static string FirewallRuleCreationFailed + { + get + { + return Keys.GetString(Keys.FirewallRuleCreationFailed); + } } - - public static string FirewallRuleCreationFailed - { - get - { - return Keys.GetString(Keys.FirewallRuleCreationFailed); - } + + public static string FirewallRuleCreationFailedWithError + { + get + { + return Keys.GetString(Keys.FirewallRuleCreationFailedWithError); + } } - - public static string InvalidIpAddress - { - get - { - return Keys.GetString(Keys.InvalidIpAddress); - } + + public static string InvalidIpAddress + { + get + { + return Keys.GetString(Keys.InvalidIpAddress); + } } - - public static string InvalidServerTypeErrorMessage - { - get - { - return Keys.GetString(Keys.InvalidServerTypeErrorMessage); - } + + public static string InvalidServerTypeErrorMessage + { + get + { + return Keys.GetString(Keys.InvalidServerTypeErrorMessage); + } } - - public static string LoadingExportableFailedGeneralErrorMessage - { - get - { - return Keys.GetString(Keys.LoadingExportableFailedGeneralErrorMessage); - } + + public static string LoadingExportableFailedGeneralErrorMessage + { + get + { + return Keys.GetString(Keys.LoadingExportableFailedGeneralErrorMessage); + } } - - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Keys - { - static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ResourceProvider.Core.Localization.SR", typeof(SR).GetTypeInfo().Assembly); - - static CultureInfo _culture = null; + + public static string FirewallRuleUnsupportedConnectionType + { + get + { + return Keys.GetString(Keys.FirewallRuleUnsupportedConnectionType); + } + } + + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Keys + { + static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ResourceProvider.Core.Localization.SR", typeof(SR).GetTypeInfo().Assembly); + + static CultureInfo _culture = null; public const string AzureServerNotFound = "AzureServerNotFound"; @@ -116,6 +132,9 @@ namespace Microsoft.SqlTools.ResourceProvider.Core public const string FirewallRuleCreationFailed = "FirewallRuleCreationFailed"; + public const string FirewallRuleCreationFailedWithError = "FirewallRuleCreationFailedWithError"; + + public const string InvalidIpAddress = "InvalidIpAddress"; @@ -125,25 +144,28 @@ namespace Microsoft.SqlTools.ResourceProvider.Core public const string LoadingExportableFailedGeneralErrorMessage = "LoadingExportableFailedGeneralErrorMessage"; - private Keys() - { } + public const string FirewallRuleUnsupportedConnectionType = "FirewallRuleUnsupportedConnectionType"; - public static CultureInfo Culture - { - get - { - return _culture; - } - set - { - _culture = value; - } - } - - public static string GetString(string key) - { - return resourceManager.GetString(key, _culture); - } + + private Keys() + { } + + public static CultureInfo Culture + { + get + { + return _culture; + } + set + { + _culture = value; + } + } + + public static string GetString(string key) + { + return resourceManager.GetString(key, _culture); + } } } diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.resx b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.resx old mode 100644 new mode 100755 index 865b7923..1f16faa4 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.resx @@ -1,152 +1,160 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The server you specified {0} does not exist in any subscription in {1}. Either you have signed in with an incorrect account or your server was removed from subscription(s) in this account. Please check your account and try again. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The server you specified {0} does not exist in any subscription in {1}. Either you have signed in with an incorrect account or your server was removed from subscription(s) in this account. Please check your account and try again. + - - An error occurred while getting Azure subscriptions - + + An error occurred while getting Azure subscriptions + - - An error occurred while getting databases from servers of type {0} from {1} - + + An error occurred while getting databases from servers of type {0} from {1} + - - {0} does not have permission to change the server firewall rule. Try again with a different account that is an Owner or Contributor of the Azure subscription or the server. - + + {0} does not have permission to change the server firewall rule. Try again with a different account that is an Owner or Contributor of the Azure subscription or the server. + - - An error occurred while creating a new firewall rule. - + + An error occurred while creating a new firewall rule. + - - Invalid IP address - + + An error occurred while creating a new firewall rule: '{0}' + - - Server Type is invalid. - + + Invalid IP address + - - A required dll cannot be loaded. Please repair your application. - + + Server Type is invalid. + + + + A required dll cannot be loaded. Please repair your application. + + + + Cannot open a firewall rule for the specified connection type + diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.strings b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.strings index ba1bed62..d0b41403 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.strings @@ -27,6 +27,8 @@ AzureSubscriptionFailedErrorMessage = An error occurred while getting Azure subs DatabaseDiscoveryFailedErrorMessage = An error occurred while getting databases from servers of type {0} from {1} FirewallRuleAccessForbidden = {0} does not have permission to change the server firewall rule. Try again with a different account that is an Owner or Contributor of the Azure subscription or the server. FirewallRuleCreationFailed = An error occurred while creating a new firewall rule. +FirewallRuleCreationFailedWithError = An error occurred while creating a new firewall rule: '{0}' InvalidIpAddress = Invalid IP address InvalidServerTypeErrorMessage = Server Type is invalid. LoadingExportableFailedGeneralErrorMessage = A required dll cannot be loaded. Please repair your application. +FirewallRuleUnsupportedConnectionType = Cannot open a firewall rule for the specified connection type \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.xlf b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.xlf index 3ab7cbb5..3e061f47 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/Localization/sr.xlf @@ -42,6 +42,16 @@ An error occurred while creating a new firewall rule. + + Cannot open a firewall rule for the specified connection type + Cannot open a firewall rule for the specified connection type + + + + An error occurred while creating a new firewall rule: '{0}' + An error occurred while creating a new firewall rule: '{0}' + + \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ResourceProvider.Core/ResourceProviderService.cs b/src/Microsoft.SqlTools.ResourceProvider.Core/ResourceProviderService.cs new file mode 100644 index 00000000..3a93cb13 --- /dev/null +++ b/src/Microsoft.SqlTools.ResourceProvider.Core/ResourceProviderService.cs @@ -0,0 +1,119 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Composition; +using System.Threading.Tasks; +using Microsoft.SqlTools.Extensibility; +using Microsoft.SqlTools.Hosting; +using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.ResourceProvider.Core.Authentication; +using Microsoft.SqlTools.ResourceProvider.Core.Contracts; +using Microsoft.SqlTools.ResourceProvider.Core.Firewall; +using Microsoft.SqlTools.Utility; + +namespace Microsoft.SqlTools.ResourceProvider.Core +{ + + [Export(typeof(IHostedService))] + public class ResourceProviderService : HostedService, IComposableService + { + private const string MssqlProviderId = "MSSQL"; + + private FirewallRuleService firewallRuleService; + /// + /// The default constructor is required for MEF-based composable services + /// + public ResourceProviderService() + { + } + + public override void InitializeService(IProtocolEndpoint serviceHost) + { + Logger.Write(LogLevel.Verbose, "ResourceProvider initialized"); + serviceHost.SetRequestHandler(CreateFirewallRuleRequest.Type, HandleCreateFirewallRuleRequest); + serviceHost.SetRequestHandler(CanHandleFirewallRuleRequest.Type, ProcessHandleFirewallRuleRequest); + + firewallRuleService = new FirewallRuleService() + { + AuthenticationManager = ServiceProvider.GetService(), + ResourceManager = ServiceProvider.GetService() + }; + } + + /// + /// Handles a firewall rule creation request. It does this by matching the server name to an Azure Server resource, + /// then issuing the command to create a new firewall rule for the specified IP address against that instance + /// + /// + /// + /// + public async Task HandleCreateFirewallRuleRequest(CreateFirewallRuleParams firewallRule, RequestContext requestContext) + { + Func> requestHandler = () => + { + return DoHandleCreateFirewallRuleRequest(firewallRule); + }; + await HandleRequest(requestHandler, requestContext, "HandleCreateFirewallRuleRequest"); + } + + private async Task DoHandleCreateFirewallRuleRequest(CreateFirewallRuleParams firewallRule) + { + var result = new CreateFirewallRuleResponse(); + // Note: currently not catching the exception. Expect the caller to this message to handle error cases by + // showing the error string and responding with a clean failure message to the user + try + { + AuthenticationService authService = ServiceProvider.GetService(); + IUserAccount account = await authService.SetCurrentAccountAsync(firewallRule.Account, firewallRule.SecurityTokenMappings); + FirewallRuleResponse response = await firewallRuleService.CreateFirewallRuleAsync(firewallRule.ServerName, firewallRule.StartIpAddress, firewallRule.EndIpAddress); + result.Result = true; + } + catch(FirewallRuleException ex) + { + result.Result = false; + result.ErrorMessage = ex.Message; + } + return result; + } + + public async Task ProcessHandleFirewallRuleRequest(HandleFirewallRuleParams canHandleRuleParams, RequestContext requestContext) + { + Func> requestHandler = () => + { + HandleFirewallRuleResponse response = new HandleFirewallRuleResponse(); + if (!MssqlProviderId.Equals(canHandleRuleParams.ConnectionTypeId, StringComparison.OrdinalIgnoreCase)) + { + response.Result = false; + response.ErrorMessage = SR.FirewallRuleUnsupportedConnectionType; + } + else + { + FirewallErrorParser parser = new FirewallErrorParser(); + FirewallParserResponse parserResponse = parser.ParseErrorMessage(canHandleRuleParams.ErrorMessage, canHandleRuleParams.ErrorCode); + response.Result = parserResponse.FirewallRuleErrorDetected; + response.IpAddress = parserResponse.BlockedIpAddress != null ? parserResponse.BlockedIpAddress.ToString() : string.Empty; + } + return Task.FromResult(response); + }; + await HandleRequest(requestHandler, requestContext, "HandleCreateFirewallRuleRequest"); + } + + private async Task HandleRequest(Func> handler, RequestContext requestContext, string requestType) + { + Logger.Write(LogLevel.Verbose, requestType); + + try + { + T result = await handler(); + await requestContext.SendResult(result); + } + catch (Exception ex) + { + await requestContext.SendError(ex.ToString()); + } + } + } +} diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureAuthenticationManager.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureAuthenticationManager.cs new file mode 100644 index 00000000..3c212593 --- /dev/null +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureAuthenticationManager.cs @@ -0,0 +1,316 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.SqlTools.ResourceProvider.Core; +using Microsoft.SqlTools.ResourceProvider.Core.Authentication; +using Microsoft.SqlTools.ResourceProvider.Core.Contracts; +using Microsoft.SqlTools.ResourceProvider.Core.Extensibility; +using Microsoft.SqlTools.Utility; + +namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl +{ + + /// + /// Implementation for . + /// Provides functionality to authenticate to Azure and discover associated accounts and subscriptions + /// + [Exportable( + ServerTypes.SqlServer, + Categories.Azure, + typeof(IAzureAuthenticationManager), + "Microsoft.SqlTools.ResourceProvider.DefaultImpl.AzureAuthenticationManager", + 1) + ] + class AzureAuthenticationManager : ExportableBase, IAzureAuthenticationManager + { + private Dictionary accountsMap; + private string currentAccountId = null; + private IEnumerable _selectedSubscriptions = null; + private readonly object _selectedSubscriptionsLockObject = new object(); + private readonly ConcurrentCache> _subscriptionCache = + new ConcurrentCache>(); + + + public AzureAuthenticationManager() + { + Metadata = new ExportableMetadata( + ServerTypes.SqlServer, + Categories.Azure, + "Microsoft.SqlTools.ResourceProvider.DefaultImpl.AzureAuthenticationManager", + 1); + accountsMap = new Dictionary(); + + } + + + public IEnumerable UserAccounts + { + get { return accountsMap.Values; } + } + + public bool HasLoginDialog + { + get { return false; } + } + + /// + /// Set current logged in user + /// + public async Task SetCurrentAccountAsync(object account) + { + CommonUtil.CheckForNull(account, nameof(account)); + AccountTokenWrapper accountTokenWrapper = account as AccountTokenWrapper; + if (accountTokenWrapper != null) + { + AzureUserAccount userAccount = CreateUserAccount(accountTokenWrapper); + accountsMap[userAccount.UniqueId] = userAccount; + currentAccountId = userAccount.UniqueId; + } + else + { + throw new ServiceFailedException(string.Format(CultureInfo.CurrentCulture, SR.UnsupportedAuthType, account.GetType().Name)); + } + OnCurrentAccountChanged(); + return await GetCurrentAccountAsync(); + } + + /// + /// Public for testing purposes. Creates an Azure account with the correct set of mappings for tenants etc. + /// + /// + /// + public AzureUserAccount CreateUserAccount(AccountTokenWrapper accountTokenWrapper) + { + Account account = accountTokenWrapper.Account; + CommonUtil.CheckForNull(accountTokenWrapper.Account, nameof(account)); + CommonUtil.CheckForNull(accountTokenWrapper.SecurityTokenMappings, nameof(account) + ".SecurityTokenMappings"); + AzureUserAccount userAccount = new AzureUserAccount(); + userAccount.UniqueId = account.Key.AccountId; + userAccount.DisplayInfo = ToDisplayInfo(account); + IList tenants = new List(); + foreach (Tenant tenant in account.Properties.Tenants) + { + AccountSecurityToken token; + if (accountTokenWrapper.SecurityTokenMappings.TryGetValue(tenant.Id, out token)) + { + AzureTenant azureTenant = new AzureTenant() + { + TenantId = tenant.Id, + AccountDisplayableId = tenant.DisplayName, + Resource = token.Resource, + AccessToken = token.Token, + TokenType = token.TokenType + }; + tenants.Add(azureTenant); + } + // else ignore for now as we can't handle a request to get a tenant without an access key + } + userAccount.AllTenants = tenants; + return userAccount; + } + + private AzureUserAccountDisplayInfo ToDisplayInfo(Account account) + { + return new AzureUserAccountDisplayInfo() + { + AccountDisplayName = account.DisplayInfo.DisplayName, + ProviderDisplayName = account.Key.ProviderId + }; + } + + private void OnCurrentAccountChanged() + { + lock (_selectedSubscriptionsLockObject) + { + _selectedSubscriptions = null; + } + if (CurrentAccountChanged != null) + { + CurrentAccountChanged(this, new EventArgs()); + } + } + + /// + /// The event to be raised when the current account is changed + /// + public event EventHandler CurrentAccountChanged; + + public Task AddUserAccountAsync() + { + throw new NotImplementedException(); + } + + public Task AuthenticateAsync() + { + throw new NotImplementedException(); + } + + public async Task GetCurrentAccountAsync() + { + var account = await GetCurrentAccountInternalAsync(); + return account; + } + + private Task GetCurrentAccountInternalAsync() + { + + AzureUserAccount account = null; + if (currentAccountId != null + && accountsMap.TryGetValue(currentAccountId, out account)) + { + // TODO is there more needed here? + } + return Task.FromResult(account); + } + + public async Task> GetSelectedSubscriptionsAsync() + { + return _selectedSubscriptions ?? await GetSubscriptionsAsync(); + } + + /// + /// Returns user's subscriptions + /// + public async Task> GetSubscriptionsAsync() + { + var result = Enumerable.Empty(); + bool userNeedsAuthentication = await GetUserNeedsReauthenticationAsync(); + if (!userNeedsAuthentication) + { + AzureUserAccount currentUser = await GetCurrentAccountInternalAsync(); + if (currentUser != null) + { + try + { + result = await GetSubscriptionsFromCacheAsync(currentUser); + } + catch (ServiceExceptionBase) + { + throw; + } + catch (Exception ex) + { + throw new ServiceFailedException(SR.AzureSubscriptionFailedErrorMessage, ex); + } + } + result = result ?? Enumerable.Empty(); + } + return result; + } + + private async Task> GetSubscriptionsFromCacheAsync(AzureUserAccount user) + { + var result = Enumerable.Empty(); + + if (user != null) + { + result = _subscriptionCache.Get(user.UniqueId); + if (result == null) + { + result = await GetSubscriptionFromServiceAsync(user); + _subscriptionCache.UpdateCache(user.UniqueId, result); + } + } + result = result ?? Enumerable.Empty(); + return result; + } + + private async Task> GetSubscriptionFromServiceAsync(AzureUserAccount userAccount) + { + List subscriptionList = new List(); + + try + { + if (userAccount != null && !userAccount.NeedsReauthentication) + { + IAzureResourceManager resourceManager = ServiceProvider.GetService(); + IEnumerable contexts = await resourceManager.GetSubscriptionContextsAsync(userAccount); + subscriptionList = contexts.ToList(); + } + else + { + throw new UserNeedsAuthenticationException(SR.AzureSubscriptionFailedErrorMessage); + } + } + // TODO handle stale tokens + //catch (MissingSecurityTokenException missingSecurityTokenException) + //{ + // //User needs to reauthenticate + // if (userAccount != null) + // { + // userAccount.NeedsReauthentication = true; + // } + // throw new UserNeedsAuthenticationException(SR.AzureSubscriptionFailedErrorMessage, missingSecurityTokenException); + //} + catch (ServiceExceptionBase) + { + throw; + } + catch (Exception ex) + { + throw new ServiceFailedException(SR.AzureSubscriptionFailedErrorMessage, ex); + } + return subscriptionList; + } + + + public Task GetUserNeedsReauthenticationAsync() + { + // for now, we don't support handling stale auth objects + return Task.FromResult(false); + } + + /// + /// Stores the selected subscriptions given the ids + /// + public async Task SetSelectedSubscriptionsAsync(IEnumerable subscriptionIds) + { + IEnumerable subscriptions = await GetSubscriptionsAsync(); + List subscriptionList = subscriptions.ToList(); + + List newSelectedSubscriptions = subscriptionIds == null + ? subscriptionList + : subscriptionList.Where(x => subscriptionIds.Contains(x.Subscription.SubscriptionId)).ToList(); + + //If the current account changes during setting selected subscription, none of the ids should be found + //so we just reset the selected subscriptions + if (subscriptionIds != null && subscriptionIds.Any() && newSelectedSubscriptions.Count == 0) + { + newSelectedSubscriptions = subscriptionList; + } + lock (_selectedSubscriptionsLockObject) + { + if (!SelectedSubscriptionsEquals(newSelectedSubscriptions)) + { + _selectedSubscriptions = newSelectedSubscriptions; + return true; + } + } + return false; + } + + private bool SelectedSubscriptionsEquals(List newSelectedSubscriptions) + { + if (_selectedSubscriptions != null && _selectedSubscriptions.Count() == newSelectedSubscriptions.Count) + { + return newSelectedSubscriptions.All(subscription => _selectedSubscriptions.Contains(subscription)); + } + return false; + } + + /// + /// Tries to find a subscription given subscription id + /// + public bool TryParseSubscriptionIdentifier(string value, out IAzureSubscriptionIdentifier subscription) + { + // TODO can port this over from the VS implementation if needed, but for now disabling as we don't serialize / deserialize subscriptions + throw new NotImplementedException(); + } + } +} diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceManager.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceManager.cs index af7dbb0d..b810bb73 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceManager.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceManager.cs @@ -13,13 +13,14 @@ using Microsoft.Azure.Management.Sql; using Microsoft.Azure.Management.Sql.Models; using RestFirewallRule = Microsoft.Azure.Management.Sql.Models.FirewallRule; using Microsoft.SqlTools.ResourceProvider.Core.Authentication; -using Microsoft.SqlTools.ResourceProvider.Core.FirewallRule; +using Microsoft.SqlTools.ResourceProvider.Core.Firewall; using Microsoft.SqlTools.ResourceProvider.Core.Extensibility; using Microsoft.SqlTools.Utility; using Microsoft.Rest; using System.Globalization; using Microsoft.Rest.Azure; using Microsoft.SqlTools.ResourceProvider.Core; +using System.Collections; namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl { @@ -31,7 +32,7 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl ServerTypes.SqlServer, Categories.Azure, typeof(IAzureResourceManager), - "Microsoft.SqlServer.ConnectionServices.Azure.Impl.VsAzureResourceManager", + "Microsoft.SqlTools.ResourceProvider.DefaultImpl.AzureResourceManager", 1) ] public class AzureResourceManager : ExportableBase, IAzureResourceManager @@ -45,18 +46,24 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl Metadata = new ExportableMetadata( ServerTypes.SqlServer, Categories.Azure, - "Microsoft.SqlServer.ConnectionServices.Azure.Impl.VsAzureResourceManager"); + "Microsoft.SqlTools.ResourceProvider.DefaultImpl.AzureResourceManager"); } - public async Task CreateSessionAsync(IAzureUserAccountSubscriptionContext subscriptionContext) + public Task CreateSessionAsync(IAzureUserAccountSubscriptionContext subscriptionContext) { CommonUtil.CheckForNull(subscriptionContext, "subscriptionContext"); try { - ServiceClientCredentials credentials = await CreateCredentialsAsync(subscriptionContext); - SqlManagementClient sqlManagementClient = new SqlManagementClient(_resourceManagementUri, credentials); - ResourceManagementClient resourceManagementClient = new ResourceManagementClient(_resourceManagementUri, credentials); - return new AzureResourceManagementSession(sqlManagementClient, resourceManagementClient, subscriptionContext); + ServiceClientCredentials credentials = CreateCredentials(subscriptionContext); + SqlManagementClient sqlManagementClient = new SqlManagementClient(credentials) + { + SubscriptionId = subscriptionContext.Subscription.SubscriptionId + }; + ResourceManagementClient resourceManagementClient = new ResourceManagementClient(credentials) + { + SubscriptionId = subscriptionContext.Subscription.SubscriptionId + }; + return Task.FromResult(new AzureResourceManagementSession(sqlManagementClient, resourceManagementClient, subscriptionContext)); } catch (Exception ex) { @@ -120,27 +127,29 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl AzureResourceManagementSession vsAzureResourceManagementSession = azureResourceManagementSession as AzureResourceManagementSession; if(vsAzureResourceManagementSession != null) { - IEnumerable resourceGroupNames = await GetResourceGroupsAsync(vsAzureResourceManagementSession); - if (resourceGroupNames != null) + // Note: Ideally wouldn't need to query resource groups, but the current impl requires it + // since any update will need the resource group name and it's not returned from the server. + // This has a very negative impact on perf, so we should investigate running these queries + // in parallel + + try { - foreach (ResourceGroup resourceGroupExtended in resourceGroupNames) + IServersOperations serverOperations = vsAzureResourceManagementSession.SqlManagementClient.Servers; + IPage servers = await serverOperations.ListAsync(); + if (servers != null) { - try - { - IServersOperations serverOperations = vsAzureResourceManagementSession.SqlManagementClient.Servers; - IPage servers = await serverOperations.ListByResourceGroupAsync(resourceGroupExtended.Name); - if (servers != null) - { - sqlServers.AddRange(servers.Select(x => - new SqlAzureResource(x) { ResourceGroupName = resourceGroupExtended.Name })); - } - } - catch (HttpOperationException ex) - { - throw new AzureResourceFailedException(SR.FailedToGetAzureSqlServersErrorMessage, ex.Response.StatusCode); - } + sqlServers.AddRange(servers.Select(server => { + var serverResource = new SqlAzureResource(server); + // TODO ResourceGroup name + return serverResource; + })); } } + catch (HttpOperationException ex) + { + throw new AzureResourceFailedException( + string.Format(CultureInfo.CurrentCulture, SR.FailedToGetAzureSqlServersWithError, ex.Message), ex.Response.StatusCode); + } } } catch(Exception ex) @@ -175,21 +184,24 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl StartIpAddress = firewallRuleRequest.StartIpAddress.ToString() }; IFirewallRulesOperations firewallRuleOperations = vsAzureResourceManagementSession.SqlManagementClient.FirewallRules; - var firewallRuleResponse = await firewallRuleOperations.CreateOrUpdateAsync( - azureSqlServer.ResourceGroupName, + var firewallRuleResponse = await firewallRuleOperations.CreateOrUpdateWithHttpMessagesAsync( + azureSqlServer.ResourceGroupName ?? string.Empty, azureSqlServer.Name, firewallRuleRequest.FirewallRuleName, - firewallRule); + firewallRule, + GetCustomHeaders()); + var response = firewallRuleResponse.Body; return new FirewallRuleResponse() { - StartIpAddress = firewallRuleResponse.StartIpAddress, - EndIpAddress = firewallRuleResponse.EndIpAddress, + StartIpAddress = response.StartIpAddress, + EndIpAddress = response.EndIpAddress, Created = true }; } catch (HttpOperationException ex) { - throw new AzureResourceFailedException(SR.FirewallRuleCreationFailed, ex.Response.StatusCode); + throw new AzureResourceFailedException( + string.Format(CultureInfo.CurrentCulture, SR.FirewallRuleCreationFailedWithError, ex.Message), ex.Response.StatusCode); } } // else respond with failure case @@ -200,11 +212,22 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl } catch (Exception ex) { - TraceException(TraceEventType.Error, (int) TraceId.AzureResource, ex, "Failed to get databases"); + TraceException(TraceEventType.Error, (int) TraceId.AzureResource, ex, "Failed to create firewall rule"); throw; } } + private Dictionary> GetCustomHeaders() + { + // For some unknown reason the firewall rule method defaults to returning XML. Fixes this by adding an Accept header + // ensuring it's always JSON + var headers = new Dictionary>(); + headers["Accept"] = new List() { + "application/json" + }; + return headers; + } + /// /// Returns the azure resource groups for given subscription /// @@ -226,7 +249,7 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl } catch (HttpOperationException ex) { - throw new AzureResourceFailedException(SR.FailedToGetAzureResourceGroupsErrorMessage, ex.Response.StatusCode); + throw new AzureResourceFailedException(string.Format(CultureInfo.CurrentCulture, SR.FailedToGetAzureResourceGroupsErrorMessage, ex.Message), ex.Response.StatusCode); } } @@ -239,18 +262,104 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl } } + /// + /// Gets all subscription contexts under a specific user account. Queries all tenants for the account and uses these to log in + /// and retrieve subscription information as needed + /// + public async Task> GetSubscriptionContextsAsync(IAzureUserAccount userAccount) + { + List contexts = new List(); + foreach (IAzureTenant tenant in userAccount.AllTenants) + { + AzureTenant azureTenant = tenant as AzureTenant; + if (azureTenant != null) + { + ServiceClientCredentials credentials = CreateCredentials(azureTenant); + using (SubscriptionClient client = new SubscriptionClient(_resourceManagementUri, credentials)) + { + IEnumerable subs = await GetSubscriptionsAsync(client); + contexts.AddRange(subs.Select(sub => + { + AzureSubscriptionIdentifier subId = new AzureSubscriptionIdentifier(userAccount, azureTenant.TenantId, sub.SubscriptionId, _resourceManagementUri); + AzureUserAccountSubscriptionContext context = new AzureUserAccountSubscriptionContext(subId, credentials); + return context; + })); + } + } + } + return contexts; + } + + /// + /// Returns the azure resource groups for given subscription + /// + private async Task> GetSubscriptionsAsync(SubscriptionClient subscriptionClient) + { + try + { + if (subscriptionClient != null) + { + try + { + ISubscriptionsOperations subscriptionsOperations = subscriptionClient.Subscriptions; + IPage subscriptionList = await subscriptionsOperations.ListAsync(); + if (subscriptionList != null) + { + return subscriptionList.AsEnumerable(); + } + + } + catch (HttpOperationException ex) + { + throw new AzureResourceFailedException( + string.Format(CultureInfo.CurrentCulture, SR.AzureSubscriptionFailedErrorMessage, ex.Message), ex.Response.StatusCode); + } + } + + return Enumerable.Empty(); + } + catch (Exception ex) + { + TraceException(TraceEventType.Error, (int)TraceId.AzureResource, ex, "Failed to get azure resource groups"); + throw; + } + } /// /// Creates credential instance for given subscription /// - private Task CreateCredentialsAsync(IAzureUserAccountSubscriptionContext subscriptionContext) + private ServiceClientCredentials CreateCredentials(IAzureTenant tenant) + { + AzureTenant azureTenant = tenant as AzureTenant; + + if (azureTenant != null) + { + TokenCredentials credentials; + if (!string.IsNullOrWhiteSpace(azureTenant.TokenType)) + { + credentials = new TokenCredentials(azureTenant.AccessToken, azureTenant.TokenType); + } + else + { + credentials = new TokenCredentials(azureTenant.AccessToken); + } + + return credentials; + } + throw new NotSupportedException("This uses an unknown subscription type"); + } + + /// + /// Creates credential instance for given subscription + /// + private ServiceClientCredentials CreateCredentials(IAzureUserAccountSubscriptionContext subscriptionContext) { AzureUserAccountSubscriptionContext azureUserSubContext = subscriptionContext as AzureUserAccountSubscriptionContext; if (azureUserSubContext != null) { - return Task.FromResult(azureUserSubContext.Credentials); + return azureUserSubContext.Credentials; } throw new NotSupportedException("This uses an unknown subscription type"); } diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceWrapper.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceWrapper.cs index 6b0f5bdb..eda2ec1f 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceWrapper.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureResourceWrapper.cs @@ -14,6 +14,9 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl /// public class AzureResourceWrapper : IAzureResource { + public const string ResourceGroupsPart = "resourceGroups"; + private string resourceGroupName; + /// /// Initializes the resource /// @@ -71,7 +74,39 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl /// /// Resource Group Name /// - public string ResourceGroupName { get; set; } + public string ResourceGroupName { + get + { + if (this.resourceGroupName == null) + { + this.resourceGroupName = ParseResourceGroupNameFromId(); + } + return this.resourceGroupName; + } + set + { + this.resourceGroupName = value; + } + } + + private string ParseResourceGroupNameFromId() + { + if (!string.IsNullOrEmpty(Id)) + { + string[] idParts = Id.Split('/'); + + // Look for the "resourceGroups" section and return the section after this, hence + // always stop before idParts.Length - 1 + for (int i = 0; i < idParts.Length - 1; i++) + { + if (string.Compare(idParts[i], ResourceGroupsPart, StringComparison.OrdinalIgnoreCase) == 0) + { + return idParts[i + 1]; + } + } + } + return null; + } /// /// Resource Location diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureSubscriptionIdentifier.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureSubscriptionIdentifier.cs index 52b2b949..997ca9ba 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureSubscriptionIdentifier.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureSubscriptionIdentifier.cs @@ -17,9 +17,10 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl /// /// Default constructor to initialize the subscription identifier /// - public AzureSubscriptionIdentifier(IAzureUserAccount userAccount, string subscriptionId, Uri serviceManagementEndpoint) + public AzureSubscriptionIdentifier(IAzureUserAccount userAccount, string tenantId, string subscriptionId, Uri serviceManagementEndpoint) { UserAccount = userAccount; + TenantId = tenantId; SubscriptionId = subscriptionId; ServiceManagementEndpoint = serviceManagementEndpoint; } @@ -56,6 +57,15 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl { get; private set; - } + } + + /// + /// The ID of the tenant this subscription comes from + /// + public string TenantId + { + get; + private set; + } } } diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureTenant.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureTenant.cs new file mode 100644 index 00000000..e20b2a6b --- /dev/null +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureTenant.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.SqlTools.ResourceProvider.Core; +using Microsoft.SqlTools.ResourceProvider.Core.Authentication; + +namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl +{ + /// + /// Implementation for using VS services + /// Contains information about an Azure account + /// + public class AzureTenant : IAzureTenant + { + public string TenantId + { + get; + set; + } + + public string AccountDisplayableId + { + get; + set; + } + + + /// + /// URI defining the root for resource lookup + /// + public string Resource { get; set; } + + + /// + /// Access token for use in login scenarios. Note that we could consider implementing this better in the + /// + public string AccessToken + { + get; + set; + } + + /// + /// Optional token type defining whether this is a Bearer token or other type of token + /// + public string TokenType + { + get; + set; + } + } +} diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccount.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccount.cs index 075dd9ca..7b559954 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccount.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/AzureUserAccount.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using Microsoft.SqlTools.ResourceProvider.Core; using Microsoft.SqlTools.ResourceProvider.Core.Authentication; @@ -36,7 +37,9 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl this.DisplayInfo = new AzureUserAccountDisplayInfo(azureUserAccount.DisplayInfo); this.NeedsReauthentication = azureUserAccount.NeedsReauthentication; this.TenantId = azureUserAccount.TenantId; + this.AllTenants = azureUserAccount.AllTenants; this.UniqueId = azureUserAccount.UniqueId; + AzureUserAccount account = azureUserAccount as AzureUserAccount; } /// /// Returns true if given user account equals this class @@ -46,6 +49,7 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl return other != null && CommonUtil.SameString(other.UniqueId, UniqueId) && CommonUtil.SameString(other.TenantId, TenantId); + // TODO probably should check the AllTenants field } /// @@ -88,6 +92,12 @@ namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl { get; set; - } + } + + public IList AllTenants + { + get; + set; + } } } diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.cs b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.cs old mode 100644 new mode 100755 index b052d9e7..a7abd08c --- a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.cs @@ -1,105 +1,160 @@ -// WARNING: -// This file was generated by the Microsoft DataWarehouse String Resource Tool 1.37.0.0 -// from information in sr.strings -// DO NOT MODIFY THIS FILE'S CONTENTS, THEY WILL BE OVERWRITTEN -// -namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl -{ - using System; - using System.Reflection; - using System.Resources; - using System.Globalization; +// WARNING: +// This file was generated by the Microsoft DataWarehouse String Resource Tool 1.37.0.0 +// from information in sr.strings +// DO NOT MODIFY THIS FILE'S CONTENTS, THEY WILL BE OVERWRITTEN +// +namespace Microsoft.SqlTools.ResourceProvider.DefaultImpl +{ + using System; + using System.Reflection; + using System.Resources; + using System.Globalization; + + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SR + { + protected SR() + { } + + public static CultureInfo Culture + { + get + { + return Keys.Culture; + } + set + { + Keys.Culture = value; + } + } - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class SR - { - protected SR() - { } - - public static CultureInfo Culture - { - get - { - return Keys.Culture; - } - set - { - Keys.Culture = value; - } + + public static string FailedToGetAzureDatabasesErrorMessage + { + get + { + return Keys.GetString(Keys.FailedToGetAzureDatabasesErrorMessage); + } } - - - public static string FailedToGetAzureDatabasesErrorMessage - { - get - { - return Keys.GetString(Keys.FailedToGetAzureDatabasesErrorMessage); - } + + public static string FailedToGetAzureSubscriptionsErrorMessage + { + get + { + return Keys.GetString(Keys.FailedToGetAzureSubscriptionsErrorMessage); + } } - - public static string FailedToGetAzureResourceGroupsErrorMessage - { - get - { - return Keys.GetString(Keys.FailedToGetAzureResourceGroupsErrorMessage); - } + + public static string FailedToGetAzureResourceGroupsErrorMessage + { + get + { + return Keys.GetString(Keys.FailedToGetAzureResourceGroupsErrorMessage); + } } - - public static string FailedToGetAzureSqlServersErrorMessage - { - get - { - return Keys.GetString(Keys.FailedToGetAzureSqlServersErrorMessage); - } + + public static string FailedToGetAzureSqlServersErrorMessage + { + get + { + return Keys.GetString(Keys.FailedToGetAzureSqlServersErrorMessage); + } } - - public static string FirewallRuleCreationFailed - { - get - { - return Keys.GetString(Keys.FirewallRuleCreationFailed); - } + + public static string FailedToGetAzureSqlServersWithError + { + get + { + return Keys.GetString(Keys.FailedToGetAzureSqlServersWithError); + } } - - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Keys - { - static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ResourceProvider.DefaultImpl.Localization.SR", typeof(SR).GetTypeInfo().Assembly); - - static CultureInfo _culture = null; + + public static string FirewallRuleCreationFailed + { + get + { + return Keys.GetString(Keys.FirewallRuleCreationFailed); + } + } + + public static string FirewallRuleCreationFailedWithError + { + get + { + return Keys.GetString(Keys.FirewallRuleCreationFailedWithError); + } + } + + public static string AzureSubscriptionFailedErrorMessage + { + get + { + return Keys.GetString(Keys.AzureSubscriptionFailedErrorMessage); + } + } + + public static string UnsupportedAuthType + { + get + { + return Keys.GetString(Keys.UnsupportedAuthType); + } + } + + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Keys + { + static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ResourceProvider.DefaultImpl.Localization.SR", typeof(SR).GetTypeInfo().Assembly); + + static CultureInfo _culture = null; public const string FailedToGetAzureDatabasesErrorMessage = "FailedToGetAzureDatabasesErrorMessage"; + public const string FailedToGetAzureSubscriptionsErrorMessage = "FailedToGetAzureSubscriptionsErrorMessage"; + + public const string FailedToGetAzureResourceGroupsErrorMessage = "FailedToGetAzureResourceGroupsErrorMessage"; public const string FailedToGetAzureSqlServersErrorMessage = "FailedToGetAzureSqlServersErrorMessage"; + public const string FailedToGetAzureSqlServersWithError = "FailedToGetAzureSqlServersWithError"; + + public const string FirewallRuleCreationFailed = "FirewallRuleCreationFailed"; - private Keys() - { } + public const string FirewallRuleCreationFailedWithError = "FirewallRuleCreationFailedWithError"; - public static CultureInfo Culture - { - get - { - return _culture; - } - set - { - _culture = value; - } - } - public static string GetString(string key) - { - return resourceManager.GetString(key, _culture); - } + public const string AzureSubscriptionFailedErrorMessage = "AzureSubscriptionFailedErrorMessage"; + + + public const string UnsupportedAuthType = "UnsupportedAuthType"; + + + private Keys() + { } + + public static CultureInfo Culture + { + get + { + return _culture; + } + set + { + _culture = value; + } + } + + public static string GetString(string key) + { + return resourceManager.GetString(key, _culture); + } } } diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.resx b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.resx old mode 100644 new mode 100755 index d44a779c..2130cb0b --- a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.resx @@ -1,136 +1,156 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - An error occurred while getting Azure databases - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + An error occurred while getting Azure databases + - - An error occurred while getting Azure resource groups - + + An error occurred while getting Azure subscriptions: {0} + - - An error occurred while getting Azure Sql Servers - + + An error occurred while getting Azure resource groups: {0} + - - An error occurred while creating a new firewall rule. - + + An error occurred while getting Azure Sql Servers + + + + An error occurred while getting Azure Sql Servers: '{0}' + + + + An error occurred while creating a new firewall rule. + + + + An error occurred while creating a new firewall rule: '{0}' + + + + An error occurred while getting Azure subscriptions + + + + Unsupported account type '{0}' for this provider + diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.strings b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.strings index 3e87c835..b8e5ba2d 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.strings @@ -23,6 +23,11 @@ ############################################################################ # Azure Core DLL FailedToGetAzureDatabasesErrorMessage = An error occurred while getting Azure databases -FailedToGetAzureResourceGroupsErrorMessage = An error occurred while getting Azure resource groups +FailedToGetAzureSubscriptionsErrorMessage = An error occurred while getting Azure subscriptions: {0} +FailedToGetAzureResourceGroupsErrorMessage = An error occurred while getting Azure resource groups: {0} FailedToGetAzureSqlServersErrorMessage = An error occurred while getting Azure Sql Servers -FirewallRuleCreationFailed = An error occurred while creating a new firewall rule. \ No newline at end of file +FailedToGetAzureSqlServersWithError = An error occurred while getting Azure Sql Servers: '{0}' +FirewallRuleCreationFailed = An error occurred while creating a new firewall rule. +FirewallRuleCreationFailedWithError = An error occurred while creating a new firewall rule: '{0}' +AzureSubscriptionFailedErrorMessage = An error occurred while getting Azure subscriptions +UnsupportedAuthType = Unsupported account type '{0}' for this provider \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.xlf b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.xlf index 0c54fb77..41c18ae0 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Localization/sr.xlf @@ -8,7 +8,7 @@ - An error occurred while getting Azure resource groups + An error occurred while getting Azure resource groups: {0} An error occurred while getting Azure resource groups @@ -22,6 +22,31 @@ An error occurred while creating a new firewall rule. + + An error occurred while getting Azure subscriptions + An error occurred while getting Azure subscriptions + + + + Unsupported account type '{0}' for this provider + Unsupported account type '{0}' for this provider + + + + An error occurred while getting Azure Sql Servers: '{0}' + An error occurred while getting Azure Sql Servers: '{0}' + + + + An error occurred while creating a new firewall rule: '{0}' + An error occurred while creating a new firewall rule: '{0}' + + + + An error occurred while getting Azure subscriptions: {0} + An error occurred while getting Azure subscriptions: {0} + + \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj index 0c4a8845..2a8c92be 100644 --- a/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj +++ b/src/Microsoft.SqlTools.ResourceProvider.DefaultImpl/Microsoft.SqlTools.ResourceProvider.DefaultImpl.csproj @@ -3,12 +3,12 @@ netstandard2.0 Microsoft.SqlTools.ResourceProvider.DefaultImpl Microsoft.SqlTools.ResourceProvider.DefaultImpl - false - - Library - - Provides the default for SqlTools applications. - � Microsoft Corporation. All rights reserved. + false + + Library + + Provides the default for SqlTools applications. + � Microsoft Corporation. All rights reserved. @@ -18,7 +18,7 @@ - + diff --git a/src/Microsoft.SqlTools.ResourceProvider/Microsoft.SqlTools.ResourceProvider.csproj b/src/Microsoft.SqlTools.ResourceProvider/Microsoft.SqlTools.ResourceProvider.csproj index d1e9e31e..208d0955 100644 --- a/src/Microsoft.SqlTools.ResourceProvider/Microsoft.SqlTools.ResourceProvider.csproj +++ b/src/Microsoft.SqlTools.ResourceProvider/Microsoft.SqlTools.ResourceProvider.csproj @@ -20,6 +20,11 @@ + + + diff --git a/src/Microsoft.SqlTools.ResourceProvider/Program.cs b/src/Microsoft.SqlTools.ResourceProvider/Program.cs index aa2d2d42..bc0c212d 100644 --- a/src/Microsoft.SqlTools.ResourceProvider/Program.cs +++ b/src/Microsoft.SqlTools.ResourceProvider/Program.cs @@ -11,11 +11,11 @@ using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ResourceProvider { /// - /// Main application class for Credentials Service Host executable + /// Main application class for the executable that supports the resource provider and identity services /// internal class Program { - private const string ServiceName = "SqlToolsAzure.exe"; + private const string ServiceName = "SqlToolsResourceProviderService.exe"; /// /// Main entry point into the Credentials Service Host @@ -31,7 +31,7 @@ namespace Microsoft.SqlTools.ResourceProvider return; } - string logFilePath = "sqltoolsazure"; + string logFilePath = "SqlToolsResourceProviderService"; if (!string.IsNullOrWhiteSpace(commandOptions.LoggingDirectory)) { logFilePath = Path.Combine(commandOptions.LoggingDirectory, logFilePath); @@ -40,11 +40,11 @@ namespace Microsoft.SqlTools.ResourceProvider // turn on Verbose logging during early development // we need to switch to Normal when preparing for public preview Logger.Initialize(logFilePath: logFilePath, minimumLogLevel: LogLevel.Verbose, isEnabled: commandOptions.EnableLogging); - Logger.Write(LogLevel.Normal, "Starting SqlTools Azure Provider"); + Logger.Write(LogLevel.Normal, "Starting SqlTools Resource Provider"); // set up the host details and profile paths var hostDetails = new HostDetails( - name: "SqlTools Azure Provider", + name: "SqlTools Resource Provider", profileId: "Microsoft.SqlTools.ResourceProvider", version: new Version(1, 0)); diff --git a/src/Microsoft.SqlTools.ResourceProvider/ResourceProviderHostLoader.cs b/src/Microsoft.SqlTools.ResourceProvider/ResourceProviderHostLoader.cs index 24fc22ea..6fbf8bbd 100644 --- a/src/Microsoft.SqlTools.ResourceProvider/ResourceProviderHostLoader.cs +++ b/src/Microsoft.SqlTools.ResourceProvider/ResourceProviderHostLoader.cs @@ -2,6 +2,8 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Generic; +using System.Reflection; using Microsoft.SqlTools.Extensibility; using Microsoft.SqlTools.Hosting; using Microsoft.SqlTools.Hosting.Protocol; @@ -45,18 +47,25 @@ namespace Microsoft.SqlTools.ResourceProvider { // Load extension provider, which currently finds all exports in current DLL. Can be changed to find based // on directory or assembly list quite easily in the future - ExtensionServiceProvider serviceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider(); + ExtensionServiceProvider serviceProvider = ExtensionServiceProvider.CreateFromAssembliesInDirectory(GetResourceProviderExtensionDlls()); + serviceProvider.RegisterSingleService(sqlToolsContext); serviceProvider.RegisterSingleService(serviceHost); - - // CredentialService.Instance.InitializeService(serviceHost); - // serviceProvider.RegisterSingleService(CredentialService.Instance); - + InitializeHostedServices(serviceProvider, serviceHost); serviceHost.InitializeRequestHandlers(); } + public static IEnumerable GetResourceProviderExtensionDlls() + { + return new string[] { + "SqlToolsResourceProviderService.dll", + "Microsoft.SqlTools.ResourceProvider.Core.dll", + "Microsoft.SqlTools.ResourceProvider.DefaultImpl.dll" + }; + } + /// /// Internal to support testing. Initializes instances in the service, /// and registers them for their preferred service type diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs index e0b865ad..5851bf26 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs @@ -4,6 +4,7 @@ // using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts { diff --git a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequestParams.cs b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequestParams.cs index c882e34b..cf4692b0 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequestParams.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DisasterRecovery/Contracts/RestoreRequestParams.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.Utility; using Newtonsoft.Json.Linq; namespace Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts diff --git a/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StartProfilingRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StartProfilingRequest.cs index 26de7c3d..64d40527 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StartProfilingRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Profiler/Contracts/StartProfilingRequest.cs @@ -5,6 +5,7 @@ using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.Profiler.Contracts { diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs index b90e7796..3c987844 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/DisasterRecovery/RestoreOptionsHelperTests.cs @@ -9,6 +9,7 @@ using Microsoft.SqlTools.ServiceLayer.DisasterRecovery; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.Contracts; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery.RestoreOperation; using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.Utility; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.UnitTests.DisasterRecovery diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj index b20c3156..e74ed27a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj @@ -28,6 +28,7 @@ + diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureSubscriptionContextTest.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureSubscriptionContextTest.cs index 12dc138d..3279e177 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureSubscriptionContextTest.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureSubscriptionContextTest.cs @@ -25,8 +25,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure public void SubscriptionNameShouldReturnCorrectValueGivenValidSubscription() { string name = Guid.NewGuid().ToString(); - - AzureSubscriptionContext subscriptionContext = new AzureSubscriptionContext(new AzureSubscriptionIdentifier(null, name, null)); + string tenantId = Guid.NewGuid().ToString(); + AzureSubscriptionContext subscriptionContext = new AzureSubscriptionContext(new AzureSubscriptionIdentifier(null, null, name, null)); Assert.True(subscriptionContext.SubscriptionName == name); Assert.True(subscriptionContext.Subscription != null); } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureTestContext.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureTestContext.cs index 91eea47e..cf4164ff 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureTestContext.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/Azure/AzureTestContext.cs @@ -30,7 +30,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider.Azure foreach (string subscriptionName in subscriptionToDatabaseMap.Keys) { var azureAccount = new AzureUserAccount(); - AzureSubscriptionIdentifier subId = new AzureSubscriptionIdentifier(azureAccount, subscriptionName, null); + string tenantId = Guid.NewGuid().ToString(); + AzureSubscriptionIdentifier subId = new AzureSubscriptionIdentifier(azureAccount, tenantId, subscriptionName, null); var subscription = new AzureUserAccountSubscriptionContext(subId, new TokenCredentials("dummy")); accountSubscriptions.Add(subscription); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/FirewallErrorParserTest.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/FirewallErrorParserTest.cs index 804d0545..938e493a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/FirewallErrorParserTest.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/FirewallErrorParserTest.cs @@ -3,7 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using Microsoft.SqlTools.ResourceProvider.Core.FirewallRule; +using Microsoft.SqlTools.ResourceProvider.Core.Firewall; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ResourceProvider diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/FirewallRuleServiceTest.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/FirewallRuleServiceTest.cs index e43f7627..54157904 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/FirewallRuleServiceTest.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/FirewallRuleServiceTest.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.SqlTools.ResourceProvider.Core; using Microsoft.SqlTools.ResourceProvider.Core.Authentication; -using Microsoft.SqlTools.ResourceProvider.Core.FirewallRule; +using Microsoft.SqlTools.ResourceProvider.Core.Firewall; using Moq; using Xunit; diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/ResourceProviderServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/ResourceProviderServiceTests.cs new file mode 100644 index 00000000..17082c65 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ResourceProvider/ResourceProviderServiceTests.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.Extensibility; +using Microsoft.SqlTools.ResourceProvider.Core; +using Moq; +using Microsoft.SqlTools.ResourceProvider; +using Microsoft.SqlTools.ResourceProvider.Core.Authentication; + +namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter +{ + public class ResourceProviderServiceTests + { + public ResourceProviderServiceTests() + { + HostMock = new Mock(); + AuthenticationManagerMock = new Mock(); + ResourceManagerMock = new Mock(); + ServiceProvider = ExtensionServiceProvider.CreateFromAssembliesInDirectory(ResourceProviderHostLoader.GetResourceProviderExtensionDlls()); + ServiceProvider.RegisterSingleService(AuthenticationManagerMock.Object); + ServiceProvider.RegisterSingleService(ResourceManagerMock.Object); + HostLoader.InitializeHostedServices(ServiceProvider, HostMock.Object); + ResourceProviderService = ServiceProvider.GetService(); + } + + protected RegisteredServiceProvider ServiceProvider { get; private set; } + protected Mock HostMock { get; private set; } + + protected Mock AuthenticationManagerMock { get; set; } + protected Mock ResourceManagerMock { get; set; } + + protected ResourceProviderService ResourceProviderService { get; private set; } + + + + } +} \ No newline at end of file