diff --git a/TGServerService/App.config b/TGServerService/App.config
index 26d868825c..601eff041c 100644
--- a/TGServerService/App.config
+++ b/TGServerService/App.config
@@ -67,6 +67,9 @@
localhost
+
+
+
diff --git a/TGServerService/AuthorizationManager.cs b/TGServerService/AuthorizationManager.cs
new file mode 100644
index 0000000000..2e8cc2737c
--- /dev/null
+++ b/TGServerService/AuthorizationManager.cs
@@ -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;
+
+ ///
+ public string GetCurrentAuthorizedGroup()
+ {
+ try
+ {
+ return TheDroidsWereLookingFor != null ? TheDroidsWereLookingFor.Translate(typeof(GroupPrincipal)).ToString() : "ADMIN";
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ ///
+ 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();
+ 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;
+ }
+ }
+}
diff --git a/TGServerService/InterfaceBase.cs b/TGServerService/InterfaceBase.cs
index 21503fa982..64c42b0019 100644
--- a/TGServerService/InterfaceBase.cs
+++ b/TGServerService/InterfaceBase.cs
@@ -16,6 +16,7 @@ namespace TGServerService
//called when the service is started
public TGStationServer()
{
+ FindTheDroidsWereLookingFor();
InitChat();
InitByond();
InitCompiler();
diff --git a/TGServerService/Properties/Settings.Designer.cs b/TGServerService/Properties/Settings.Designer.cs
index f3db2a924e..6bbfbe6e30 100644
--- a/TGServerService/Properties/Settings.Designer.cs
+++ b/TGServerService/Properties/Settings.Designer.cs
@@ -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;
+ }
+ }
}
}
diff --git a/TGServerService/Properties/Settings.settings b/TGServerService/Properties/Settings.settings
index 3a443fafea..ee0d0945b7 100644
--- a/TGServerService/Properties/Settings.settings
+++ b/TGServerService/Properties/Settings.settings
@@ -59,5 +59,8 @@
localhost
+
+
+
\ No newline at end of file
diff --git a/TGServerService/ServerService.cs b/TGServerService/ServerService.cs
index ef921fc2b3..bfafc579b9 100644
--- a/TGServerService/ServerService.cs
+++ b/TGServerService/ServerService.cs
@@ -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 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
}
diff --git a/TGServerService/TGServerService.csproj b/TGServerService/TGServerService.csproj
index 5bd5b49e98..253a8dfc23 100644
--- a/TGServerService/TGServerService.csproj
+++ b/TGServerService/TGServerService.csproj
@@ -67,6 +67,7 @@
+
@@ -83,6 +84,7 @@
+
diff --git a/TGServiceInterface/Server.cs b/TGServiceInterface/Server.cs
index 9056c42580..08e848e3f0 100644
--- a/TGServiceInterface/Server.cs
+++ b/TGServiceInterface/Server.cs
@@ -9,7 +9,7 @@ namespace TGServiceInterface
///
/// List of types that can be used with GetComponen
///
- public static readonly IList ValidInterfaces = new List { typeof(ITGByond), typeof(ITGChat), typeof(ITGCompiler), typeof(ITGConfig), typeof(ITGDreamDaemon), typeof(ITGRepository), typeof(ITGSService), typeof(ITGConnectivity) };
+ public static readonly IList ValidInterfaces = new List { typeof(ITGByond), typeof(ITGChat), typeof(ITGCompiler), typeof(ITGConfig), typeof(ITGDreamDaemon), typeof(ITGRepository), typeof(ITGSService), typeof(ITGConnectivity), typeof(ITGAdministration) };
///
/// Base name of the communication pipe
@@ -111,6 +111,7 @@ namespace TGServiceInterface
///
/// As opposed to VerifyConnection(), this check user credentials
+ /// Requires a prior call to
///
/// true if credentials are valid, false otherwise
public static bool Authenticate()
@@ -125,8 +126,29 @@ namespace TGServiceInterface
return false;
}
}
+
+ ///
+ /// As opposed to Authentication() this returns true if the current login can use the interface.
+ /// Requires a prior call to
+ ///
+ /// true if the connection may use the interface, false otherwise
+ public static bool AuthenticateAdmin()
+ {
+ try
+ {
+ GetComponent().GetCurrentAuthorizedGroup();
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
}
+ ///
+ /// Used for testing connections to the service without authentication
+ ///
[ServiceContract]
public interface ITGConnectivity
{
@@ -137,6 +159,28 @@ namespace TGServiceInterface
void VerifyConnection();
}
+ ///
+ /// Manage the group that is used to access the service, can only be used by an administrator
+ ///
+ [ServiceContract]
+ public interface ITGAdministration
+ {
+ ///
+ /// Returns the name of the windows group allowed to use the service other than administrator
+ ///
+ /// The name of the windows group allowed to use the service other than administrator, "ADMIN" if it's unset, null on failure
+ [OperationContract]
+ string GetCurrentAuthorizedGroup();
+
+ ///
+ /// Searches the windows machine for the group named , sets it as the authorized group if it's found
+ ///
+ /// The name of the group to search for or null to clear the setting
+ /// The full name of the group that is now authorized on success, null on failure, "ADMIN" on clearing
+ [OperationContract]
+ string SetAuthorizedGroup(string groupName);
+ }
+
///
/// Interface for managing the service
///