mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-23 09:35:38 -05:00
* added handler to fetch credentials * added credentials to security service rather than agent service
523 lines
20 KiB
C#
523 lines
20 KiB
C#
//
|
|
// 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;
|
|
using System.Data;
|
|
using Microsoft.SqlServer.Management.Common;
|
|
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
|
using Microsoft.SqlServer.Management.Smo.Agent;
|
|
using Microsoft.SqlTools.ServiceLayer.Agent.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.Management;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
|
{
|
|
internal class AgentProxyAccountActions : ManagementActionBase
|
|
{
|
|
#region Constants
|
|
internal const string ProxyAccountPropertyName = "proxyaccount";
|
|
internal const string ProxyAccountSubsystem = "SubSystem";
|
|
internal const string ProxyAccountMode = "Mode";
|
|
internal const string ProxyAccountDuplicateMode = "Duplicate";
|
|
internal const int SysnameLength = 256;
|
|
internal const int DescriptionLength = 512;
|
|
#endregion
|
|
|
|
internal enum ProxyPrincipalType
|
|
{
|
|
SqlLogin,
|
|
MsdbRole,
|
|
ServerRole
|
|
}
|
|
|
|
// Collections of principals for logins/server roles/msdb roles
|
|
private ArrayList[] principals;
|
|
|
|
// Name of the proxy account we work with
|
|
private string proxyAccountName;
|
|
|
|
private AgentProxyInfo proxyInfo;
|
|
|
|
// Flag indicating that proxy account should be duplicated
|
|
private bool duplicate;
|
|
|
|
private ConfigAction configAction;
|
|
|
|
private bool readOnly = false;
|
|
|
|
/// <summary>
|
|
/// Main constructor. Creates all pages and adds them
|
|
/// to the tree control.
|
|
/// </summary>
|
|
public AgentProxyAccountActions(CDataContainer dataContainer, AgentProxyInfo proxyInfo, ConfigAction configAction)
|
|
{
|
|
this.DataContainer = dataContainer;
|
|
this.proxyInfo = proxyInfo;
|
|
this.configAction = configAction;
|
|
|
|
if (configAction != ConfigAction.Drop)
|
|
{
|
|
// Create data structures
|
|
int length = Enum.GetValues(typeof(ProxyPrincipalType)).Length;
|
|
this.principals = new ArrayList[length];
|
|
for (int i = 0; i < length; ++i)
|
|
{
|
|
this.principals[i] = new ArrayList();
|
|
}
|
|
|
|
if (configAction == ConfigAction.Update)
|
|
{
|
|
RefreshData();
|
|
}
|
|
}
|
|
|
|
// Find out if we are creating a new proxy account or
|
|
// modifying an existing one.
|
|
GetProxyAccountName(dataContainer, ref this.proxyAccountName, ref this.duplicate);
|
|
}
|
|
|
|
public static string SysadminAccount
|
|
{
|
|
get { return SR.SysadminAccount; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main execution method. Creates or Alters a proxyAccount name.
|
|
/// </summary>
|
|
/// <returns>Always returns false</returns>
|
|
protected override bool DoPreProcessExecution(RunType runType, out ExecutionMode executionResult)
|
|
{
|
|
base.DoPreProcessExecution(runType, out executionResult);
|
|
|
|
if (this.configAction == ConfigAction.Create)
|
|
{
|
|
return Create();
|
|
}
|
|
else if (this.configAction == ConfigAction.Update)
|
|
{
|
|
return Update();
|
|
}
|
|
else if (this.configAction == ConfigAction.Drop)
|
|
{
|
|
return Drop();
|
|
}
|
|
|
|
// Always return false to stop framework from calling OnRunNow
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// It creates a new ProxyAccount or gets an existing
|
|
/// one from JobServer and updates all properties.
|
|
/// </summary>
|
|
private bool CreateOrUpdateProxyAccount(AgentProxyInfo proxyInfo)
|
|
{
|
|
ProxyAccount proxyAccount = null;
|
|
if (this.configAction == ConfigAction.Create)
|
|
{
|
|
proxyAccount = new ProxyAccount(this.DataContainer.Server.JobServer,
|
|
proxyInfo.AccountName,
|
|
proxyInfo.CredentialName,
|
|
proxyInfo.IsEnabled,
|
|
proxyInfo.Description);
|
|
|
|
UpdateProxyAccount(proxyAccount);
|
|
proxyAccount.Create();
|
|
}
|
|
else if (this.DataContainer.Server.JobServer.ProxyAccounts.Contains(this.proxyAccountName))
|
|
{
|
|
// Try refresh and check again
|
|
this.DataContainer.Server.JobServer.ProxyAccounts.Refresh();
|
|
if (this.DataContainer.Server.JobServer.ProxyAccounts.Contains(this.proxyAccountName))
|
|
{
|
|
proxyAccount = AgentProxyAccountActions.GetProxyAccount(this.proxyAccountName, this.DataContainer.Server.JobServer);
|
|
// Set the other properties
|
|
proxyAccount.CredentialName = proxyInfo.CredentialName;
|
|
proxyAccount.Description = proxyInfo.Description;
|
|
|
|
UpdateProxyAccount(proxyAccount);
|
|
proxyAccount.Alter();
|
|
|
|
// Rename the proxy if needed
|
|
// This has to be done after Alter, in order to
|
|
// work correcly when scripting this action.
|
|
if (this.proxyAccountName != proxyInfo.AccountName)
|
|
{
|
|
proxyAccount.Rename(proxyInfo.AccountName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
#if false // @TODO - reenable subsystem code below
|
|
|
|
// Update the subsystems
|
|
foreach (AgentSubSystem subsystem in this.addSubSystems)
|
|
{
|
|
proxyAccount.AddSubSystem(subsystem);
|
|
}
|
|
|
|
foreach (AgentSubSystem subsystem in this.removeSubSystems)
|
|
{
|
|
proxyAccount.RemoveSubSystem(subsystem);
|
|
|
|
// Update jobsteps that use this proxy accunt
|
|
// when some subsystems are removed from it
|
|
string reassignToProxyName = this.reassignToProxyNames[(int)subsystem];
|
|
|
|
if (reassignToProxyName != null)
|
|
{
|
|
// if version is sql 11 and above call SMO API to reassign proxy account
|
|
if (Utils.IsSql11OrLater(this.DataContainer.Server.ServerVersion))
|
|
{
|
|
proxyAccount.Reassign(reassignToProxyName);
|
|
}
|
|
else
|
|
{
|
|
// legacy code
|
|
// Get a list of all job step objects that use this proxy and this subsystem
|
|
Request req = new Request();
|
|
req.Urn = string.Format(System.Globalization.CultureInfo.InvariantCulture,
|
|
"Server/JobServer/Job/Step[@ProxyName=\'{0}\' and @SubSystem={1}]",
|
|
Urn.EscapeString(proxyAccount.Name),
|
|
(int)subsystem);
|
|
req.Fields = new string[] { "Name" };
|
|
req.ParentPropertiesRequests = new PropertiesRequest[1] { new PropertiesRequest() };
|
|
req.ParentPropertiesRequests[0].Fields = new string[] { "Name" };
|
|
|
|
Enumerator en = new Enumerator();
|
|
DataTable table = en.Process(this.DataContainer.ServerConnection, req);
|
|
foreach (DataRow row in table.Rows)
|
|
{
|
|
// Get the actual job step object using urn
|
|
string urnString = string.Format(System.Globalization.CultureInfo.InvariantCulture, "Server/JobServer/Job[@Name=\"{0}\"/Step[@Name=\"{1}\"", row["Job_Name"], row["Name"]);
|
|
Urn urn = new Urn(urnString);
|
|
JobStep jobStep = (JobStep)this.DataContainer.Server.GetSmoObject(urn);
|
|
|
|
jobStep.ProxyName = reassignToProxyName;
|
|
jobStep.Alter();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public bool Create()
|
|
{
|
|
CreateOrUpdateProxyAccount(this.proxyInfo);
|
|
return true;
|
|
}
|
|
|
|
public bool Update()
|
|
{
|
|
CreateOrUpdateProxyAccount(this.proxyInfo);
|
|
return true;
|
|
}
|
|
|
|
public bool Drop()
|
|
{
|
|
if (this.DataContainer.Server.JobServer.ProxyAccounts.Contains(this.proxyAccountName))
|
|
{
|
|
// Try refresh and check again
|
|
this.DataContainer.Server.JobServer.ProxyAccounts.Refresh();
|
|
if (this.DataContainer.Server.JobServer.ProxyAccounts.Contains(this.proxyAccountName))
|
|
{
|
|
ProxyAccount proxyAccount = AgentProxyAccountActions.GetProxyAccount(this.proxyAccountName, this.DataContainer.Server.JobServer);
|
|
proxyAccount.DropIfExists();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called to update the proxy object
|
|
/// </summary>
|
|
public void UpdateProxyAccount(ProxyAccount proxyAccount)
|
|
{
|
|
if (proxyAccount == null)
|
|
{
|
|
throw new ArgumentNullException("proxyAccount");
|
|
}
|
|
|
|
ArrayList principalsToAdd = new ArrayList();
|
|
ArrayList principalsToRemove = new ArrayList();
|
|
|
|
// Process Sql Logins
|
|
if (ExtractPermissionsToAddAndRemove(
|
|
this.configAction == ConfigAction.Update ? proxyAccount.EnumLogins() : null,
|
|
this.principals[(int) ProxyPrincipalType.SqlLogin],
|
|
principalsToAdd,
|
|
principalsToRemove))
|
|
{
|
|
foreach (string principal in principalsToRemove)
|
|
{
|
|
proxyAccount.RemoveLogin(principal);
|
|
}
|
|
|
|
foreach (string principal in principalsToAdd)
|
|
{
|
|
proxyAccount.AddLogin(principal);
|
|
}
|
|
}
|
|
|
|
// Process Server Roles
|
|
if (ExtractPermissionsToAddAndRemove(
|
|
this.configAction == ConfigAction.Update ? proxyAccount.EnumServerRoles() : null,
|
|
this.principals[(int) ProxyPrincipalType.ServerRole],
|
|
principalsToAdd,
|
|
principalsToRemove))
|
|
{
|
|
foreach (string principal in principalsToRemove)
|
|
{
|
|
proxyAccount.RemoveServerRole(principal);
|
|
}
|
|
|
|
foreach (string principal in principalsToAdd)
|
|
{
|
|
proxyAccount.AddServerRole(principal);
|
|
}
|
|
}
|
|
|
|
// Process Msdb Roles
|
|
if (ExtractPermissionsToAddAndRemove(
|
|
this.configAction == ConfigAction.Update ? proxyAccount.EnumMsdbRoles() : null,
|
|
this.principals[(int) ProxyPrincipalType.MsdbRole],
|
|
principalsToAdd,
|
|
principalsToRemove))
|
|
{
|
|
foreach (string principal in principalsToRemove)
|
|
{
|
|
proxyAccount.RemoveMsdbRole(principal);
|
|
}
|
|
|
|
foreach (string principal in principalsToAdd)
|
|
{
|
|
proxyAccount.AddMsdbRole(principal);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method scans two list of principals - an existing one extracted from ProxyAccount object
|
|
/// and a new one obtained from this panel and then it creates a two differential lists: one of
|
|
/// principals to add and the other of principals to remove.
|
|
/// </summary>
|
|
/// <returns>true if there are any changes between existingPermissions and newPermissions lists</returns>
|
|
private bool ExtractPermissionsToAddAndRemove(DataTable existingPermissions, ArrayList newPermissions, ArrayList principalsToAdd, ArrayList principalsToRemove)
|
|
{
|
|
// Reset both output lists
|
|
principalsToAdd.Clear();
|
|
principalsToRemove.Clear();
|
|
|
|
// Sort both input lists
|
|
DataRow[] existingRows = existingPermissions != null? existingPermissions.Select(string.Empty, "Name DESC") : new DataRow[] {};
|
|
newPermissions.Sort();
|
|
|
|
// Go through both lists at the same time and find differences
|
|
int existingPos = 0;
|
|
int newPos = 0;
|
|
|
|
while (newPos < newPermissions.Count && existingPos < existingRows.Length)
|
|
{
|
|
int result = string.Compare(existingRows[existingPos]["Name"] as string, newPermissions[newPos] as string,StringComparison.Ordinal);
|
|
|
|
if (result < 0)
|
|
{
|
|
// element in existingRows is lower then element in newPermissions
|
|
// mark element in existingRows for removal
|
|
principalsToRemove.Add(existingRows[existingPos]["Name"]);
|
|
++existingPos;
|
|
}
|
|
else if (result > 0)
|
|
{
|
|
// element in existingRows is greater then element in newPermissions
|
|
// mark element in newPermissions for adding
|
|
principalsToAdd.Add(newPermissions[newPos]);
|
|
++newPos;
|
|
}
|
|
else
|
|
{
|
|
// Both elements are equal.
|
|
// Advance to the next element
|
|
++existingPos;
|
|
++newPos;
|
|
}
|
|
}
|
|
|
|
while (newPos < newPermissions.Count)
|
|
{
|
|
// Some elements are still left
|
|
// Copy them all to Add collection
|
|
principalsToAdd.Add(newPermissions[newPos++]);
|
|
}
|
|
|
|
while (existingPos < existingRows.Length)
|
|
{
|
|
// Some elements are still left
|
|
// Copy them all to Remove collection
|
|
principalsToRemove.Add(existingRows[existingPos++]["Name"]);
|
|
}
|
|
|
|
return(principalsToAdd.Count > 0 || principalsToRemove.Count > 0);
|
|
}
|
|
|
|
private void RefreshData()
|
|
{
|
|
// Reset all principal collections
|
|
for (int i = 0; i < this.principals.Length; ++i)
|
|
{
|
|
this.principals[i].Clear();
|
|
}
|
|
|
|
// Add new data from proxy account
|
|
if (this.proxyAccountName != null)
|
|
{
|
|
ProxyAccount proxyAccount = GetProxyAccount(this.proxyAccountName, this.DataContainer.Server.JobServer);
|
|
|
|
// Get all the logins associated with this proxy
|
|
DataTable dt = proxyAccount.EnumLogins();
|
|
foreach (DataRow row in dt.Rows)
|
|
{
|
|
this.principals[(int)ProxyPrincipalType.SqlLogin].Add(row["Name"]);
|
|
}
|
|
|
|
// Get all the Server roles associated with this proxy
|
|
dt = proxyAccount.EnumServerRoles();
|
|
foreach (DataRow row in dt.Rows)
|
|
{
|
|
this.principals[(int)ProxyPrincipalType.ServerRole].Add(row["Name"]);
|
|
}
|
|
|
|
// Get all the MSDB roles associated with this account
|
|
dt = proxyAccount.EnumMsdbRoles();
|
|
foreach (DataRow row in dt.Rows)
|
|
{
|
|
this.principals[(int)ProxyPrincipalType.MsdbRole].Add(row["Name"]);
|
|
}
|
|
|
|
// only sa can modify
|
|
this.readOnly = !this.DataContainer.Server.ConnectionContext.IsInFixedServerRole(FixedServerRoles.SysAdmin);
|
|
}
|
|
}
|
|
|
|
#region Static methods
|
|
/// <summary>
|
|
/// Retrieves an instance of ProxyAccount from job server using name provided.
|
|
/// If proxy does not exist it throws an exception.
|
|
/// </summary>
|
|
/// <param name="proxyAccountName">Name of the proxy to get</param>
|
|
/// <param name="jobServer">Job server to get the proxy from</param>
|
|
/// <returns>A valid proxy account.</returns>
|
|
internal static ProxyAccount GetProxyAccount(string proxyAccountName, JobServer jobServer)
|
|
{
|
|
if (proxyAccountName == null || proxyAccountName.Length == 0)
|
|
{
|
|
throw new ArgumentException("proxyAccountName");
|
|
}
|
|
|
|
if (jobServer == null)
|
|
{
|
|
throw new ArgumentNullException("jobServer");
|
|
}
|
|
|
|
ProxyAccount proxyAccount = jobServer.ProxyAccounts[proxyAccountName];
|
|
if (proxyAccount == null)
|
|
{
|
|
// proxy not found. Try refreshing the collection
|
|
jobServer.ProxyAccounts.Refresh();
|
|
proxyAccount = jobServer.ProxyAccounts[proxyAccountName];
|
|
|
|
// if still cannot get the proxy throw an exception
|
|
if (proxyAccount == null)
|
|
{
|
|
throw new ApplicationException(SR.ProxyAccountNotFound(proxyAccountName));
|
|
}
|
|
}
|
|
return proxyAccount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a proxy account name from shared data containter.
|
|
/// </summary>
|
|
internal static void GetProxyAccountName(CDataContainer dataContainer, ref string proxyAccountName, ref bool duplicate)
|
|
{
|
|
STParameters parameters = new STParameters();
|
|
parameters.SetDocument(dataContainer.Document);
|
|
|
|
// Get proxy name
|
|
parameters.GetParam(AgentProxyAccountActions.ProxyAccountPropertyName, ref proxyAccountName);
|
|
if (proxyAccountName != null && proxyAccountName.Length == 0)
|
|
{
|
|
// Reset empty name back to null
|
|
proxyAccountName = null;
|
|
}
|
|
|
|
// Get duplicate flag
|
|
string mode = string.Empty;
|
|
if (parameters.GetParam(AgentProxyAccountActions.ProxyAccountMode, ref mode) &&
|
|
0 == string.Compare(mode, AgentProxyAccountActions.ProxyAccountDuplicateMode, StringComparison.Ordinal))
|
|
{
|
|
duplicate = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uses enumerator to list names of all proxy accounts that use specified Agent SubSystem.
|
|
/// </summary>
|
|
/// <param name="serverConnection">Connection to use.</param>
|
|
/// <param name="subsystemName">Requested SubSystem name</param>
|
|
/// <param name="includeSysadmin">If set to true, 'sysadmin' account is added as a first entry in
|
|
/// the list of proxy accounts.</param>
|
|
/// <returns>An array containing names of proxy accounts</returns>
|
|
internal static string[] ListProxyAccountsForSubsystem(
|
|
ServerConnection serverConnection,
|
|
string subsystemName,
|
|
bool includeSysadmin)
|
|
{
|
|
ArrayList proxyAccounts = new ArrayList();
|
|
|
|
// This method is valid only on Yukon and later
|
|
if (serverConnection.ServerVersion.Major >= 9)
|
|
{
|
|
if (includeSysadmin)
|
|
{
|
|
proxyAccounts.Add(AgentProxyAccountActions.SysadminAccount);
|
|
}
|
|
|
|
// Get the list of proxy accounts
|
|
Request req = new Request();
|
|
|
|
req.Urn = string.Format(System.Globalization.CultureInfo.InvariantCulture,
|
|
"Server/JobServer/ProxyAccount/AgentSubSystem[@Name = \"{0}\"]",
|
|
Urn.EscapeString(subsystemName));
|
|
req.ResultType = ResultType.IDataReader;
|
|
req.Fields = new string[] { "Name" };
|
|
req.ParentPropertiesRequests = new PropertiesRequest[1] { new PropertiesRequest() };
|
|
req.ParentPropertiesRequests[0].Fields = new string[] { "Name" };
|
|
|
|
Enumerator en = new Enumerator();
|
|
|
|
using (IDataReader reader = en.Process(serverConnection, req).Data as IDataReader)
|
|
{
|
|
while (reader.Read())
|
|
{
|
|
proxyAccounts.Add(reader.GetString(0));
|
|
}
|
|
}
|
|
}
|
|
|
|
return (string[]) proxyAccounts.ToArray(typeof(string));
|
|
}
|
|
#endregion
|
|
}
|
|
}
|