mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-26 22:48:20 +01:00
WIP group authentication service side
This commit is contained in:
@@ -67,6 +67,9 @@
|
||||
<setting name="CertificateURL" serializeAs="String">
|
||||
<value>localhost</value>
|
||||
</setting>
|
||||
<setting name="AuthorizedGroupName" serializeAs="String">
|
||||
<value />
|
||||
</setting>
|
||||
</TGServerService.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.DirectoryServices.AccountManagement;
|
||||
using System.Security.Principal;
|
||||
using System.ServiceModel;
|
||||
using System.ServiceModel.Dispatcher;
|
||||
using TGServiceInterface;
|
||||
|
||||
namespace TGServerService
|
||||
{
|
||||
partial class TGStationServer : ServiceAuthorizationManager, ITGAdministration
|
||||
{
|
||||
SecurityIdentifier TheDroidsWereLookingFor;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetCurrentAuthorizedGroup()
|
||||
{
|
||||
try
|
||||
{
|
||||
return TheDroidsWereLookingFor != null ? TheDroidsWereLookingFor.Translate(typeof(GroupPrincipal)).ToString() : "ADMIN";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string SetAuthorizedGroup(string groupName)
|
||||
{
|
||||
if(groupName == null)
|
||||
{
|
||||
TheDroidsWereLookingFor = null;
|
||||
var config = Properties.Settings.Default;
|
||||
config.AuthorizedGroupName = null;
|
||||
config.Save();
|
||||
return "ADMIN";
|
||||
}
|
||||
return FindTheDroidsWereLookingFor(groupName);
|
||||
}
|
||||
|
||||
string FindTheDroidsWereLookingFor(string search = null)
|
||||
{
|
||||
//find the group that is authorized to use the tools
|
||||
var pc = new PrincipalContext(ContextType.Machine);
|
||||
var config = Properties.Settings.Default;
|
||||
var groupName = search ?? config.AuthorizedGroupName;
|
||||
if (String.IsNullOrWhiteSpace(groupName))
|
||||
return null;
|
||||
var gp = GroupPrincipal.FindByIdentity(pc, search != null ? IdentityType.Name : IdentityType.Sid, groupName);
|
||||
if (gp == null)
|
||||
return null;
|
||||
TheDroidsWereLookingFor = gp.Sid;
|
||||
if (search != null)
|
||||
{
|
||||
config.AuthorizedGroupName = TheDroidsWereLookingFor.Value;
|
||||
config.Save();
|
||||
}
|
||||
return gp.Name;
|
||||
}
|
||||
protected override bool CheckAccessCore(OperationContext operationContext)
|
||||
{
|
||||
if (operationContext.EndpointDispatcher.ContractName == typeof(ITGConnectivity).Name) //always allow connectivity checks
|
||||
return true;
|
||||
|
||||
var windowsIdent = operationContext.ServiceSecurityContext.WindowsIdentity;
|
||||
//first allow admins
|
||||
var authSuccess = new WindowsPrincipal(windowsIdent).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)
|
||||
//and allow those in the authorized group
|
||||
authSuccess = (TheDroidsWereLookingFor != null || windowsIdent.Groups.Contains(TheDroidsWereLookingFor));
|
||||
|
||||
var actions = new List<string>();
|
||||
try
|
||||
{
|
||||
var realfilter = (ActionMessageFilter)operationContext.EndpointDispatcher.ContractFilter;
|
||||
var cnamespace = operationContext.EndpointDispatcher.ContractNamespace;
|
||||
foreach (var I in realfilter.Actions)
|
||||
actions.Add(I.Replace(cnamespace, "")); //filter out some garbage
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
TGServerService.WriteError("IF YOU SEE THIS CALL CYBERBOSS: " + e.ToString(), TGServerService.EventID.Authentication);
|
||||
}
|
||||
TGServerService.WriteAccess(operationContext.ServiceSecurityContext.WindowsIdentity.Name, actions, authSuccess);
|
||||
return authSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace TGServerService
|
||||
//called when the service is started
|
||||
public TGStationServer()
|
||||
{
|
||||
FindTheDroidsWereLookingFor();
|
||||
InitChat();
|
||||
InitByond();
|
||||
InitCompiler();
|
||||
|
||||
+12
@@ -250,5 +250,17 @@ namespace TGServerService.Properties {
|
||||
this["CertificateURL"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string AuthorizedGroupName {
|
||||
get {
|
||||
return ((string)(this["AuthorizedGroupName"]));
|
||||
}
|
||||
set {
|
||||
this["AuthorizedGroupName"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,5 +59,8 @@
|
||||
<Setting Name="CertificateURL" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)">localhost</Value>
|
||||
</Setting>
|
||||
<Setting Name="AuthorizedGroupName" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
@@ -13,6 +14,7 @@ namespace TGServerService
|
||||
//only deprecate events, do not reuse them
|
||||
public enum EventID
|
||||
{
|
||||
Authentication = 1,
|
||||
ChatCommand = 100,
|
||||
ChatConnectFail = 200,
|
||||
ChatProviderStartFail = 300,
|
||||
@@ -95,6 +97,11 @@ namespace TGServerService
|
||||
ActiveService.EventLog.WriteEntry(message, EventLogEntryType.Warning, (int)id);
|
||||
}
|
||||
|
||||
public static void WriteAccess(string username, IList<string> actions, bool authSuccess)
|
||||
{
|
||||
ActiveService.EventLog.WriteEntry(String.Format("Access from: {0}. Actions: {1}", username, String.Join(", ", actions)), authSuccess ? EventLogEntryType.SuccessAudit : EventLogEntryType.FailureAudit, (int)EventID.Authentication);
|
||||
}
|
||||
|
||||
ServiceHost host; //the WCF host
|
||||
|
||||
void MigrateSettings(int oldVersion, int newVersion)
|
||||
@@ -142,16 +149,18 @@ namespace TGServerService
|
||||
}
|
||||
Environment.CurrentDirectory = Config.ServerDirectory;
|
||||
|
||||
host = new ServiceHost(typeof(TGStationServer), new Uri[] { new Uri("net.pipe://localhost"), new Uri(String.Format("https://localhost:{0}", Server.HTTPSPort)) })
|
||||
var instance = new TGStationServer();
|
||||
|
||||
host = new ServiceHost(instance, new Uri[] { new Uri("net.pipe://localhost"), new Uri(String.Format("https://localhost:{0}", Server.HTTPSPort)) })
|
||||
{
|
||||
CloseTimeout = new TimeSpan(0, 0, 5)
|
||||
}; //construction runs here
|
||||
};
|
||||
|
||||
foreach (var I in Server.ValidInterfaces)
|
||||
AddEndpoint(I);
|
||||
|
||||
host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, Config.CertificateURL);
|
||||
host.Credentials.UserNameAuthentication.IncludeWindowsGroups = true;
|
||||
host.Authorization.ServiceAuthorizationManager = instance;
|
||||
|
||||
host.Open(); //...or maybe here, doesn't really matter
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.Composition" />
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
<Reference Include="System.DirectoryServices.AccountManagement" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Numerics" />
|
||||
@@ -83,6 +84,7 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AuthorizationManager.cs" />
|
||||
<Compile Include="Byond.cs" />
|
||||
<Compile Include="Chat.cs" />
|
||||
<Compile Include="ChatCommands.cs" />
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace TGServiceInterface
|
||||
/// <summary>
|
||||
/// List of types that can be used with GetComponen
|
||||
/// </summary>
|
||||
public static readonly IList<Type> ValidInterfaces = new List<Type> { typeof(ITGByond), typeof(ITGChat), typeof(ITGCompiler), typeof(ITGConfig), typeof(ITGDreamDaemon), typeof(ITGRepository), typeof(ITGSService), typeof(ITGConnectivity) };
|
||||
public static readonly IList<Type> ValidInterfaces = new List<Type> { typeof(ITGByond), typeof(ITGChat), typeof(ITGCompiler), typeof(ITGConfig), typeof(ITGDreamDaemon), typeof(ITGRepository), typeof(ITGSService), typeof(ITGConnectivity), typeof(ITGAdministration) };
|
||||
|
||||
/// <summary>
|
||||
/// Base name of the communication pipe
|
||||
@@ -111,6 +111,7 @@ namespace TGServiceInterface
|
||||
|
||||
/// <summary>
|
||||
/// As opposed to VerifyConnection(), this check user credentials
|
||||
/// Requires a prior call to <see cref="VerifyConnection"/>
|
||||
/// </summary>
|
||||
/// <returns>true if credentials are valid, false otherwise</returns>
|
||||
public static bool Authenticate()
|
||||
@@ -125,8 +126,29 @@ namespace TGServiceInterface
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// As opposed to Authentication() this returns true if the current login can use the <see cref="ITGAdministration"/> interface.
|
||||
/// Requires a prior call to <see cref="Authenticate"/>
|
||||
/// </summary>
|
||||
/// <returns>true if the connection may use the <see cref="ITGAdministration"/> interface, false otherwise</returns>
|
||||
public static bool AuthenticateAdmin()
|
||||
{
|
||||
try
|
||||
{
|
||||
GetComponent<ITGAdministration>().GetCurrentAuthorizedGroup();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for testing connections to the service without authentication
|
||||
/// </summary>
|
||||
[ServiceContract]
|
||||
public interface ITGConnectivity
|
||||
{
|
||||
@@ -137,6 +159,28 @@ namespace TGServiceInterface
|
||||
void VerifyConnection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manage the group that is used to access the service, can only be used by an administrator
|
||||
/// </summary>
|
||||
[ServiceContract]
|
||||
public interface ITGAdministration
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the name of the windows group allowed to use the service other than administrator
|
||||
/// </summary>
|
||||
/// <returns>The name of the windows group allowed to use the service other than administrator, "ADMIN" if it's unset, null on failure</returns>
|
||||
[OperationContract]
|
||||
string GetCurrentAuthorizedGroup();
|
||||
|
||||
/// <summary>
|
||||
/// Searches the windows machine for the group named <paramref name="groupName"/>, sets it as the authorized group if it's found
|
||||
/// </summary>
|
||||
/// <param name="groupName">The name of the group to search for or null to clear the setting</param>
|
||||
/// <returns>The full name of the group that is now authorized on success, null on failure, "ADMIN" on clearing</returns>
|
||||
[OperationContract]
|
||||
string SetAuthorizedGroup(string groupName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for managing the service
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user