Files
tgstation-server/TGS.Server/Instance/Administration.cs
T

203 lines
6.1 KiB
C#

using System;
using System.DirectoryServices.AccountManagement;
using System.Security.Principal;
using System.ServiceModel;
using System.Threading;
using TGS.Interface;
using TGS.Interface.Components;
using TGS.Server.Security;
namespace TGS.Server
{
sealed partial class Instance : ServiceAuthorizationManager, ITGAdministration
{
/// <summary>
/// The <see cref="SecurityIdentifier"/> of the Windows group authorized to access the <see cref="Instance"/>
/// </summary>
SecurityIdentifier TheDroidsWereLookingFor;
/// <summary>
/// Used for multithreading safety
/// </summary>
object authLock = new object();
/// <summary>
/// The <see cref="WindowsIdentity.Name"/> of the last <see cref="WindowsIdentity"/> to attempt to access the <see cref="Instance"/>
/// </summary>
string LastSeenUser;
/// <summary>
/// The <see cref="SecurityIdentifier"/> of the account the <see cref="Server"/> is running as
/// </summary>
readonly SecurityIdentifier ServiceSID = WindowsIdentity.GetCurrent().User;
void InitAdministration()
{
lock(RootAuthorizationManager.InstanceAuthManagers)
RootAuthorizationManager.InstanceAuthManagers.Add(this);
FindTheDroidsWereLookingFor();
}
/// <inheritdoc />
public string GetCurrentAuthorizedGroup()
{
try
{
if (TheDroidsWereLookingFor == null)
return "ADMIN";
string res = null;
try
{
res = GroupPrincipal.FindByIdentity(new PrincipalContext(ContextType.Machine), IdentityType.Sid, TheDroidsWereLookingFor.Value).Name;
}
catch { }
return res ?? GroupPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain), IdentityType.Sid, TheDroidsWereLookingFor.Value).Name;
}
catch
{
return null;
}
}
/// <inheritdoc />
public string SetAuthorizedGroup(string groupName)
{
if (groupName == null)
{
TheDroidsWereLookingFor = null;
Config.AuthorizedUserGroupSID = null;
Config.Save();
return "ADMIN";
}
return FindTheDroidsWereLookingFor(groupName);
}
/// <summary>
/// Set <see cref="TheDroidsWereLookingFor"/> based off either an <paramref name="search"/>ed name or a string <see cref="SecurityIdentifier"/> from the config
/// </summary>
/// <param name="search">The name of the group to search for</param>
/// <param name="useDomain">Recursive parameter used to check for the group using <see cref="ContextType.Domain"/> instead of <see cref="ContextType.Machine"/></param>
/// <returns>The name of the group allowed to access the <see cref="Instance"/> if it could be found, <see langword="null"/> otherwise</returns>
string FindTheDroidsWereLookingFor(string search = null, bool useDomain = false)
{
//find the group that is authorized to use the tools
var pc = new PrincipalContext(useDomain ? ContextType.Domain : ContextType.Machine);
var groupName = search ?? Config.AuthorizedUserGroupSID;
if (String.IsNullOrWhiteSpace(groupName))
return null;
var gp = GroupPrincipal.FindByIdentity(pc, search != null ? IdentityType.Name : IdentityType.Sid, groupName);
if (gp == null)
{
if (search != null)
//try again with all types
gp = GroupPrincipal.FindByIdentity(pc, search);
if (gp == null)
return useDomain ? null : FindTheDroidsWereLookingFor(search, true);
}
TheDroidsWereLookingFor = gp.Sid;
if (search != null)
{
Config.AuthorizedUserGroupSID = TheDroidsWereLookingFor.Value;
Config.Save();
}
return gp.Name;
}
/// <summary>
/// Cleans up the <see cref="ITGAdministration"/> component
/// </summary>
void DisposeAdministration()
{
lock(RootAuthorizationManager.InstanceAuthManagers)
RootAuthorizationManager.InstanceAuthManagers.Remove(this);
}
/// <summary>
/// Called by WCF whenever a component call is made. Checks to see that the supplied user account has access to the requested component
/// </summary>
/// <param name="operationContext">Various parameters about the operation supplied by WCF</param>
/// <returns><see langword="true"/> if the supplied user account may use the requested component, <see langword="false"/> otherwise</returns>
protected override bool CheckAccessCore(OperationContext operationContext)
{
var contract = operationContext.EndpointDispatcher.ContractName;
if (contract == typeof(ITGConnectivity).Name) //always allow connectivity checks
return true;
var windowsIdent = operationContext.ServiceSecurityContext.WindowsIdentity;
if (contract == typeof(ITGInterop).Name)
{
//only allow the same user the service is running as to use interop, because that's what DD is running as, and don't spam the logs with it unless it fails
var result = windowsIdent.User == ServiceSID;
if(!result)
WriteAccess(windowsIdent.Name, false);
return result;
}
var wp = new WindowsPrincipal(windowsIdent);
//first allow admins
var authSuccess = wp.IsInRole(WindowsBuiltInRole.Administrator);
//if we're not an admin, check that we aren't trying to access the admin interface
if (!authSuccess && operationContext.EndpointDispatcher.ContractName != typeof(ITGAdministration).Name && TheDroidsWereLookingFor != null)
authSuccess = wp.IsInRole(new SecurityIdentifier(Config.AuthorizedUserGroupSID));
lock (authLock)
{
var user = windowsIdent.Name;
if (LastSeenUser != user)
{
LastSeenUser = user;
WriteAccess(user, authSuccess);
}
}
return authSuccess;
}
public string RecreateStaticFolder()
{
lock (RepoLock)
{
if (RepoBusy)
return "Repo locked!";
RepoBusy = true;
}
try
{
if (!Monitor.TryEnter(watchdogLock))
return "Watchdog locked!";
try
{
if (!Monitor.TryEnter(configLock))
return "Static dir locked!";
try
{
if (currentStatus != DreamDaemonStatus.Offline)
return "Watchdog running!";
BackupAndDeleteStaticDirectory();
InitialConfigureRepository();
}
finally
{
Monitor.Exit(configLock);
}
}
finally
{
Monitor.Exit(watchdogLock);
}
}
catch(Exception e)
{
return e.ToString();
}
finally
{
lock (RepoLock)
RepoBusy = false;
}
return null;
}
}
}