22 Commits

Author SHA1 Message Date
Gary Sharp d1aee95d35 qol: user photos to honour image orientation metadata 2026-03-09 18:59:20 +11:00
Gary Sharp 72a709c11d qol: Saved Exports: informational message 2026-03-05 15:36:18 +11:00
Gary Sharp 4f7f6db804 qol: option to view/print generated document inline 2026-03-01 14:15:27 +11:00
Gary Sharp 892299a791 bug: device enrolment match initially match on account name instead of account id 2026-02-25 15:35:02 +11:00
Gary Sharp 48512fa9d1 qol: offline domain join to reuse AD computer accounts
Replaces old behaviour of deleting and creating new accounts. Now when a device has a new name, its existing account is renamed and reused.
2026-02-25 14:34:34 +11:00
Gary Sharp 204d57a4a5 feature: display pdf attachments inline 2026-01-26 16:34:26 +11:00
Gary Sharp f807d75162 #180: bulk download device/job/user attachments 2026-01-26 15:04:04 +11:00
Gary Sharp e809c63e37 #184: optionally set managed group descriptions 2026-01-26 12:31:01 +11:00
Gary Sharp e1f1973520 feature: Bootstrapper secure server discovery 2026-01-22 15:26:23 +11:00
Gary Sharp 71fa53bfb2 bug: on the home page a white bar appeared at the bottom of the screen when there was a pending enrolment 2026-01-22 15:22:23 +11:00
Gary Sharp 834d2f8fae feature: #UserDetails[] expression helper ignores hide/obfuscate mode symbols 2026-01-22 15:20:23 +11:00
Gary Sharp 2ab765f2d7 bug: edge case where the assigned user is incorrectly set for login 2026-01-22 15:17:19 +11:00
Gary Sharp 04be92a1df feature: online session authentication and one-way decryption 2026-01-01 19:21:00 +11:00
Gary Sharp 4e7c7c117b fix activation registration after local-network-access policy introduction 2025-12-29 13:14:52 +11:00
Gary Sharp f975c55b8a bug: whitespace missing in device/user flag tooltips 2025-12-10 14:38:58 +11:00
Gary Sharp 9e0d832aa1 bug fix: save non-warranty purchase order reference 2025-12-04 14:48:34 +11:00
Gary Sharp 94a31282eb bug fix #185: fix document package generation 2025-12-03 15:49:10 +11:00
Gary Sharp 529bba5c72 resolves #179: filter noticeboard by job queue 2025-10-31 17:45:04 +11:00
Gary Sharp 8424a9a9a2 feature #180: bulk download document attachment instances 2025-10-31 14:58:54 +11:00
Gary Sharp 202bbb163b qol: lazy-loading of device profile organisation units 2025-10-31 13:58:59 +11:00
Gary Sharp 0e15d2a880 fix: device batch timeline link 2025-10-19 19:19:28 +11:00
Gary Sharp 89c14084f5 fix #181 - explicitly fetch DeviceComments when showing a device 2025-10-19 19:11:00 +11:00
172 changed files with 13079 additions and 3592 deletions
+4
View File
@@ -60,6 +60,10 @@
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
+1
View File
@@ -141,6 +141,7 @@
<Compile Include="Extensions\EnrolExtensions.cs" />
<Compile Include="Extensions\WhoAmIExtensions.cs" />
<Compile Include="Interop\Certificates.cs" />
<Compile Include="Interop\EndpointDiscovery.cs" />
<Compile Include="Interop\Hardware.cs" />
<Compile Include="Interop\LocalAuthentication.cs" />
<Compile Include="Interop\Native\NetworkConnectionStatuses.cs" />
+8 -7
View File
@@ -10,19 +10,18 @@ namespace Disco.Client
{
public static class ErrorReporting
{
private const string ServicePathTemplate = "http://DISCO:9292/Services/Client/ClientError";
public static string DeviceIdentifier { get; set; }
public static string EnrolmentSessionId { get; set; }
public static void ReportError(Exception Ex, bool ReportToServer)
public static void ReportError(Exception exception, bool reportToServer)
{
bool isClientServiceException = Ex is ClientServiceException;
bool isClientServiceException = exception is ClientServiceException;
ErrorReport report = new ErrorReport()
{
DeviceIdentifier = DeviceIdentifier,
SessionId = EnrolmentSessionId,
JsonException = Ex.IntenseExceptionSerialization()
JsonException = exception.IntenseExceptionSerialization()
};
try
@@ -38,7 +37,7 @@ namespace Disco.Client
catch (Exception) { }
// Don't log server errors back to the server
if (!isClientServiceException && ReportToServer)
if (!isClientServiceException && reportToServer)
{
try
{
@@ -49,7 +48,7 @@ namespace Disco.Client
try
{
Presentation.WriteFatalError(Ex);
Presentation.WriteFatalError(exception);
}
catch (Exception) { }
}
@@ -85,7 +84,9 @@ namespace Disco.Client
string reportJson = JsonConvert.SerializeObject(report);
string reportResponse;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(ServicePathTemplate);
var serverUri = new Uri(Program.ServerUrl ?? new Uri("http://disco:9292"), "/Services/Client/ClientError");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serverUri);
request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}";
request.ContentType = "application/json";
request.Method = WebRequestMethods.Http.Post;
@@ -1,30 +1,23 @@
using Disco.Models.ClientServices;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Net;
using System.Reflection;
namespace Disco.Client.Extensions
{
public static class ClientServicesExtensions
internal static class ClientServicesExtensions
{
//#if DEBUG
// public const string ServicePathAuthenticatedTemplate = "http://WS-GSHARP:57252/Services/Client/Authenticated/{0}";
// public const string ServicePathUnauthenticatedTemplate = "http://WS-GSHARP:57252/Services/Client/Unauthenticated/{0}";
//#else
public const string ServicePathAuthenticatedTemplate = "http://DISCO:9292/Services/Client/Authenticated/{0}";
public const string ServicePathUnauthenticatedTemplate = "http://DISCO:9292/Services/Client/Unauthenticated/{0}";
//#endif
public static ResponseType Post<ResponseType>(this ServiceBase<ResponseType> Service, bool Authenticated)
public static ResponseType Post<ResponseType>(this ServiceBase<ResponseType> service, bool authenticated)
{
ResponseType serviceResponse;
string serviceUrl;
Uri serviceUrl;
if (Authenticated)
serviceUrl = string.Format(ServicePathAuthenticatedTemplate, Service.Feature);
if (authenticated)
serviceUrl = new Uri(Program.ServerUrl, $"/Services/Client/Authenticated/{service.Feature}");
else
serviceUrl = string.Format(ServicePathUnauthenticatedTemplate, Service.Feature);
serviceUrl = new Uri(Program.ServerUrl, $"/Services/Client/Unauthenticated/{service.Feature}");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceUrl);
request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}";
@@ -39,7 +32,7 @@ namespace Disco.Client.Extensions
{
using (var jsonWriter = new JsonTextWriter(requestWriter))
{
jsonSerializer.Serialize(jsonWriter, Service);
jsonSerializer.Serialize(jsonWriter, service);
}
}
+46 -25
View File
@@ -2,14 +2,12 @@
using Disco.Models.ClientServices;
using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace Disco.Client.Extensions
{
public static class EnrolExtensions
internal static class EnrolExtensions
{
public static void Build(this Enrol enrol)
{
enrol.ComputerName = Environment.MachineName;
@@ -62,6 +60,28 @@ namespace Disco.Client.Extensions
Program.AllowUninstall = enrolResponse.AllowBootstrapperUninstall;
}
[Flags]
private enum NETSETUP_PROVISION_FLAGS : int
{
NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT = 0x00000001,
NETSETUP_PROVISION_REUSE_ACCOUNT = 0x00000002,
NETSETUP_PROVISION_USE_DEFAULT_PASSWORD = 0x00000004,
NETSETUP_PROVISION_SKIP_ACCOUNT_SEARCH = 0x00000008,
NETSETUP_PROVISION_ROOT_CA_CERTS = 0x00000010,
NETSETUP_PROVISION_PERSISTENTSITE = 0x00000020,
NETSETUP_PROVISION_ONLINE_CALLER = 0x40000000,
NETSETUP_PROVISION_CHECK_PWD_ONLY = unchecked((int)0x80000000),
}
[DllImport("Netapi32.dll", CallingConvention = CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.I4)]
private static extern int NetRequestOfflineDomainJoin(
[In] IntPtr pProvisionBinData,
[In, MarshalAs(UnmanagedType.I4)] int cbProvisionBinDataSize,
[In, MarshalAs(UnmanagedType.I4)] NETSETUP_PROVISION_FLAGS dwOptions,
[In, MarshalAs(UnmanagedType.LPWStr)] string lpWindowsPath
);
/// <summary>
/// Processes a Client Service Enrol Response for Offline Domain Join Actions
/// </summary>
@@ -73,33 +93,34 @@ namespace Disco.Client.Extensions
{
Presentation.UpdateStatus("Enrolling Device", $"Performing Offline Domain Join:\r\nRenaming Computer: {Environment.MachineName} -> {enrolResponse.ComputerName}", true, -1, 1500);
string odjFile = Path.GetTempFileName();
File.WriteAllBytes(odjFile, Convert.FromBase64String(enrolResponse.OfflineDomainJoinManifest));
var provisionData = Convert.FromBase64String(enrolResponse.OfflineDomainJoinManifest);
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot");
string odjWindowsPath = Environment.GetEnvironmentVariable("SystemRoot");
string odjProcessArguments = $"/REQUESTODJ /LOADFILE \"{odjFile}\" /WINDOWSPATH \"{odjWindowsPath}\" /LOCALOS";
ProcessStartInfo odjProcessStartInfo = new ProcessStartInfo("DJOIN.EXE", odjProcessArguments)
var provisionDataPointer = Marshal.AllocCoTaskMem(provisionData.Length);
Marshal.Copy(provisionData, 0, provisionDataPointer, provisionData.Length);
var joinResult = default(int);
try
{
CreateNoWindow = true,
ErrorDialog = false,
LoadUserProfile = true,
RedirectStandardOutput = true,
UseShellExecute = false
};
string odjResult;
using (Process odjProcess = System.Diagnostics.Process.Start(odjProcessStartInfo))
{
odjResult = odjProcess.StandardOutput.ReadToEnd();
odjProcess.WaitForExit(20000); // 20 Seconds
joinResult = NetRequestOfflineDomainJoin(provisionDataPointer, provisionData.Length, NETSETUP_PROVISION_FLAGS.NETSETUP_PROVISION_ONLINE_CALLER, systemRoot);
}
finally
{
Marshal.FreeCoTaskMem(provisionDataPointer);
}
Presentation.UpdateStatus("Enrolling Device", $"Offline Domain Join Result:\r\n{odjResult}", true, -1, 3000);
if (File.Exists(odjFile))
File.Delete(odjFile);
if (joinResult != 0)
{
var win32Exception = new System.ComponentModel.Win32Exception(joinResult);
Presentation.UpdateStatus("Enrolling Device", $"Offline Domain Join Failed:\r\n{win32Exception.Message} [{joinResult}]", true, -1, 3000);
throw new InvalidOperationException($"Offline Domain Join Failed:\r\n{win32Exception.Message} [{joinResult}]");
}
else
{
Presentation.UpdateStatus("Enrolling Device", $"Offline Domain Join Succeeded", true, -1, 2000);
}
// Flush Logged-On History
if (!string.IsNullOrEmpty(enrolResponse.DomainName))
if (enrolResponse.SetAssignedUserForLogon && !string.IsNullOrEmpty(enrolResponse.DomainName))
{
using (RegistryKey regWinlogon = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{
+317
View File
@@ -0,0 +1,317 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Threading;
namespace Disco.Client.Interop
{
internal class EndpointDiscovery
{
[DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)] ref string pszName, NativeDnsQueryTypes wType, NativeDnsQueryOptions options, int aipServers, ref IntPtr ppQueryResults, int pReserved);
[DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)]
private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);
private const int DNS_ERROR_RCODE_NAME_ERROR = 0x232B;
private const int DNS_ERROR_BAD_PACKET = 0x251E;
public static Tuple<Uri, string> DiscoverServer(Uri forcedServerUri)
{
// 1. Check first command line argument for server name
if (forcedServerUri != null)
return Tuple.Create(forcedServerUri, "Manual");
// 2. Check for a DNS SRV record for _discoict._tcp.domain
var domainSuffixes = new List<string>();
var primaryDomain = IPGlobalProperties.GetIPGlobalProperties().DomainName;
if (!string.IsNullOrEmpty(primaryDomain))
domainSuffixes.Add(primaryDomain);
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up);
foreach (var ni in networkInterfaces)
{
var domainSuffix = ni.GetIPProperties().DnsSuffix;
if (!string.IsNullOrWhiteSpace(domainSuffix))
{
if (domainSuffix.Equals("mshome.net", StringComparison.OrdinalIgnoreCase))
continue;
if (!domainSuffixes.Contains(domainSuffix, StringComparer.OrdinalIgnoreCase))
domainSuffixes.Add(domainSuffix);
}
}
foreach (var domain in domainSuffixes)
{
var dnsRecords = GetSRVRecords("_discoict._tcp." + domain);
if (dnsRecords.Count > 0)
{
var firstRecord = dnsRecords.OrderBy(r => r.Priority).ThenByDescending(r => r.Weight).First();
if (firstRecord.Port == 443)
return Tuple.Create(new Uri($"https://{firstRecord.Target}"), "SRV");
else
return Tuple.Create(new Uri($"https://{firstRecord.Target}:{firstRecord.Port}"), "SRV");
}
}
// 3. Detect VicSmart network and try resolving with Disco ICT Online Services
if (TryResolveVicSmartServer(domainSuffixes, out var vicSmartServerUrl))
return Tuple.Create(vicSmartServerUrl, "VicSmart");
// 4. Legacy: Ping 'disco' and assume port 9292
using (Ping p = new Ping())
{
try
{
PingReply pr = p.Send("disco", 2000);
if (pr.Status == IPStatus.Success)
return Tuple.Create(new Uri("http://disco:9292"), "Legacy");
}
catch (Exception)
{
}
}
throw new Exception("Could not locate Disco ICT server on the network.");
}
private static bool TryResolveVicSmartServer(List<string> domainSuffixes, out Uri serverUrl)
{
if (IsVicSmartNetwork(domainSuffixes))
{
var potentialVicSmartAddresses = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.SelectMany(ni => ni.GetIPProperties().UnicastAddresses)
.Where(ua => ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
.Select(ua => ua.Address.GetAddressBytes())
.Where(a => a[0] == 10)
.Select(a => (ushort)((a[1] >> 4) & 0x000F) | ((a[1] << 4) & 0x00F0) | ((a[2] << 12) & 0xF000) | ((a[2] << 4) & 0x0F00))
.Distinct()
.Select(a => $"{a:x4}.vicsmart.discoict.com")
.ToList();
foreach (var potentialAddress in potentialVicSmartAddresses)
{
var records = GetTxtRecords(potentialAddress);
foreach (var record in records)
{
if (!record.Content.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
continue;
if (Uri.TryCreate(record.Content, UriKind.Absolute, out var discoveredUri))
{
serverUrl = discoveredUri;
return true;
}
}
}
}
serverUrl = null;
return false;
}
private static bool IsVicSmartNetwork(List<string> domainSuffixes)
{
if (domainSuffixes.Any(s => string.Equals("services.education.vic.gov.au", s, StringComparison.OrdinalIgnoreCase)) ||
domainSuffixes.Any(s => string.Equals("education.vic.gov.au", s, StringComparison.OrdinalIgnoreCase))
)
return true;
IPHostEntry doeWanDnsEntry;
try
{
doeWanDnsEntry = Dns.GetHostEntry("broadband.doe.wan");
if (doeWanDnsEntry.AddressList.Length > 0)
return true;
}
catch (Exception)
{ }
return false;
}
private static List<DnsTxtRecord> GetTxtRecords(string name)
{
IntPtr resourceRecordsPointer = IntPtr.Zero;
var records = new List<DnsTxtRecord>();
var retry = 5;
retry:
try
{
int queryResult = DnsQuery(ref name, NativeDnsQueryTypes.DNS_TYPE_TEXT, NativeDnsQueryOptions.DNS_QUERY_STANDARD, 0, ref resourceRecordsPointer, 0);
if (queryResult != 0)
{
if (queryResult == DNS_ERROR_RCODE_NAME_ERROR)
return records;
else if (queryResult == DNS_ERROR_BAD_PACKET && retry > 0)
{
// Sometimes a BAD_PACKET error is returned, retry a few times
Thread.Sleep(200);
retry--;
goto retry;
}
else
throw new Win32Exception(queryResult);
}
NativeDnsTxtRecord record;
for (var resourceRecordPointer = resourceRecordsPointer; !resourceRecordPointer.Equals(IntPtr.Zero); resourceRecordPointer = record.pNext)
{
record = Marshal.PtrToStructure<NativeDnsTxtRecord>(resourceRecordPointer);
if (record.wType == (ushort)NativeDnsQueryTypes.DNS_TYPE_TEXT)
records.Add(DnsTxtRecord.FromNativeRecord(record));
}
}
finally
{
if (resourceRecordsPointer != IntPtr.Zero)
DnsRecordListFree(resourceRecordsPointer, 0);
}
return records;
}
private static List<DnsSrvRecord> GetSRVRecords(string name)
{
IntPtr resourceRecordsPointer = IntPtr.Zero;
var records = new List<DnsSrvRecord>();
var retry = 5;
retry:
try
{
int queryResult = DnsQuery(ref name, NativeDnsQueryTypes.DNS_TYPE_SRV, NativeDnsQueryOptions.DNS_QUERY_STANDARD, 0, ref resourceRecordsPointer, 0);
if (queryResult != 0)
{
if (queryResult == DNS_ERROR_RCODE_NAME_ERROR)
return records;
else if (queryResult == DNS_ERROR_BAD_PACKET && retry > 0)
{
// Sometimes a BAD_PACKET error is returned, retry a few times
Thread.Sleep(200);
retry--;
goto retry;
}
else
throw new Win32Exception(queryResult);
}
NativeDnsSrvRecord record;
for (var resourceRecordPointer = resourceRecordsPointer; !resourceRecordPointer.Equals(IntPtr.Zero); resourceRecordPointer = record.pNext)
{
record = Marshal.PtrToStructure<NativeDnsSrvRecord>(resourceRecordPointer);
if (record.wType == (ushort)NativeDnsQueryTypes.DNS_TYPE_SRV)
records.Add(DnsSrvRecord.FromNativeRecord(record));
}
}
finally
{
if (resourceRecordsPointer != IntPtr.Zero)
DnsRecordListFree(resourceRecordsPointer, 0);
}
return records;
}
private enum NativeDnsQueryOptions
{
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
DNS_QUERY_BYPASS_CACHE = 8,
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
DNS_QUERY_NO_HOSTS_FILE = 0x40,
DNS_QUERY_NO_LOCAL_NAME = 0x20,
DNS_QUERY_NO_NETBT = 0x80,
DNS_QUERY_NO_RECURSION = 4,
DNS_QUERY_NO_WIRE_QUERY = 0x10,
DNS_QUERY_RESERVED = -16777216,
DNS_QUERY_RETURN_MESSAGE = 0x200,
DNS_QUERY_STANDARD = 0,
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
DNS_QUERY_USE_TCP_ONLY = 2,
DNS_QUERY_WIRE_ONLY = 0x100
}
private enum NativeDnsQueryTypes
{
DNS_TYPE_TEXT = 0x0010,
DNS_TYPE_SRV = 0x0021
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsSrvRecord
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string pNameTarget;
public ushort wPriority;
public ushort wWeight;
public ushort wPort;
public ushort Pad;
}
private class DnsSrvRecord
{
public string Name { get; set; }
public int Type { get; set; }
public int Ttl { get; set; }
public string Target { get; set; }
public int Priority { get; set; }
public int Weight { get; set; }
public int Port { get; set; }
public static DnsSrvRecord FromNativeRecord(NativeDnsSrvRecord nativeRecord)
{
return new DnsSrvRecord
{
Name = nativeRecord.pName,
Type = nativeRecord.wType,
Ttl = nativeRecord.dwTtl,
Target = nativeRecord.pNameTarget,
Priority = nativeRecord.wPriority,
Weight = nativeRecord.wWeight,
Port = nativeRecord.wPort
};
}
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsTxtRecord
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
public uint dwStringLength;
[MarshalAs(UnmanagedType.LPWStr)]
public string pStringArray;
}
private class DnsTxtRecord
{
public string Name { get; set; }
public int Type { get; set; }
public int Ttl { get; set; }
public string Content { get; set; }
public static DnsTxtRecord FromNativeRecord(NativeDnsTxtRecord nativeRecord)
{
return new DnsTxtRecord
{
Name = nativeRecord.pName,
Type = nativeRecord.wType,
Ttl = nativeRecord.dwTtl,
Content = nativeRecord.pStringArray,
};
}
}
}
}
+15 -3
View File
@@ -1,6 +1,7 @@
using Disco.Client.Extensions;
using Disco.Client.Interop;
using System;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
@@ -26,7 +27,7 @@ namespace Disco.Client
}
public static void UpdateStatus(string SubHeading, string Message, bool ShowProgress, int Progress)
{
Console.WriteLine($"#{SubHeading.EscapeMessage()},{Message.EscapeMessage()},{ShowProgress.ToString()},{Progress.ToString()}");
Console.WriteLine($"#{SubHeading.EscapeMessage()},{Message.EscapeMessage()},{ShowProgress},{Progress}");
}
public static void TryDelay(int Milliseconds)
{
@@ -38,6 +39,11 @@ namespace Disco.Client
{
StringBuilder message = new StringBuilder();
message.AppendLine($"Version: {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}");
message.Append($"Server: {Program.ServerUrl})");
if (Program.ServerUrl.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
message.AppendLine(" [Secure]");
else
message.AppendLine(" [Insecure]");
message.AppendLine($"Device: {Hardware.Information.SerialNumber} ({Hardware.Information.Manufacturer} {Hardware.Information.Model})");
Console.ForegroundColor = ConsoleColor.Yellow;
UpdateStatus("Preparation Client Started", message.ToString(), false, 0);
@@ -48,12 +54,18 @@ namespace Disco.Client
{
Console.ForegroundColor = ConsoleColor.Magenta;
ClientServiceException clientServiceException = ex as ClientServiceException;
if (clientServiceException != null)
if (ex is ClientServiceException clientServiceException)
{
UpdateStatus($"An error occurred during {clientServiceException.ServiceFeature}",
clientServiceException.Message, false, 0);
}
else if (ex is WebException exWeb &&
exWeb.Response is HttpWebResponse webResponse &&
webResponse.StatusCode == HttpStatusCode.InternalServerError)
{
UpdateStatus("Something went wrong on the server",
"Review logs for more information (Configuration > Logging)", false, 0);
}
else
{
StringBuilder message = new StringBuilder();
+59 -6
View File
@@ -1,6 +1,8 @@
using Disco.Client.Extensions;
using Disco.Client.Interop;
using Disco.Models.ClientServices;
using System;
using System.Diagnostics;
using System.Linq;
using System.Net;
@@ -11,6 +13,9 @@ namespace Disco.Client
public static bool IsAuthenticated { get; set; }
public static bool RebootRequired { get; set; }
public static bool AllowUninstall { get; set; }
public static int BootstrapperVersion { get; private set; } = 1;
public static int BootstrapperProcessId { get; private set; } = -1;
public static Uri ServerUrl { get; private set; }
[STAThread]
public static void Main(string[] args)
@@ -24,12 +29,15 @@ namespace Disco.Client
{
Console.WriteLine("Waiting for Debugger to Attach");
System.Threading.Thread.Sleep(1000);
} while (!System.Diagnostics.Debugger.IsAttached);
} while (!Debugger.IsAttached);
}
#endif
// Initialize Environment Settings
SetupEnvironment();
SetupEnvironment(args);
if (ServerUrl == null)
keepProcessing = DiscoverDiscoIct();
// Report to Bootstrapper
Presentation.WriteBanner();
@@ -45,7 +53,7 @@ namespace Disco.Client
Presentation.WriteFooter(RebootRequired, AllowUninstall, !keepProcessing);
}
public static void SetupEnvironment()
public static void SetupEnvironment(string[] args)
{
// Hookup Unhandled Error Handling
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.CurrentDomain_UnhandledException;
@@ -54,21 +62,66 @@ namespace Disco.Client
WebRequest.DefaultWebProxy = new WebProxy();
// Override Http 100 Continue Behaviour
ServicePointManager.Expect100Continue = false;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
// Assume success unless otherwise notified
AllowUninstall = true;
if (args != null && args.Length == 3)
{
// Parse Bootstrapper Version
int parsedVersion;
if (int.TryParse(args[0], out parsedVersion))
BootstrapperVersion = parsedVersion;
// Parse Bootstrapper Process ID
int parsedProcessId;
if (int.TryParse(args[1], out parsedProcessId))
BootstrapperProcessId = parsedProcessId;
// Parse Server URL
Uri parsedUri;
if (Uri.TryCreate(args[2], UriKind.Absolute, out parsedUri))
ServerUrl = parsedUri;
}
else
{
BootstrapperVersion = 1;
BootstrapperProcessId = -1;
ServerUrl = null;
}
// Detect Disco.Bootstrapper - Create Enable UI Delay if Running
Presentation.DelayUI = false;
try
{
Presentation.DelayUI = (System.Diagnostics.Process.GetProcessesByName("Disco.ClientBootstrapper").Length > 0);
if (BootstrapperProcessId != -1)
{
var parentProcess = Process.GetProcessById(BootstrapperProcessId);
Presentation.DelayUI = !parentProcess.HasExited;
}
}
catch (Exception)
{
Presentation.DelayUI = true; // Add Delays on Error
}
}
public static bool DiscoverDiscoIct()
{
try
{
Presentation.UpdateStatus("Detecting Disco ICT", "Locating Disco ICT Server, Please wait...", true, -1);
Presentation.TryDelay(3000);
ServerUrl = EndpointDiscovery.DiscoverServer(null).Item1;
// Complete
return true;
}
catch (Exception ex)
{
ErrorReporting.ReportError(ex, false);
}
return false;
}
public static bool WhoAmI()
{
try
@@ -144,7 +197,7 @@ namespace Disco.Client
var secondsConsumed = (DateTimeOffset.Now - startTime).TotalSeconds;
var progress = (int)((secondsConsumed / totalSeconds) * 100);
Presentation.UpdateStatus($"Pending Device Enrolment Approval: {response.PendingIdentifier}", $"Waiting for enrolment session '{response.PendingIdentifier}' to be approved.{Environment.NewLine}Reason: {response.PendingReason}", true, progress);
Presentation.UpdateStatus($"Pending Device Enrolment Approval: {response.PendingIdentifier}", $"Server: {Program.ServerUrl}{Environment.NewLine}Reason: {response.PendingReason}", true, progress);
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10));
}
else
+2 -2
View File
@@ -1,9 +1,9 @@
@ECHO OFF
IF /I "%USERDOMAIN%"=="NT AUTHORITY" GOTO RunAsNetworkService
Disco.Client.exe
Disco.Client.exe %1 %2 %3
EXIT /B 0
:RunAsNetworkService
ECHO #Running,Launching Preparation Client, Please wait...{newline}Starting client as 'NT AUTHORITY\Network Service',true,-1
PsExec -acceptula -i -u "NT AUTHORITY\Network Service" -w "%CD%" "%CD%\Start.bat"
PsExec -acceptula -i -u "NT AUTHORITY\Network Service" -w "%CD%" "%CD%\Start.bat %1 %2 %3"
EXIT /B 0
+90 -101
View File
@@ -1,4 +1,6 @@
using System;
using Disco.Client.Interop;
using Disco.ClientBootstrapper.Interop;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -6,141 +8,115 @@ using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper
{
class BootstrapperLoop
internal class BootstrapperLoop
{
public Thread LoopThread;
public delegate void LoopCompleteCallback();
private LoopCompleteCallback mLoopCompleteCallback;
private IStatus statusUI;
private readonly Func<CancellationToken, Task> completeCallback;
private readonly CancellationToken cancellationToken;
private readonly IStatus statusUI;
private readonly Uri forcedServerUrl;
private string tempWorkingDirectory;
private StringBuilder errorMessage;
private Process clientProcess;
//#if DEBUG
// public const string DiscoServerName = "WS-GSHARP";
// public const int DiscoServerPort = 57252;
//#else
public const string DiscoServerName = "DISCO";
public const int DiscoServerPort = 9292;
//#endif
public BootstrapperLoop(IStatus StatusUI, LoopCompleteCallback Callback)
public BootstrapperLoop(IStatus statusUI, Uri forcedServerUrl, Func<CancellationToken, Task> callback, CancellationToken cancellationToken)
{
statusUI = StatusUI;
mLoopCompleteCallback = Callback;
errorMessage = new StringBuilder();
this.statusUI = statusUI;
this.forcedServerUrl = forcedServerUrl;
completeCallback = callback;
this.cancellationToken = cancellationToken;
}
public void Start()
{
LoopThread = new Thread(new ThreadStart(loopHost));
LoopThread.Start();
Task.Factory.StartNew(async () =>
{
await Loop(forcedServerUrl, cancellationToken);
}, cancellationToken);
}
private void loopHost()
private async Task Loop(Uri forcedServerUrl, CancellationToken cancellationToken)
{
try
{
loop();
}
catch (Exception ex)
{
if (ex.GetType() == typeof(ThreadAbortException))
return;
if (ex.GetType() == typeof(ThreadInterruptedException))
return;
Program.WriteAppError(ex);
throw;
}
}
private void loop()
{
#if Debug
statusUI.UpdateStatus("Waiting for Debugger", "Please wait...", true, -1);
try
{
do
{
System.Threading.Thread.Sleep(10);
} while (!System.Diagnostics.Debugger.IsAttached);
}
catch (Exception ex)
{
statusUI.UpdateStatus("Error", ex.Message, true, -1);
return;
}
#else
statusUI.UpdateStatus("System Preparation (Bootstrapper)", "Starting", "Please wait...", true, -1);
#endif
tempWorkingDirectory = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco\\Temp");
tempWorkingDirectory = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), @"Disco\Temp");
if (!Directory.Exists(tempWorkingDirectory))
Directory.CreateDirectory(tempWorkingDirectory);
// Check for Network Connectivity
statusUI.UpdateStatus(null, "Detecting Network", "Checking network connectivity, Please wait...", true, -1);
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
if (!NetworkInterop.HasNetworkConnectivity())
{
statusUI.UpdateStatus(null, "Detecting Network", "No network connectivity detected, Diagnosing...", true, -1);
statusUI_WriteAdapterInfo();
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
if (!NetworkInterop.HasNetworkConnectivity())
{
// Check for Wireless
var hasWireless = (Interop.NetworkInterop.NetworkAdapters.Count(na => na.IsWireless) > 0);
var hasWireless = (NetworkInterop.NetworkAdapters.Count(na => na.IsWireless) > 0);
if (hasWireless)
{
// True: Do wireless loop
statusUI.UpdateStatus(null, "Configuring Wireless Network", "Wireless adapter detected, Configuring...", true, -1);
Interop.NetworkInterop.ConfigureWireless();
await NetworkInterop.ConfigureWireless(cancellationToken);
statusUI.UpdateStatus(null, "Waiting for Wireless Network", null, true, 0);
for (int i = 0; i < 100; i++)
for (int i = 0; i < 30; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
Program.SleepThread(500, false);
if (Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
await Program.SleepThread(2000, false, cancellationToken);
if (NetworkInterop.HasNetworkConnectivity())
break;
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
if (!NetworkInterop.HasNetworkConnectivity())
{
statusUI.UpdateStatus(null, "Wireless Network Failed", "Unable to connect to the wireless network, please connect the network cable...", false);
Program.SleepThread(3000, false);
await Program.SleepThread(3000, false, cancellationToken);
}
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
if (!NetworkInterop.HasNetworkConnectivity())
{
// Instruct user to connect network cable
statusUI.UpdateStatus(null, "Please connect the network cable", null);
for (int i = 0; i < 100; i++)
for (int i = 0; i < 30; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
Program.SleepThread(500, false);
if (Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
await Program.SleepThread(2000, false, cancellationToken);
if (NetworkInterop.HasNetworkConnectivity())
break;
}
}
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
if (!NetworkInterop.HasNetworkConnectivity())
{
// Client Failed
if (mLoopCompleteCallback != null)
{
mLoopCompleteCallback.BeginInvoke(null, null);
}
if (completeCallback != null)
await completeCallback(cancellationToken);
return;
}
}
Tuple<Uri, string> serverDiscovery;
statusUI.UpdateStatus(null, "Detecting Disco ICT", "Locating Disco ICT Server, Please wait...", true, -1);
try
{
serverDiscovery = EndpointDiscovery.DiscoverServer(forcedServerUrl);
statusUI.UpdateStatus(null, null, $"{serverDiscovery.Item1} ({serverDiscovery.Item2})", true, -1);
}
catch (Exception)
{
statusUI.UpdateStatus(null, null, "Failed to locate Disco ICT Server, exiting...", true, -1);
await Program.SleepThread(2000, false, cancellationToken);
throw;
}
// Download Client
statusUI.UpdateStatus(null, "Downloading", "Retrieving Preparation Client, Please wait...", true, -1);
string clientSourceLocation = Path.Combine(tempWorkingDirectory, "PreparationClient.zip");
@@ -148,8 +124,29 @@ namespace Disco.ClientBootstrapper
{
// Don't use a proxy when downloading the Client
webClient.Proxy = new WebProxy();
webClient.DownloadFile($"http://{DiscoServerName}:{DiscoServerPort}/Services/Client/PreparationClient", clientSourceLocation);
webClient.Headers.Add("X-DiscoICT-Discovery", serverDiscovery.Item2);
try
{
webClient.DownloadFile(new Uri(serverDiscovery.Item1, "/Services/Client/PreparationClient"), clientSourceLocation);
}
catch (WebException ex)
{
if (ex.Response != null &&
ex.Response is HttpWebResponse response)
{
if (response.StatusCode == HttpStatusCode.BadRequest)
{
statusUI.UpdateStatus(null, "Download failed: Bad Request", response.StatusDescription, true, -1);
await Program.SleepThread(5000, false, cancellationToken);
}
else if (response.StatusCode == HttpStatusCode.InternalServerError)
{
statusUI.UpdateStatus(null, "Download failed: Something went wrong on the server", "Review logs for more information (Configuration > Logging)", true, -1);
await Program.SleepThread(5000, false, cancellationToken);
}
}
throw;
}
}
// Unzip Client
@@ -166,7 +163,7 @@ namespace Disco.ClientBootstrapper
// Launch Client
statusUI.UpdateStatus("System Preparation (Client)", "Running", "Launching Preparation Client, Please wait...", true, -1);
ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat"))
ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat"), $"2 {Process.GetCurrentProcess().Id} {serverDiscovery.Item1}")
{
WorkingDirectory = clientLocation,
CreateNoWindow = true,
@@ -194,26 +191,29 @@ namespace Disco.ClientBootstrapper
// Cleanup
if (Directory.Exists(tempWorkingDirectory))
Directory.Delete(tempWorkingDirectory, true);
Interop.CertificateInterop.RemoveTempCerts();
CertificateInterop.RemoveTempCerts();
// Pause if Error
if (errorMessage.Length > 0)
{
Program.SleepThread(10000, true);
}
catch (Exception ex)
{
if (ex.GetType() == typeof(ThreadAbortException))
return;
if (ex.GetType() == typeof(ThreadInterruptedException))
return;
if (ex.GetType() == typeof(OperationCanceledException))
return;
Program.WriteAppError(ex);
}
// End Of Loop
if (mLoopCompleteCallback != null)
{
mLoopCompleteCallback.BeginInvoke(null, null);
}
if (completeCallback != null)
await completeCallback(cancellationToken);
}
void statusUI_WriteAdapterInfo()
private void statusUI_WriteAdapterInfo()
{
var info = new StringBuilder();
foreach (var na in Interop.NetworkInterop.NetworkAdapters)
foreach (var na in NetworkInterop.NetworkAdapters)
{
if (na.IsWireless)
{
@@ -228,11 +228,10 @@ namespace Disco.ClientBootstrapper
}
void clientProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
private void clientProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.Data))
{
Debug.WriteLine($"OUTPUT: {e.Data}");
var data = e.Data.Substring(1).Split(new char[] { ',' });
switch (e.Data[0])
{
@@ -249,15 +248,5 @@ namespace Disco.ClientBootstrapper
}
}
//void clientProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
//{
// if (!string.IsNullOrEmpty(e.Data))
// {
// System.Diagnostics.Debug.WriteLine(string.Format("ERROR: {0}", e.Data));
// this.errorMessage.AppendLine(e.Data);
// statusUI.UpdateStatus(null, "An Error Occurred", this.errorMessage.ToString(), false);
// }
//}
}
}
@@ -90,6 +90,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Disco.Client\Interop\EndpointDiscovery.cs">
<Link>Interop\EndpointDiscovery.cs</Link>
</Compile>
<Compile Include="..\Resources\Libraries\DotNetZip\Source\BZip2\BitWriter.cs">
<Link>DotNetZip\BZip2\BitWriter.cs</Link>
</Compile>
+16 -16
View File
@@ -7,22 +7,22 @@ namespace Disco.ClientBootstrapper
{
private delegate void dUpdateStatus(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress);
private dUpdateStatus mUpdateStatus;
private readonly dUpdateStatus mUpdateStatus;
public FormStatus()
{
InitializeComponent();
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
this.labelVersion.Text = $"v{version.ToString(3)}";
labelVersion.Text = $"v{version.ToString(3)}";
this.FormClosed += new FormClosedEventHandler(FormStatus_FormClosed);
FormClosed += new FormClosedEventHandler(FormStatus_FormClosed);
mUpdateStatus = new dUpdateStatus(UpdateStatusDo);
Cursor.Hide();
}
void FormStatus_FormClosed(object sender, FormClosedEventArgs e)
private void FormStatus_FormClosed(object sender, FormClosedEventArgs e)
{
Cursor.Show();
Program.ExitApplication();
@@ -32,43 +32,43 @@ namespace Disco.ClientBootstrapper
{
try
{
this.Invoke(mUpdateStatus, Heading, SubHeading, Message, ShowProgress, Progress);
Invoke(mUpdateStatus, Heading, SubHeading, Message, ShowProgress, Progress);
}
catch (Exception) { }
}
private void UpdateStatusDo(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress)
{
if (Heading != null)
if (this.labelHeading.Text != Heading)
this.labelHeading.Text = Heading;
if (labelHeading.Text != Heading)
labelHeading.Text = Heading;
if (SubHeading != null)
if (this.labelSubHeading.Text != SubHeading)
this.labelSubHeading.Text = SubHeading;
if (labelSubHeading.Text != SubHeading)
labelSubHeading.Text = SubHeading;
if (Message != null)
if (this.labelMessage.Text != Message)
this.labelMessage.Text = Message;
if (labelMessage.Text != Message)
labelMessage.Text = Message;
if (ShowProgress.HasValue)
{
if (ShowProgress.Value)
{
this.progressBar.Visible = true;
progressBar.Visible = true;
if (Progress.HasValue)
{
if (Progress.Value >= 0)
{
this.progressBar.Value = Math.Min(Progress.Value, 100);
this.progressBar.Style = ProgressBarStyle.Continuous;
progressBar.Value = Math.Min(Progress.Value, 100);
progressBar.Style = ProgressBarStyle.Continuous;
}
else
{
this.progressBar.Style = ProgressBarStyle.Marquee;
progressBar.Style = ProgressBarStyle.Marquee;
}
}
}
else
{
this.progressBar.Visible = false;
progressBar.Visible = false;
}
}
}
+23 -28
View File
@@ -1,43 +1,36 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper
{
class InstallLoop
internal class InstallLoop
{
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly string installLocation;
private readonly string wimImageId;
private readonly string tempPath;
private readonly Action completeCallback;
private readonly Uri forcedServerUrl;
public Thread LoopThread;
public delegate void CompleteCallback();
private CompleteCallback mCompleteCallback;
private string InstallLocation;
private string WimImageId;
private string TempPath;
public InstallLoop(string InstallLocation, string WimImageId, string TempPath)
public InstallLoop(string installLocation, string wimImageId, string tempPath, Action completeCallback, Uri forcedServerUrl)
{
this.InstallLocation = InstallLocation;
this.WimImageId = WimImageId;
this.TempPath = TempPath;
this.installLocation = installLocation;
this.wimImageId = wimImageId;
this.tempPath = tempPath;
this.completeCallback = completeCallback;
this.forcedServerUrl = forcedServerUrl;
}
public void Start(CompleteCallback Callback)
public void Start()
{
mCompleteCallback = Callback;
LoopThread = new Thread(new ThreadStart(loopHost));
LoopThread.Start();
}
private void loopHost()
var cancellationToken = cancellationTokenSource.Token;
Task.Run(async () =>
{
try
{
//Program.Status.UpdateStatus(null, null, "Testing UI");
//Program.SleepThread(5000, false);
Interop.InstallInterop.Install(InstallLocation, WimImageId, TempPath);
if (mCompleteCallback != null)
{
mCompleteCallback.BeginInvoke(null, null);
}
await Interop.InstallInterop.Install(installLocation, wimImageId, tempPath, forcedServerUrl, cancellationToken);
completeCallback?.BeginInvoke(null, null);
}
catch (Exception ex)
{
@@ -45,10 +38,12 @@ namespace Disco.ClientBootstrapper
return;
if (ex.GetType() == typeof(ThreadInterruptedException))
return;
if (ex.GetType() == typeof(OperationCanceledException))
return;
Program.WriteAppError(ex);
throw;
}
}
}, cancellationToken);
}
}
}
@@ -4,6 +4,8 @@ using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper.Interop
{
@@ -20,12 +22,12 @@ namespace Disco.ClientBootstrapper.Interop
//Remove(StoreName.Root, StoreLocation.LocalMachine, _tempCerts);
}
}
public static void AddTempCerts()
public static async Task AddTempCerts(CancellationToken cancellationToken)
{
if (_tempCerts == null)
_tempCerts = new List<string>();
var inlineCertificateLocation = Program.InlinePath.Value;
var inlineCertificateLocation = Path.GetDirectoryName(typeof(Program).Assembly.Location);
// Root Certificates
try
@@ -35,6 +37,7 @@ namespace Disco.ClientBootstrapper.Interop
{
foreach (var certFile in CertFiles)
{
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password");
var result = Add(StoreName.Root, StoreLocation.LocalMachine, cert);
if (result)
@@ -42,7 +45,7 @@ namespace Disco.ClientBootstrapper.Interop
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, $"Added Root Certificate: {cert.ShortSubjectName()}");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
}
}
}
@@ -60,6 +63,7 @@ namespace Disco.ClientBootstrapper.Interop
{
foreach (var certFile in CertFiles)
{
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password");
var result = Add(StoreName.CertificateAuthority, StoreLocation.LocalMachine, cert);
if (result)
@@ -67,7 +71,7 @@ namespace Disco.ClientBootstrapper.Interop
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, $"Added Intermediate Certificate: {cert.ShortSubjectName()}");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
}
}
}
@@ -85,6 +89,7 @@ namespace Disco.ClientBootstrapper.Interop
{
foreach (var certFile in CertFiles)
{
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
var result = Add(StoreName.My, StoreLocation.LocalMachine, cert);
if (result)
@@ -92,7 +97,7 @@ namespace Disco.ClientBootstrapper.Interop
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, $"Added Host Certificate: {cert.ShortSubjectName()}");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
}
}
}
@@ -4,15 +4,17 @@ using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper.Interop
{
public static class InstallInterop
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags);
private static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags);
[Flags]
enum MoveFileFlags
private enum MoveFileFlags
{
MOVEFILE_REPLACE_EXISTING = 0x00000001,
MOVEFILE_COPY_ALLOWED = 0x00000002,
@@ -22,19 +24,19 @@ namespace Disco.ClientBootstrapper.Interop
MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020
}
private static void Install(string RootFilesystemLocation, RegistryKey RootRegistryLocation, string FilesystemInstallLocation, string VirtualRootFilesystemLocation)
private static async Task Install(string rootFilesystemLocation, RegistryKey rootRegistryLocation, string filesystemInstallLocation, string virtualRootFilesystemLocation, Uri forcedServerUrl, CancellationToken cancellationToken)
{
var SourceLocation = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
var InstallLocation = Path.Combine(RootFilesystemLocation, FilesystemInstallLocation);
var BootstrapperCmdLinePath = Path.Combine(VirtualRootFilesystemLocation, FilesystemInstallLocation, "Disco.ClientBootstrapper.exe");
var InstallLocation = Path.Combine(rootFilesystemLocation, filesystemInstallLocation);
var BootstrapperCmdLinePath = Path.Combine(virtualRootFilesystemLocation, filesystemInstallLocation, "Disco.ClientBootstrapper.exe");
var GroupPolicyScriptsIniLocation = Path.Combine(RootFilesystemLocation, "Windows\\System32\\GroupPolicy\\Machine\\Scripts\\scripts.ini");
var GroupPolicyScriptsIniBackupLocation = Path.Combine(RootFilesystemLocation, "Windows\\System32\\GroupPolicy\\Machine\\Scripts\\disco_scripts.ini");
var GroupPolicyScriptsIniLocation = Path.Combine(rootFilesystemLocation, @"Windows\System32\GroupPolicy\Machine\Scripts\scripts.ini");
var GroupPolicyScriptsIniBackupLocation = Path.Combine(rootFilesystemLocation, @"Windows\System32\GroupPolicy\Machine\Scripts\disco_scripts.ini");
// Create file system Location
#region "Create File System Location"
Program.Status.UpdateStatus(null, null, "Creating Installation Location");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
if (Directory.Exists(InstallLocation))
{
// Try and Delete Directory
@@ -52,19 +54,23 @@ namespace Disco.ClientBootstrapper.Interop
var installDir = Directory.CreateDirectory(InstallLocation);
installDir.Attributes = installDir.Attributes | FileAttributes.Hidden;
}
cancellationToken.ThrowIfCancellationRequested();
#endregion
// Copy files to file system location
#region "Copy to File System"
Program.Status.UpdateStatus(null, null, "Copying Files");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
// Copy Bootstrapper
// ie: Executing Assembly
File.Copy(System.Reflection.Assembly.GetExecutingAssembly().Location, Path.Combine(InstallLocation, "Disco.ClientBootstrapper.exe"));
cancellationToken.ThrowIfCancellationRequested();
foreach (var file in Directory.EnumerateFiles(SourceLocation))
{
cancellationToken.ThrowIfCancellationRequested();
var fileName = Path.GetFileName(file);
// Only Copy Certain Files
@@ -86,7 +92,7 @@ namespace Disco.ClientBootstrapper.Interop
// Backup & Create Group Policy Scripts.ini
#region "Group Policy Scripts.ini"
Program.Status.UpdateStatus(null, null, "Creating Group Policy Script Entry");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
// Backup
if (!File.Exists(GroupPolicyScriptsIniBackupLocation))
{
@@ -95,6 +101,7 @@ namespace Disco.ClientBootstrapper.Interop
File.Move(GroupPolicyScriptsIniLocation, GroupPolicyScriptsIniBackupLocation);
}
}
cancellationToken.ThrowIfCancellationRequested();
// Create
if (File.Exists(GroupPolicyScriptsIniLocation))
@@ -105,56 +112,67 @@ namespace Disco.ClientBootstrapper.Interop
{
using (var scriptsIniStreamWriter = new StreamWriter(scriptsIniStream, Encoding.Unicode))
{
scriptsIniStreamWriter.Write($"[Startup]{Environment.NewLine}0CmdLine={BootstrapperCmdLinePath}{Environment.NewLine}0Parameters=/AllowUninstall");
scriptsIniStreamWriter.Flush();
scriptsIniStreamWriter.WriteLine("[Startup]");
scriptsIniStreamWriter.WriteLine($"0CmdLine={BootstrapperCmdLinePath}");
if (forcedServerUrl == null)
scriptsIniStreamWriter.WriteLine("0Parameters=/AllowUninstall");
else
scriptsIniStreamWriter.WriteLine($"0Parameters=/AllowUninstall {forcedServerUrl}");
}
}
cancellationToken.ThrowIfCancellationRequested();
#endregion
// Backup & Create Group Policy Registry
#region "Group Policy Registry"
Program.Status.UpdateStatus(null, null, "Creating Group Policy Registry Entries");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
// Backup Scripts
using (var regGroupPolicy = RootRegistryLocation.OpenSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy", true))
using (var regGroupPolicy = rootRegistryLocation.OpenSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy", true))
{
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{
RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts");
}
}
cancellationToken.ThrowIfCancellationRequested();
// Create Scripts
RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Shutdown").Dispose();
using (var regScriptsStartup = RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Startup\\0"))
rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Shutdown").Dispose();
using (var regScriptsStartup = rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Startup\0"))
{
regScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String);
regScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String);
regScriptsStartup.SetValue("FileSysPath", Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine"), RegistryValueKind.String);
regScriptsStartup.SetValue("FileSysPath", Path.Combine(Environment.SystemDirectory, @"GroupPolicy\Machine"), RegistryValueKind.String);
regScriptsStartup.SetValue("DisplayName", "Local Group Policy", RegistryValueKind.String);
regScriptsStartup.SetValue("GPOName", "Local Group Policy", RegistryValueKind.String);
regScriptsStartup.SetValue("PSScriptOrder", 1, RegistryValueKind.DWord);
using (var regScriptsStartup0 = regScriptsStartup.CreateSubKey("0"))
{
regScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String);
if (forcedServerUrl == null)
regScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String);
else
regScriptsStartup0.SetValue("Parameters", $"/AllowUninstall {forcedServerUrl}", RegistryValueKind.String);
regScriptsStartup0.SetValue("IsPowershell", 0, RegistryValueKind.DWord);
regScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary);
}
}
RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Shutdown").Dispose();
rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Shutdown").Dispose();
cancellationToken.ThrowIfCancellationRequested();
// Backup Scripts State
using (var regGroupPolicy = RootRegistryLocation.OpenSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine", true))
using (var regGroupPolicy = rootRegistryLocation.OpenSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\State\Machine", true))
{
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{
RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts");
}
}
cancellationToken.ThrowIfCancellationRequested();
// Create Scripts State
using (var regStateScriptsStartup = RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Startup\\0"))
using (var regStateScriptsStartup = rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Startup\0"))
{
regStateScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String);
regStateScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String);
@@ -165,17 +183,21 @@ namespace Disco.ClientBootstrapper.Interop
using (var regStateScriptsStartup0 = regStateScriptsStartup.CreateSubKey("0"))
{
regStateScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String);
if (forcedServerUrl == null)
regStateScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String);
else
regStateScriptsStartup0.SetValue("Parameters", $"/AllowUninstall {forcedServerUrl}", RegistryValueKind.String);
regStateScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary);
}
}
cancellationToken.ThrowIfCancellationRequested();
#endregion
// Set Registry Startup Environment Policies
#region "Registry Startup Policies"
Program.Status.UpdateStatus(null, null, "Creating Startup Policy Registry Entries");
Program.SleepThread(500, false);
using (var regWinlogon = RootRegistryLocation.OpenSubKey("Microsoft\\Windows NT\\CurrentVersion\\Winlogon", true))
await Program.SleepThread(500, false, cancellationToken);
using (var regWinlogon = rootRegistryLocation.OpenSubKey(@"Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{
regWinlogon.SetValue("HideStartupScripts", 0, RegistryValueKind.DWord);
regWinlogon.SetValue("RunStartupScriptSync", 1, RegistryValueKind.DWord);
@@ -183,96 +205,112 @@ namespace Disco.ClientBootstrapper.Interop
#endregion
}
public static void Install(string InstallLocation, string WimImageId, string TempPath)
public static async Task Install(string installLocation, string wimImageId, string tempPath, Uri forcedServerUrl, CancellationToken cancellationToken)
{
Program.Status.UpdateStatus("Installing Bootstrapper", "Starting", "Please wait...", false);
if (string.IsNullOrWhiteSpace(InstallLocation))
InstallLocation = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco");
if (string.IsNullOrWhiteSpace(installLocation))
installLocation = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco");
if (InstallLocation.EndsWith(".wim", StringComparison.OrdinalIgnoreCase))
cancellationToken.ThrowIfCancellationRequested();
if (installLocation.EndsWith(".wim", StringComparison.OrdinalIgnoreCase))
{
// Offline File System (WIM)
Program.Status.UpdateStatus("Installing Bootstrapper (Offline)", "Installing", $"Install Location: {InstallLocation}");
Program.SleepThread(1000, false);
Program.Status.UpdateStatus("Installing Bootstrapper (Offline)", "Installing", $"Install Location: {installLocation}");
await Program.SleepThread(1000, false, cancellationToken);
// Mount WIM
int wimImageIndex = 0;
using (var wim = new WIMInterop.WindowsImageContainer(InstallLocation, WIMInterop.WindowsImageContainer.CreateFileMode.OpenExisting, WIMInterop.WindowsImageContainer.CreateFileAccess.Write))
using (var wim = new WIMInterop.WindowsImageContainer(installLocation, WIMInterop.WindowsImageContainer.CreateFileMode.OpenExisting, WIMInterop.WindowsImageContainer.CreateFileAccess.Write))
{
if (WimImageId == null)
WimImageId = "1";
if (!int.TryParse(WimImageId, out wimImageIndex))
cancellationToken.ThrowIfCancellationRequested();
if (wimImageId == null)
wimImageId = "1";
if (!int.TryParse(wimImageId, out wimImageIndex))
{
Program.Status.UpdateStatus(null, "Analysing WIM", $"Looking for Image Name: {WimImageId}");
Program.SleepThread(500, false);
Program.Status.UpdateStatus(null, "Analysing WIM", $"Looking for Image Name: {wimImageId}");
await Program.SleepThread(500, false, cancellationToken);
for (int i = 0; i < wim.ImageCount; i++)
{
var wimImageInfo = new System.Xml.XmlDocument();
using (var wimImage = wim[i])
wimImageInfo.LoadXml(wimImage.ImageInformation);
var wimImageInfoName = wimImageInfo.SelectSingleNode("//IMAGE/NAME");
if (wimImageInfoName != null && wimImageInfoName.InnerText.Equals(WimImageId, StringComparison.OrdinalIgnoreCase))
if (wimImageInfoName != null && wimImageInfoName.InnerText.Equals(wimImageId, StringComparison.OrdinalIgnoreCase))
{
wimImageIndex = i + 1;
Program.Status.UpdateStatus(null, "Analysing WIM", $"Found Image Id '{WimImageId}' at Index {wimImageIndex}");
Program.SleepThread(500, false);
Program.Status.UpdateStatus(null, "Analysing WIM", $"Found Image Id '{wimImageId}' at Index {wimImageIndex}");
await Program.SleepThread(500, false, cancellationToken);
break;
}
}
}
}
cancellationToken.ThrowIfCancellationRequested();
if (wimImageIndex == 0)
{
Program.Status.UpdateStatus(null, "Error", $"Unable to load WIM Image Id: {WimImageId}");
Program.SleepThread(5000, false);
Program.Status.UpdateStatus(null, "Error", $"Unable to load WIM Image Id: {wimImageId}");
await Program.SleepThread(5000, false, cancellationToken);
return;
}
// Get Temp Path
var wimMountPath = Path.Combine(TempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimMount");
var wimMountPath = Path.Combine(tempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimMount");
if (Directory.Exists(wimMountPath))
Directory.Delete(wimMountPath, true);
Directory.CreateDirectory(wimMountPath);
var wimTempMountPath = Path.Combine(TempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimTempMount");
cancellationToken.ThrowIfCancellationRequested();
var wimTempMountPath = Path.Combine(tempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimTempMount");
if (Directory.Exists(wimTempMountPath))
Directory.Delete(wimTempMountPath, true);
Directory.CreateDirectory(wimTempMountPath);
cancellationToken.ThrowIfCancellationRequested();
bool wimCommitChanges = true;
WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback m_MessageCallback = null;
try
{
// Mount WIM
Program.Status.UpdateStatus(null, "Mounting WIM", $"Mounting WIM Image to '{wimMountPath}'");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
m_MessageCallback = new WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback(WimImageEventMessagePump);
WIMInterop.WindowsImageContainer.NativeMethods.RegisterCallback(m_MessageCallback);
WIMInterop.WindowsImageContainer.NativeMethods.MountImage(wimMountPath, InstallLocation, wimImageIndex, wimTempMountPath);
WIMInterop.WindowsImageContainer.NativeMethods.MountImage(wimMountPath, installLocation, wimImageIndex, wimTempMountPath);
// Load Local Machine Registry
var wimHivePath = Path.Combine(wimMountPath, "Windows\\System32\\config\\SOFTWARE");
Program.Status.UpdateStatus(null, "Mounting Offline Registry Hive", $"Mounting Offline Registry Hive at '{wimHivePath}'");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
using (var wimReg = new RegistryInterop(RegistryInterop.RegistryHives.HKEY_LOCAL_MACHINE, "DiscoClientBootstrapperWimHive", wimHivePath))
{
try
{
cancellationToken.ThrowIfCancellationRequested();
using (RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("DiscoClientBootstrapperWimHive", true))
{
string rootFileSystemLocation = wimMountPath;
string fileSystemInstallLocation = "Disco";
string virtualRootFileSystemLocation = "C:\\";
Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, virtualRootFileSystemLocation);
cancellationToken.ThrowIfCancellationRequested();
await Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, virtualRootFileSystemLocation, forcedServerUrl, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
}
}
finally
{
// Unload Local Machine Registry
Program.Status.UpdateStatus(null, "Unmounting Offline Registry Hive", $"Unmounting Offline Registry Hive at '{wimHivePath}'");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
wimReg.Unload();
}
}
}
catch (Exception)
{
wimCommitChanges = false;
@@ -282,8 +320,8 @@ namespace Disco.ClientBootstrapper.Interop
{
// Unmount WIM
Program.Status.UpdateStatus(null, "Unmounting WIM", $"Unmounting WIM Image at '{wimMountPath}'");
Program.SleepThread(500, false);
WIMInterop.WindowsImageContainer.NativeMethods.DismountImage(wimMountPath, InstallLocation, wimImageIndex, wimCommitChanges);
await Program.SleepThread(500, false, cancellationToken);
WIMInterop.WindowsImageContainer.NativeMethods.DismountImage(wimMountPath, installLocation, wimImageIndex, wimCommitChanges);
if (m_MessageCallback != null)
{
@@ -295,23 +333,25 @@ namespace Disco.ClientBootstrapper.Interop
Directory.Delete(wimMountPath, true);
if (Directory.Exists(wimTempMountPath))
Directory.Delete(wimTempMountPath, true);
cancellationToken.ThrowIfCancellationRequested();
}
}
else
{
// Online File System
Program.Status.UpdateStatus("Installing Bootstrapper (Online)", "Installing", $"Install Location: {InstallLocation}", true, -1);
Program.SleepThread(1000, false);
string rootFileSystemLocation = Path.GetPathRoot(InstallLocation);
Program.Status.UpdateStatus("Installing Bootstrapper (Online)", "Installing", $"Install Location: {installLocation}", true, -1);
await Program.SleepThread(1000, false, cancellationToken);
string rootFileSystemLocation = Path.GetPathRoot(installLocation);
RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("SOFTWARE", true);
string fileSystemInstallLocation = InstallLocation.Substring(rootFileSystemLocation.Length);
string fileSystemInstallLocation = installLocation.Substring(rootFileSystemLocation.Length);
Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, rootFileSystemLocation);
await Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, rootFileSystemLocation, forcedServerUrl, cancellationToken);
Program.Status.UpdateStatus(null, "Online File System Installation Complete", string.Empty, true, -1);
Program.SleepThread(1000, false);
await Program.SleepThread(1000, false, cancellationToken);
}
Program.Status.UpdateStatus(null, "Complete", "Finished Installing Bootstrapper");
Program.SleepThread(1500, false);
await Program.SleepThread(1500, false, cancellationToken);
}
private static uint WimImageEventMessagePump(
@@ -349,41 +389,28 @@ namespace Disco.ClientBootstrapper.Interop
return status;
}
public static void Uninstall()
public static async Task Uninstall(CancellationToken cancellationToken)
{
// Application Directory
var appDirectory = Program.InlinePath.Value;
if (Program.AllowUninstall && !appDirectory.StartsWith("\\\\"))
var appDirectory = Path.GetDirectoryName(typeof(Program).Assembly.Location);
if (Program.AllowUninstall && !appDirectory.StartsWith(@"\\"))
{
Program.Status.UpdateStatus("System Preparation (Bootstrapper)", "Uninstalling Bootstrapper...", string.Empty, false, 0);
Program.SleepThread(1000, true);
//var uninstallScriptLocation = System.IO.Path.Combine(appDirectory, "UninstallBootstrapper.vbs");
//if (System.IO.File.Exists(uninstallScriptLocation))
//{
// var bootstrapperPID = System.Diagnostics.Process.GetCurrentProcess().Id;
// var cscriptPath = System.IO.Path.Combine(Environment.SystemDirectory, "cscript.exe");
// var cscriptArgs = string.Format("\"{0}\" /WaitForProcessID:{1}", uninstallScriptLocation, bootstrapperPID);
// var startProc = new ProcessStartInfo(cscriptPath, cscriptArgs);
// startProc.WorkingDirectory = Environment.SystemDirectory;
// startProc.WindowStyle = ProcessWindowStyle.Hidden;
// Process.Start(startProc);
//}
await Program.SleepThread(1000, true, cancellationToken);
// Remove Registry Entries
using (var regWinlogon = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", true))
using (var regWinlogon = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{
regWinlogon.DeleteValue("HideStartupScripts", false);
regWinlogon.DeleteValue("RunStartupScriptSync", false);
}
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Shutdown", false);
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Startup", false);
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Shutdown", false);
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Startup", false);
Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Shutdown", false);
Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Startup", false);
Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Shutdown", false);
Registry.LocalMachine.DeleteSubKeyTree(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Scripts\Startup", false);
// Restore Registry Backups
using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy", true))
using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy", true))
{
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{
@@ -391,7 +418,7 @@ namespace Disco.ClientBootstrapper.Interop
RegistryUtilities.RenameSubKey(regGroupPolicy, "Disco_Scripts", "Scripts");
}
}
using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine", true))
using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine", true))
{
if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{
@@ -401,10 +428,10 @@ namespace Disco.ClientBootstrapper.Interop
}
// Delete Group Policy Script File
var groupPolicyScriptsPath = Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine\\Scripts\\scripts.ini");
var groupPolicyScriptsPath = Path.Combine(Environment.SystemDirectory, @"GroupPolicy\Machine\Scripts\scripts.ini");
if (File.Exists(groupPolicyScriptsPath))
File.Delete(groupPolicyScriptsPath);
var groupPolicyScriptsBackupPath = Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine\\Scripts\\disco_scripts.ini");
var groupPolicyScriptsBackupPath = Path.Combine(Environment.SystemDirectory, @"GroupPolicy\Machine\Scripts\disco_scripts.ini");
if (File.Exists(groupPolicyScriptsBackupPath))
File.Move(groupPolicyScriptsBackupPath, groupPolicyScriptsPath);
@@ -1,14 +1,17 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace Disco.ClientBootstrapper.Interop
{
static class NetworkInterop
internal static class NetworkInterop
{
#region PInvoke
@@ -164,30 +167,35 @@ namespace Disco.ClientBootstrapper.Interop
}
}
public static bool PingDiscoIct(string ServerName)
public static bool HasNetworkConnectivity()
{
using (Ping p = new Ping())
var nics = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.ToList();
foreach (var nic in nics)
{
try
if (nic.Supports(NetworkInterfaceComponent.IPv4))
{
PingReply pr = p.Send(ServerName, 2000);
if (pr.Status == IPStatus.Success)
return true;
else
return false;
}
catch (Exception)
{
return false;
}
var ipProps = nic.GetIPProperties();
var ipv4Props = ipProps.GetIPv4Properties();
if (ipv4Props.IsAutomaticPrivateAddressingActive)
continue;
return ipProps.UnicastAddresses
.Where(ua => ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
.Any();
}
}
public static void ConfigureWireless()
return false;
}
public static async Task ConfigureWireless(CancellationToken cancellationToken)
{
// Add Certificates
Program.Status.UpdateStatus(null, null, "Configuring Wireless Certificates");
CertificateInterop.AddTempCerts();
await CertificateInterop.AddTempCerts(cancellationToken);
// Add Wireless Profiles
Program.Status.UpdateStatus(null, null, "Configuring Wireless Profiles");
@@ -208,15 +216,16 @@ namespace Disco.ClientBootstrapper.Interop
{
foreach (var inlineWirelessProfile in wirelessInlineProfiles)
{
cancellationToken.ThrowIfCancellationRequested();
if (inlineWirelessProfile.AddProfile(wlanHandle, na.Guid))
{
Program.Status.UpdateStatus(null, null, $"Added Wireless Profile: {inlineWirelessProfile.ProfileName}");
Program.SleepThread(500, false);
await Program.SleepThread(500, false, cancellationToken);
}
else
{
Program.Status.UpdateStatus(null, null, $"Unable to add Wireless Profile: {inlineWirelessProfile.ProfileName}");
Program.SleepThread(5000, false);
await Program.SleepThread(5000, false, cancellationToken);
}
}
}
@@ -246,14 +255,15 @@ namespace Disco.ClientBootstrapper.Interop
private static List<WirelessProfile> GetInlineWirelessProfiles()
{
var inlineProfileFiles = System.IO.Directory.EnumerateFiles(Program.InlinePath.Value, "WLAN_Profile_*.xml").ToList();
var directoryPath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
var inlineProfileFiles = Directory.EnumerateFiles(directoryPath, "WLAN_Profile_*.xml").ToList();
var inlineProfiles = new List<WirelessProfile>(inlineProfileFiles.Count);
foreach (var filename in inlineProfileFiles)
{
var profile = new WirelessProfile()
{
Filename = filename,
ProfileXml = System.IO.File.ReadAllText(filename)
ProfileXml = File.ReadAllText(filename)
};
var profileXml = new XmlDocument();
profileXml.LoadXml(profile.ProfileXml);
+53 -49
View File
@@ -1,36 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Disco.ClientBootstrapper
{
static class Program
internal static class Program
{
public static IStatus Status { get; set; }
public static BootstrapperLoop BootstrapperLoop { get; set; }
public static InstallLoop InstallLoop { get; set; }
private static readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
public static IStatus Status { get; private set; }
public static List<string> PostBootstrapperActions { get; set; }
public static bool AllowUninstall { get; set; }
public static bool ApplicationExiting { get; set; }
public static Lazy<string> InlinePath = new Lazy<string>(() =>
{
return System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
});
public static bool AllowUninstall { get; private set; }
public static Uri ForcedServerUrl { get; private set; } = null;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
private static void Main(string[] args)
{
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
if (args.Length > 0)
{
#if DEBUG
if (args.Any(a => a.Equals("debug", StringComparison.OrdinalIgnoreCase)))
{
do
{
Console.WriteLine("Waiting for Debugger to Attach");
Thread.Sleep(1000);
} while (!System.Diagnostics.Debugger.IsAttached);
}
#endif
if (args.Any(a => a.StartsWith("http://", StringComparison.OrdinalIgnoreCase)))
throw new ArgumentException("Only HTTPS URLs are supported for a forced server URL.");
var forcedServerArg = args.FirstOrDefault(a => a.StartsWith("https://", StringComparison.OrdinalIgnoreCase));
if (forcedServerArg != null)
{
if (Uri.TryCreate(forcedServerArg, UriKind.Absolute, out var forcedUri))
ForcedServerUrl = forcedUri;
else
throw new ArgumentException("The provided forced server URL is not valid.");
}
switch (args[0].ToLower())
{
case "/install":
@@ -46,14 +68,17 @@ namespace Disco.ClientBootstrapper
wimImage = args[2];
if (args.Length > 3)
tempPath = args[3];
InstallLoop = new InstallLoop(installLocation, wimImage, tempPath);
InstallLoop.Start(new InstallLoop.CompleteCallback(InstallComplete));
var installLoop = new InstallLoop(installLocation, wimImage, tempPath, InstallComplete, ForcedServerUrl);
installLoop.Start();
Application.Run();
return;
case "/uninstall":
AllowUninstall = true;
Status = new NullStatus();
Interop.InstallInterop.Uninstall();
Task.Run(async () =>
{
await Interop.InstallInterop.Uninstall(cancellationTokenSource.Token);
}).Wait(cancellationTokenSource.Token);
return;
case "/allowuninstall":
AllowUninstall = true;
@@ -71,13 +96,13 @@ namespace Disco.ClientBootstrapper
statusForm.Show();
}
BootstrapperLoop = new BootstrapperLoop(Status, new BootstrapperLoop.LoopCompleteCallback(LoopComplete));
BootstrapperLoop.Start();
var bootstrapperLoop = new BootstrapperLoop(Status, ForcedServerUrl, LoopComplete, cancellationTokenSource.Token);
bootstrapperLoop.Start();
Application.Run();
}
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
WriteAppError(e.Exception);
}
@@ -100,7 +125,7 @@ namespace Disco.ClientBootstrapper
catch (Exception) { }
}
public static void LoopComplete()
public static async Task LoopComplete(CancellationToken cancellationToken)
{
// Run Post Actions
if (PostBootstrapperActions != null)
@@ -108,32 +133,32 @@ namespace Disco.ClientBootstrapper
// Check Uninstall
if (AllowUninstall && PostBootstrapperActions.Contains("UninstallBootstrapper"))
{
Interop.InstallInterop.Uninstall();
await Interop.InstallInterop.Uninstall(cancellationToken);
}
// Check ShutdownActions
if (PostBootstrapperActions.Contains("Shutdown"))
{
Status.UpdateStatus("System Preparation (Bootstrapper)", "Shutting Down; Finished...", string.Empty, false, 0);
SleepThread(4000, true);
await SleepThread(4000, true, cancellationToken);
Interop.ShutdownInterop.Shutdown();
}
else if (PostBootstrapperActions.Contains("Reboot"))
{
Status.UpdateStatus("System Preparation (Bootstrapper)", "Rebooting; Finished...", string.Empty, false, 0);
SleepThread(4000, true);
await SleepThread(4000, true, cancellationToken);
Interop.ShutdownInterop.Reboot();
}
else
{
Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0);
SleepThread(2000, true);
await SleepThread(2000, true, cancellationToken);
}
}
else
{
Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0);
SleepThread(2000, true);
await SleepThread(2000, true, cancellationToken);
}
ExitApplication();
@@ -146,33 +171,12 @@ namespace Disco.ClientBootstrapper
public static void ExitApplication()
{
if (!ApplicationExiting)
{
ApplicationExiting = true;
if (BootstrapperLoop != null)
{
if (BootstrapperLoop.LoopThread != null)
{
if (BootstrapperLoop.LoopThread.ThreadState == ThreadState.WaitSleepJoin)
{
BootstrapperLoop.LoopThread.Interrupt();
}
if (BootstrapperLoop.LoopThread.ThreadState == ThreadState.Running)
{
BootstrapperLoop.LoopThread.Abort();
}
}
}
if (!cancellationTokenSource.IsCancellationRequested)
cancellationTokenSource.Cancel();
Application.Exit();
}
}
public static void Trace(string Format, params string[] args)
{
System.Diagnostics.Debug.WriteLine(Format, args);
}
public static void SleepThread(int millisecondsTimeout, bool updateUI)
public static async Task SleepThread(int millisecondsTimeout, bool updateUI, CancellationToken cancellationToken)
{
if (updateUI)
{
@@ -180,12 +184,12 @@ namespace Disco.ClientBootstrapper
{
int progress = Convert.ToInt32(((Convert.ToDouble(i) / Convert.ToDouble(millisecondsTimeout)) * 100));
Status.UpdateStatus(null, null, null, true, progress);
Thread.Sleep(500);
await Task.Delay(500, cancellationToken);
}
}
else
{
Thread.Sleep(millisecondsTimeout);
await Task.Delay(millisecondsTimeout, cancellationToken);
}
}
}
@@ -14,5 +14,11 @@ namespace Disco.Data.Configuration.Modules
get => Get(DeviceExportOptions.DefaultOptions());
set => Set(value);
}
public bool EnrollmentLegacyDiscoveryDisabled
{
get => Get(false);
set => Set(value);
}
}
}
+3 -2
View File
@@ -41,8 +41,8 @@
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Entity" />
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath>
<Reference Include="System.ValueTuple, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.6.2\lib\net462\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@@ -62,6 +62,7 @@
<Compile Include="Repository\FlagType.cs" />
<Compile Include="Repository\User\UserComment.cs" />
<Compile Include="Repository\FlagPermission.cs" />
<Compile Include="Services\Devices\DeviceEnrolmentServerDiscoveryMethod.cs" />
<Compile Include="Services\Devices\DeviceFlags\DeviceFlagExportOptions.cs" />
<Compile Include="Services\Devices\DeviceFlags\DeviceFlagExportRecord.cs" />
<Compile Include="Services\Documents\DocumentExportOptions.cs" />
@@ -0,0 +1,13 @@
namespace Disco.Models.Services.Devices
{
public enum DeviceEnrolmentServerDiscoveryMethod
{
Unknown = 0,
Manual = 1,
SRV = 2,
VicSmart = 3,
Legacy = 4,
Mac = 50,
MacSecure = 51,
}
}
@@ -6,5 +6,6 @@ namespace Disco.Models.Services.Interop.ActiveDirectory
{
public string GroupId { get; set; }
public DateTime? FilterBeginDate { get; set; }
public bool UpdateDescription { get; set; } = true;
}
}
@@ -24,6 +24,7 @@ namespace Disco.Models.Services.Interop.DiscoServices
public List<StatisticIntPair> Stat_JobIdentifiers { get; set; }
public List<StatisticJob> Stat_Jobs { get; set; }
public List<StatisticInt> Stat_EnrollmentDiscovery { get; set; }
public class StatisticIntPair
{
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace Disco.Models.Services.Jobs.Noticeboards
{
@@ -9,6 +10,7 @@ namespace Disco.Models.Services.Jobs.Noticeboards
string DeviceSerialNumber { get; }
string DeviceComputerNameFriendly { get; }
string DeviceComputerName { get; }
string DeviceName { get; }
string DeviceLocation { get; }
string DeviceDescription { get; }
@@ -16,6 +18,7 @@ namespace Disco.Models.Services.Jobs.Noticeboards
int DeviceProfileId { get; }
int? DeviceAddressId { get; }
string DeviceAddressShortName { get; }
IEnumerable<int> JobQueueIds { get; }
string UserId { get; }
string UserIdFriendly { get; }
@@ -1,8 +1,17 @@
namespace Disco.Models.UI.Config.Enrolment
using System;
namespace Disco.Models.UI.Config.Enrolment
{
public interface ConfigEnrolmentIndexModel : BaseUIModel
{
string MacSshUsername { get; set; }
int PendingTimeoutMinutes { get; set; }
Uri MacEnrolUrl { get; set; }
bool HostingPluginInstalled { get; set; }
bool IsVicSmartDeployment { get; set; }
bool IsServicesEducationVicGovAuDomain { get; set; }
string DnsSrvRecordName { get; set; }
string DnsSrvRecordValue { get; set; }
bool LegacyDiscoveryEnabled { get; set; }
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="13.0.2" targetFramework="net45" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net462" />
<package id="System.ValueTuple" version="4.6.2" targetFramework="net462" />
</packages>
@@ -41,6 +41,10 @@
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
+4
View File
@@ -56,6 +56,10 @@
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
@@ -1,6 +1,7 @@
using Disco.Data.Repository;
using Disco.Models.ClientServices;
using Disco.Models.Repository;
using Disco.Models.Services.Devices;
using Disco.Services.Authorization;
using Disco.Services.Interop.ActiveDirectory;
using Disco.Services.Users;
@@ -18,6 +19,18 @@ namespace Disco.Services.Devices.Enrolment
private static readonly string pendingIdentifierAlphabet = "23456789ABCDEFGHJKMNPQRSTWXYZ";
private static readonly Random pendingIdentifierRng = new Random();
private static readonly ConcurrentDictionary<string, EnrolResponse> pendingEnrolments = new ConcurrentDictionary<string, EnrolResponse>();
private static readonly Dictionary<DeviceEnrolmentServerDiscoveryMethod, int> discoveryMethodStatistics = Enum.GetValues(typeof(DeviceEnrolmentServerDiscoveryMethod)).Cast<DeviceEnrolmentServerDiscoveryMethod>().ToDictionary(k => k, k => 0);
public static string GetDnsServiceLocationRecordName()
=> $"_discoict._tcp.{ActiveDirectory.Context.PrimaryDomain.Name}";
public static void IncrementDiscoveryMethod(DeviceEnrolmentServerDiscoveryMethod method)
{
discoveryMethodStatistics[method]++;
}
public static IEnumerable<KeyValuePair<DeviceEnrolmentServerDiscoveryMethod, int>> GetDiscoveryMethodStatistics()
=> discoveryMethodStatistics.AsEnumerable();
private static void CleanupPendingEnrolments()
{
@@ -175,9 +188,11 @@ namespace Disco.Services.Devices.Enrolment
{
if (!authenticatedToken.Has(Claims.ComputerAccount))
throw new EnrolmentSafeException($"Connection not correctly authenticated (SN: {Request.SerialNumber}; Auth User: {authenticatedToken.User.UserId})");
else if (!string.Equals($"{Request.ComputerName}$", authenticatedToken.User.DomainUsername, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException($"Connection not correctly authenticated (SN: {Request.SerialNumber}; Computer Name: {Request.ComputerName}; Auth User: {authenticatedToken.User.UserId})");
if (domain == null)
domain = ActiveDirectory.Context.GetDomainByName(Request.DNSDomainName);
if (domain == null && !ActiveDirectory.Context.TryGetDomainByName(Request.DNSDomainName, out domain))
throw new EnrolmentSafeException($"The specified domain name '{Request.DNSDomainName}' is not recognized or reachable.");
if (!authenticatedToken.User.UserId.Equals($@"{domain.NetBiosName}\{Request.ComputerName}$", StringComparison.OrdinalIgnoreCase))
throw new EnrolmentSafeException($"Connection not correctly authenticated (SN: {Request.SerialNumber}; Auth User: {authenticatedToken.User.UserId})");
@@ -379,9 +394,7 @@ namespace Disco.Services.Devices.Enrolment
EnrolmentLog.LogSessionTaskProvisioningADAccount(sessionId, device.SerialNumber, device.DeviceDomainId);
adMachineAccount = domainController.Value.RetrieveADMachineAccount(device.DeviceDomainId);
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount, out var offlineProvisionDiagnosicInfo);
EnrolmentLog.LogSessionDiagnosticInformation(sessionId, offlineProvisionDiagnosicInfo);
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount);
response.RequireReboot = true;
}
@@ -428,10 +441,7 @@ namespace Disco.Services.Devices.Enrolment
response.ComputerName = calculatedAccountUsername;
// Create New Account
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount, out var offlineProvisionDiagnosicInfo);
EnrolmentLog.LogSessionDiagnosticInformation(sessionId, offlineProvisionDiagnosicInfo);
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount);
response.RequireReboot = true;
}
+14 -4
View File
@@ -53,8 +53,8 @@
<Reference Include="LumenWorks.Framework.IO">
<HintPath>..\packages\LumenWorks.Framework.IO.3.8.0\lib\net20\LumenWorks.Framework.IO.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNet.SignalR.Core, Version=2.1.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.SignalR.Core.2.1.2\lib\net45\Microsoft.AspNet.SignalR.Core.dll</HintPath>
<Reference Include="Microsoft.AspNet.SignalR.Core, Version=2.4.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.SignalR.Core.2.4.3\lib\net45\Microsoft.AspNet.SignalR.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNetCore.Connections.Abstractions.9.0.0\lib\net462\Microsoft.AspNetCore.Connections.Abstractions.dll</HintPath>
@@ -243,8 +243,8 @@
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath>
<Reference Include="System.ValueTuple, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.6.2\lib\net462\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
@@ -464,6 +464,7 @@
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryGroupCache.cs" />
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryManagedGroups.cs" />
<Compile Include="Interop\ActiveDirectory\ADDeviceDescriptionUpdateTask.cs" />
<Compile Include="Interop\ActiveDirectory\ADDeviceOfflineDomainJoining.cs" />
<Compile Include="Interop\ActiveDirectory\ADDirectoryEntry.cs" />
<Compile Include="Interop\ActiveDirectory\ADDiscoverServers.cs" />
<Compile Include="Interop\ActiveDirectory\ADDomain.cs" />
@@ -484,6 +485,7 @@
<Compile Include="Interop\ActiveDirectory\IADObject.cs" />
<Compile Include="Interop\DiscoServices\ActivationCleanupTask.cs" />
<Compile Include="Interop\DiscoServices\ActivationService.cs" />
<Compile Include="Interop\DiscoServices\AuthenticationSessionScope.cs" />
<Compile Include="Interop\DiscoServices\DiscoServiceHelpers.cs" />
<Compile Include="Interop\DiscoServices\Jobs.cs" />
<Compile Include="Interop\DiscoServices\LicenseValidationTask.cs" />
@@ -496,6 +498,14 @@
<Compile Include="Interop\DiscoServices\Upload\UploadOnlineClient.cs" />
<Compile Include="Interop\DiscoServices\Upload\UploadOnlineService.cs" />
<Compile Include="Interop\DiscoServices\Upload\UploadOnlineSyncTask.cs" />
<Compile Include="Interop\DNS\ADnsRecord.cs" />
<Compile Include="Interop\DNS\CnameDnsRecord.cs" />
<Compile Include="Interop\DNS\DnsRecord.cs" />
<Compile Include="Interop\DNS\DnsRecordType.cs" />
<Compile Include="Interop\DNS\DnsService.cs" />
<Compile Include="Interop\DNS\NativeDns.cs" />
<Compile Include="Interop\DNS\SrvDnsRecord.cs" />
<Compile Include="Interop\DNS\TxtDnsRecord.cs" />
<Compile Include="Interop\IIS\PreserveIisBindingsTask.cs" />
<Compile Include="Interop\MimeTypes.cs" />
<Compile Include="Interop\VicEduDept\VicSmart.cs" />
@@ -149,27 +149,28 @@ namespace Disco.Services
template = database.DocumentTemplates.Find(templateId);
if (template == null)
throw new ArgumentException("Invalid document template id", nameof(templateId));
if (!Enum.TryParse(template.Scope, out AttachmentTypes scope))
throw new InvalidOperationException("Unknown DocumentType Scope");
// validate authorization
switch (template.Scope)
switch (scope)
{
case DocumentTemplate.DocumentTemplateScopes.Device:
case AttachmentTypes.Device:
authorization.Require(Claims.Device.Actions.GenerateDocuments);
break;
case DocumentTemplate.DocumentTemplateScopes.Job:
case AttachmentTypes.Job:
authorization.Require(Claims.Job.Actions.GenerateDocuments);
break;
case DocumentTemplate.DocumentTemplateScopes.User:
case AttachmentTypes.User:
authorization.Require(Claims.User.Actions.GenerateDocuments);
break;
default:
throw new InvalidOperationException("Unknown DocumentType Scope");
throw new InvalidOperationException("Unsupported DocumentType Scope");
}
// resolve target
target = template.ResolveScopeTarget(database, targetId, out targetUser);
if (target == null)
throw new ArgumentException("Target not found", nameof(targetId));
target = template.ResolveScopeTarget(database, targetId, out targetUser)
?? throw new ArgumentException("Target not found", nameof(targetId));
}
public static IEnumerable<OnImportUserFlagRule> GetOnImportUserFlagRuleDetails(this DocumentTemplate template, DiscoDataContext database)
@@ -1,6 +1,8 @@
using Disco.Data.Repository;
using Disco.Models.Repository;
using Disco.Models.Services.Documents;
using Disco.Services.Authorization;
using Disco.Services.Documents;
using Disco.Services.Expressions;
using System;
using System.Collections.Generic;
@@ -139,12 +141,46 @@ namespace Disco.Services
}
public static IAttachmentTarget ResolveScopeTarget(this DocumentTemplatePackage templatePackage, DiscoDataContext database, string targetId)
=> templatePackage.ResolveScopeTarget(database, targetId, out _);
public static IAttachmentTarget ResolveScopeTarget(this DocumentTemplatePackage templatePackage, DiscoDataContext database, string targetId, out User targetUser)
{
if (templatePackage == null)
throw new ArgumentNullException(nameof(templatePackage));
return templatePackage.Scope.ResolveScopeTarget(database, targetId);
return templatePackage.Scope.ResolveScopeTarget(database, targetId, out targetUser);
}
public static void GetPackageAndTarget(DiscoDataContext database, AuthorizationToken authorization, string packageId, string targetId, out DocumentTemplatePackage package, out IAttachmentTarget target, out User targetUser)
{
if (string.IsNullOrWhiteSpace(packageId))
throw new ArgumentNullException(nameof(packageId));
if (string.IsNullOrWhiteSpace(targetId))
throw new ArgumentNullException(nameof(targetId));
// get template
package = DocumentTemplatePackages.GetPackage(packageId)
?? throw new ArgumentException("Invalid document template package id", nameof(packageId));
// validate authorization
switch (package.Scope)
{
case AttachmentTypes.Device:
authorization.Require(Claims.Device.Actions.GenerateDocuments);
break;
case AttachmentTypes.Job:
authorization.Require(Claims.Job.Actions.GenerateDocuments);
break;
case AttachmentTypes.User:
authorization.Require(Claims.User.Actions.GenerateDocuments);
break;
default:
throw new InvalidOperationException("Unsupported DocumentType Scope");
}
// resolve target
target = package.ResolveScopeTarget(database, targetId, out targetUser)
?? throw new ArgumentException("Target not found", nameof(targetId));
}
}
}
+21 -7
View File
@@ -8,6 +8,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
namespace Disco.Services.Expressions
@@ -200,19 +201,32 @@ namespace Disco.Services.Expressions
var detailsService = new DetailsProviderService(Database);
if (target != null)
{
var detailsTarget = default(User);
if (target is User targetUser)
{
detailsVariables.Add("UserDetails", new LazyDictionary(() => detailsService.GetDetails(targetUser)));
}
detailsTarget = targetUser;
else if (target is Job targetJob)
{
detailsVariables.Add("UserDetails", targetJob.User == null ? (IDictionary<string, string>)new Dictionary<string, string>() : new LazyDictionary(() => detailsService.GetDetails(targetJob.User)));
}
detailsTarget = targetJob.User;
else if (target is Device targetDevice)
detailsTarget = targetDevice.AssignedUser;
if (detailsTarget != null)
detailsVariables.Add("UserDetails", new LazyDictionary(() =>
{
detailsVariables.Add("UserDetails", targetDevice.AssignedUser == null ? (IDictionary<string, string>)new Dictionary<string, string>() : new LazyDictionary(() => detailsService.GetDetails(targetDevice.AssignedUser)));
var details = detailsService.GetDetails(detailsTarget);
foreach (var key in details.Keys.ToList())
{
if (key.EndsWith("*") || key.EndsWith("&"))
{
var baseKey = key.Substring(0, key.Length - 1);
if (!details.ContainsKey(baseKey))
details[baseKey] = details[key];
}
}
return details;
}));
else
detailsVariables.Add("UserDetails", new Dictionary<string, string>());
}
return new Hashtable(detailsVariables)
{
@@ -0,0 +1,100 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
namespace Disco.Services.Interop.ActiveDirectory
{
public static class ADDeviceOfflineDomainJoining
{
[Flags]
private enum NETSETUP_PROVISION_FLAGS : int
{
NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT = 0x00000001,
NETSETUP_PROVISION_REUSE_ACCOUNT = 0x00000002,
NETSETUP_PROVISION_USE_DEFAULT_PASSWORD = 0x00000004,
NETSETUP_PROVISION_SKIP_ACCOUNT_SEARCH = 0x00000008,
NETSETUP_PROVISION_ROOT_CA_CERTS = 0x00000010,
NETSETUP_PROVISION_PERSISTENTSITE = 0x00000020,
NETSETUP_PROVISION_ONLINE_CALLER = 0x40000000,
NETSETUP_PROVISION_CHECK_PWD_ONLY = unchecked((int)0x80000000),
}
[DllImport("Netapi32.dll", CallingConvention = CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.I4)]
private static extern int NetProvisionComputerAccount(
[In, MarshalAs(UnmanagedType.LPWStr)] string lpDomain,
[In, MarshalAs(UnmanagedType.LPWStr)] string lpMachineName,
[In, Optional, MarshalAs(UnmanagedType.LPWStr)] string lpMachineAccountOU,
[In, Optional, MarshalAs(UnmanagedType.LPWStr)] string lpDcName,
[In, MarshalAs(UnmanagedType.I4)] NETSETUP_PROVISION_FLAGS dwOptions,
out IntPtr pProvisionBinData,
out int pdwProvisionBinDataSize,
[Optional] IntPtr pProvisionTextData
);
[DllImport("Netapi32.dll", SetLastError = true)]
private static extern int NetApiBufferFree(IntPtr Buffer);
public static string OfflineDomainJoinProvision(this ADDomainController dc, string computerSamAccountName, string organisationalUnit, ref ADMachineAccount machineAccount)
{
if (machineAccount != null && machineAccount.IsCriticalSystemObject)
throw new InvalidOperationException($"This account {machineAccount.DistinguishedName} is a Critical System Active Directory Object and Disco ICT refuses to modify it");
if (!dc.IsWritable)
throw new InvalidOperationException($"The domain controller [{dc.Name}] is not writable. This action (Offline Domain Join Provision) requires a writable domain controller.");
if (!string.IsNullOrWhiteSpace(computerSamAccountName))
computerSamAccountName = computerSamAccountName.TrimEnd('$');
if (!string.IsNullOrWhiteSpace(computerSamAccountName) && computerSamAccountName.Contains('\\'))
computerSamAccountName = computerSamAccountName.Substring(computerSamAccountName.IndexOf('\\') + 1);
// NetBIOS Limit (16 characters; "{ComputerName}$"; 15 characters allowed)
if (string.IsNullOrWhiteSpace(computerSamAccountName) || computerSamAccountName.Length > 15)
throw new ArgumentException("Invalid Computer Name; > 0 and <= 15", "ComputerName");
// Ensure Specified OU Exists
if (!string.IsNullOrEmpty(organisationalUnit))
{
try
{
using (var deOU = dc.RetrieveDirectoryEntry(organisationalUnit, new string[] { "distinguishedName" }))
{
if (deOU == null)
throw new Exception($"OU's Directory Entry couldn't be found at [{organisationalUnit}]");
}
}
catch (Exception ex)
{
throw new ArgumentException($"An error occurred while trying to locate the specified OU: {organisationalUnit}", "OrganisationalUnit", ex);
}
}
if (machineAccount != null && !string.Equals(machineAccount.Name, computerSamAccountName, StringComparison.Ordinal))
{
// rename the account
machineAccount.RenameAccount(dc, computerSamAccountName);
}
var result = NetProvisionComputerAccount(dc.Domain.Name, computerSamAccountName, string.IsNullOrWhiteSpace(organisationalUnit) ? null : organisationalUnit, dc.Name, NETSETUP_PROVISION_FLAGS.NETSETUP_PROVISION_REUSE_ACCOUNT, out var provisionDataPointer, out var provisionDataLength);
if (result != 0)
{
var win32Exception = new System.ComponentModel.Win32Exception(result);
throw new InvalidOperationException($"NetProvisionComputerAccount failed with error code {result}: {win32Exception.Message}");
}
if (provisionDataPointer == IntPtr.Zero || provisionDataLength == 0)
throw new InvalidOperationException("NetProvisionComputerAccount did not return valid provisioning data.");
var buffer = new byte[provisionDataLength];
Marshal.Copy(provisionDataPointer, buffer, 0, provisionDataLength);
var freeResult = NetApiBufferFree(provisionDataPointer);
var encodedResult = Convert.ToBase64String(buffer);
// Reload Machine Account
machineAccount = dc.RetrieveADMachineAccount($@"{dc.Domain.NetBiosName}\{computerSamAccountName}", (machineAccount == null ? null : machineAccount.LoadedProperties.Keys.ToArray()));
return encodedResult;
}
}
}
@@ -1,19 +1,17 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
using System.Net.NetworkInformation;
using System.Security.Principal;
using System.Text;
namespace Disco.Services.Interop.ActiveDirectory
{
public class ADDomainController
{
private const string LdapPathTemplate = @"LDAP://{0}/{1}";
private ActiveDirectoryContext context;
private readonly ActiveDirectoryContext context;
public DomainController DomainController { get; private set; }
public ADDomain Domain { get; private set; }
@@ -100,25 +98,25 @@ namespace Disco.Services.Interop.ActiveDirectory
return results;
}
internal IEnumerable<ADSearchResult> SearchInternal(string SearchRoot, string LdapFilter, string[] LoadProperties, int? ResultLimit)
internal IEnumerable<ADSearchResult> SearchInternal(string searchRoot, string ldapFilter, string[] loadProperties, int? resultLimit, bool searchSubtree = true)
{
if (string.IsNullOrEmpty(SearchRoot))
if (string.IsNullOrEmpty(searchRoot))
throw new ArgumentNullException("SearchRoot");
if (string.IsNullOrEmpty(LdapFilter))
if (string.IsNullOrEmpty(ldapFilter))
throw new ArgumentNullException("LdapFilter");
if (ResultLimit.HasValue && ResultLimit.Value < 1)
if (resultLimit.HasValue && resultLimit.Value < 1)
throw new ArgumentOutOfRangeException("ResultLimit", "The ResultLimit must be 1 or greater");
using (ADDirectoryEntry rootEntry = RetrieveDirectoryEntry(SearchRoot))
using (ADDirectoryEntry rootEntry = RetrieveDirectoryEntry(searchRoot))
{
using (DirectorySearcher searcher = new DirectorySearcher(rootEntry.Entry, LdapFilter, LoadProperties, System.DirectoryServices.SearchScope.Subtree))
using (DirectorySearcher searcher = new DirectorySearcher(rootEntry.Entry, ldapFilter, loadProperties, searchSubtree ? System.DirectoryServices.SearchScope.Subtree : System.DirectoryServices.SearchScope.OneLevel))
{
searcher.PageSize = 500;
if (ResultLimit.HasValue)
searcher.SizeLimit = ResultLimit.Value;
if (resultLimit.HasValue)
searcher.SizeLimit = resultLimit.Value;
return searcher.FindAll().Cast<SearchResult>().Select(result => new ADSearchResult(Domain, this, SearchRoot, LdapFilter, result));
return searcher.FindAll().Cast<SearchResult>().Select(result => new ADSearchResult(Domain, this, searchRoot, ldapFilter, result));
}
}
}
@@ -225,7 +223,7 @@ namespace Disco.Services.Interop.ActiveDirectory
if (SecurityIdentifier == null)
throw new ArgumentNullException("SecurityIdentifier");
if (!SecurityIdentifier.IsEqualDomainSid(Domain.SecurityIdentifier))
throw new ArgumentException($"The specified Security Identifier [{SecurityIdentifier.ToString()}] does not belong to this domain [{Domain.Name}]", "SecurityIdentifier");
throw new ArgumentException($"The specified Security Identifier [{SecurityIdentifier}] does not belong to this domain [{Domain.Name}]", "SecurityIdentifier");
var sidBinaryString = SecurityIdentifier.ToBinaryString();
@@ -295,6 +293,7 @@ namespace Disco.Services.Interop.ActiveDirectory
private const string OrganisationalUnitsLdapFilter = "(objectCategory=organizationalUnit)";
private static readonly string[] OrganisationalUnitsLoadProperties = { "name", "distinguishedName" };
[Obsolete("Retrieve as needed using RetrieveADOrganisationUnits(parentDistinguishedName)")]
public List<ADOrganisationalUnit> RetrieveADOrganisationalUnitStructure()
{
Dictionary<string, List<ADOrganisationalUnit>> resultTree = new Dictionary<string, List<ADOrganisationalUnit>>();
@@ -319,6 +318,15 @@ namespace Disco.Services.Interop.ActiveDirectory
return indexedChildren[Domain.DistinguishedName];
}
public List<ADOrganisationalUnit> RetrieveADOrganisationUnits(string parentDistinguishedName = null)
{
if (parentDistinguishedName is null)
parentDistinguishedName = Domain.DistinguishedName;
return SearchInternal(parentDistinguishedName, OrganisationalUnitsLdapFilter, OrganisationalUnitsLoadProperties, null, false)
.Select(r => r.AsADOrganisationalUnit()).OrderBy(ou => ou.Name).ToList();
}
#endregion
private ADSearchResult RetrieveBySamAccountName(string Id, string LdapFilterTemplate, string[] LoadProperties)
@@ -343,93 +351,6 @@ namespace Disco.Services.Interop.ActiveDirectory
return (pr.Status == IPStatus.Success);
}
}
public string OfflineDomainJoinProvision(string ComputerSamAccountName, string OrganisationalUnit, ref ADMachineAccount MachineAccount, out string DiagnosticInformation)
{
if (MachineAccount != null && MachineAccount.IsCriticalSystemObject)
throw new InvalidOperationException($"This account {MachineAccount.DistinguishedName} is a Critical System Active Directory Object and Disco ICT refuses to modify it");
if (!IsWritable)
throw new InvalidOperationException($"The domain controller [{Name}] is not writable. This action (Offline Domain Join Provision) requires a writable domain controller.");
StringBuilder diagnosticInfo = new StringBuilder();
string DJoinResult = null;
if (!string.IsNullOrWhiteSpace(ComputerSamAccountName))
ComputerSamAccountName = ComputerSamAccountName.TrimEnd('$');
if (!string.IsNullOrWhiteSpace(ComputerSamAccountName) && ComputerSamAccountName.Contains('\\'))
ComputerSamAccountName = ComputerSamAccountName.Substring(ComputerSamAccountName.IndexOf('\\') + 1);
// NetBIOS Limit (16 characters; "{ComputerName}$"; 15 characters allowed)
if (string.IsNullOrWhiteSpace(ComputerSamAccountName) || ComputerSamAccountName.Length > 15)
throw new ArgumentException("Invalid Computer Name; > 0 and <= 15", "ComputerName");
// Ensure Specified OU Exists
if (!string.IsNullOrEmpty(OrganisationalUnit))
{
try
{
using (var deOU = RetrieveDirectoryEntry(OrganisationalUnit, new string[] { "distinguishedName" }))
{
if (deOU == null)
throw new Exception($"OU's Directory Entry couldn't be found at [{OrganisationalUnit}]");
}
}
catch (Exception ex)
{
throw new ArgumentException($"An error occurred while trying to locate the specified OU: {OrganisationalUnit}", "OrganisationalUnit", ex);
}
}
if (MachineAccount != null)
MachineAccount.DeleteAccount(this);
string tempFileName = System.IO.Path.GetTempFileName();
string argumentOU = (!string.IsNullOrWhiteSpace(OrganisationalUnit)) ? $" /MACHINEOU \"{OrganisationalUnit}\"" : string.Empty;
string arguments = $"/PROVISION /DOMAIN \"{Domain.Name}\" /DCNAME \"{Name}\" /MACHINE \"{ComputerSamAccountName}\"{argumentOU} /REUSE /SAVEFILE \"{tempFileName}\"";
ProcessStartInfo commandStarter = new ProcessStartInfo("DJOIN.EXE", arguments)
{
CreateNoWindow = true,
ErrorDialog = false,
LoadUserProfile = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
diagnosticInfo.AppendFormat($"DJOIN.EXE {arguments}");
diagnosticInfo.AppendLine();
string stdOutput;
string stdError;
using (Process commandProc = Process.Start(commandStarter))
{
commandProc.WaitForExit(20000);
stdOutput = commandProc.StandardOutput.ReadToEnd();
stdError = commandProc.StandardError.ReadToEnd();
}
if (!string.IsNullOrWhiteSpace(stdOutput))
diagnosticInfo.AppendLine(stdOutput);
if (!string.IsNullOrWhiteSpace(stdError))
diagnosticInfo.AppendLine(stdError);
if (System.IO.File.Exists(tempFileName))
{
DJoinResult = Convert.ToBase64String(System.IO.File.ReadAllBytes(tempFileName));
System.IO.File.Delete(tempFileName);
}
if (string.IsNullOrWhiteSpace(DJoinResult))
throw new InvalidOperationException(
$@"Domain Join Unsuccessful
Error: {stdError}
Output: {stdOutput}");
DiagnosticInformation = diagnosticInfo.ToString();
// Reload Machine Account
MachineAccount = RetrieveADMachineAccount($@"{Domain.NetBiosName}\{ComputerSamAccountName}", (MachineAccount == null ? null : MachineAccount.LoadedProperties.Keys.ToArray()));
return DJoinResult;
}
#endregion
public override string ToString()
@@ -204,6 +204,29 @@ namespace Disco.Services.Interop.ActiveDirectory
#region Actions
public void RenameAccount(ADDomainController writableDomainController, string newName)
{
if (IsCriticalSystemObject)
throw new InvalidOperationException($"This account [{DistinguishedName}] is a Critical System Active Directory Object and Disco ICT refuses to modify it");
if (!writableDomainController.IsWritable)
throw new InvalidOperationException($"The domain controller [{Name}] is not writable. This action (Delete Account) requires a writable domain controller.");
using (ADDirectoryEntry adEntry = writableDomainController.RetrieveDirectoryEntry(DistinguishedName))
{
var entry = adEntry.Entry;
entry.Properties["dNSHostName"][0] = $"{newName}.{Domain.Name}";
entry.Properties["sAMAccountName"][0] = $"{newName}$";
entry.CommitChanges();
entry.Rename($"CN={newName}");
entry.CommitChanges();
// Update Distinguished Name
Name = newName;
DistinguishedName = entry.Properties["distinguishedName"][0].ToString();
}
}
public void DeleteAccount(ADDomainController WritableDomainController)
{
if (IsCriticalSystemObject)
@@ -80,21 +80,23 @@ namespace Disco.Services.Interop.ActiveDirectory
return JsonConvert.DeserializeObject<ADManagedGroupConfiguration>(ConfigurationJson);
}
public static string ValidConfigurationToJson(string GroupKey, string GroupId, DateTime? FilterBeginDate)
=> ValidConfigurationToJson(GroupKey, GroupId, FilterBeginDate, true);
public static string ValidConfigurationToJson(string groupKey, string groupId, DateTime? filterBeginDate, bool updateDescription)
{
if (string.IsNullOrWhiteSpace(GroupId))
GroupId = null;
if (string.IsNullOrWhiteSpace(groupId))
groupId = null;
if (GroupId != null)
GroupId = ActiveDirectory.Context.ManagedGroups.ValidateGroupId(GroupId, GroupKey);
if (GroupId == null)
if (groupId != null)
groupId = ActiveDirectory.Context.ManagedGroups.ValidateGroupId(groupId, groupKey);
if (groupId == null)
return null;
else
return JsonConvert.SerializeObject(new ADManagedGroupConfiguration()
{
GroupId = GroupId,
FilterBeginDate = FilterBeginDate
}, new JsonSerializerSettings() { DefaultValueHandling = DefaultValueHandling.Ignore });
GroupId = groupId,
FilterBeginDate = filterBeginDate,
UpdateDescription = updateDescription,
}, new JsonSerializerSettings());
}
public abstract void Dispose();
@@ -16,7 +16,7 @@ namespace Disco.Services.Interop.ActiveDirectory
private static ActiveDirectoryContext context;
private static ActiveDirectoryGroupCache groupCache;
private static object contextInitializingLock = new object();
private static readonly object contextInitializingLock = new object();
public static void Initialize(DiscoDataContext Database)
{
@@ -157,6 +157,7 @@ namespace Disco.Services.Interop.ActiveDirectory
#endregion
#region Organisational Units
[Obsolete("Retrieve as needed using RetrieveADOrganisationUnits(parentDistinguishedName)")]
public static IEnumerable<Tuple<ADDomain, List<ADOrganisationalUnit>>> RetrieveADOrganisationalUnitStructure()
{
return Context.Domains
@@ -178,18 +179,6 @@ namespace Disco.Services.Interop.ActiveDirectory
}
#endregion
#region Actions
public static string OfflineDomainJoinProvision(string ComputerSamAccountName, string OrganisationalUnit, ref ADMachineAccount MachineAccount, out string DiagnosticInformation)
{
var domain = Context.GetDomainFromDistinguishedName(OrganisationalUnit);
var writableDomainController = domain.GetAvailableDomainController(RequireWritable: true);
return writableDomainController.OfflineDomainJoinProvision(ComputerSamAccountName, OrganisationalUnit, ref MachineAccount, out DiagnosticInformation);
}
#endregion
#region Helpers
public static string ParseDomainAccountId(string AccountId)
@@ -13,9 +13,9 @@ namespace Disco.Services.Interop.ActiveDirectory
public class ActiveDirectoryManagedGroups : IDisposable
{
private ConcurrentDictionary<string, ADManagedGroup> managedGroups;
private Subject<ADManagedGroupScheduledAction> actionBuffer;
private IDisposable actionBufferSubscription;
private readonly ConcurrentDictionary<string, ADManagedGroup> managedGroups;
private readonly Subject<ADManagedGroupScheduledAction> actionBuffer;
private readonly IDisposable actionBufferSubscription;
internal ActiveDirectoryManagedGroups()
{
@@ -379,6 +379,8 @@ namespace Disco.Services.Interop.ActiveDirectory
throw new InvalidOperationException($"This group [{adGroup.DistinguishedName}] is a Critical System Active Directory Object and Disco ICT refuses to modify it");
// Update Description
if (actionGroup.Item1.Configuration.UpdateDescription)
{
var groupDescription = $"Disco ICT: {actionGroup.Item1.GroupDescription}";
if (adGroupEntry.Entry.Properties.Value<string>("description") != groupDescription)
{
@@ -387,6 +389,7 @@ namespace Disco.Services.Interop.ActiveDirectory
adGroupEntryDescription.Clear();
adGroupEntryDescription.Add(groupDescription);
}
}
// Sync Members
var adGroupEntryMembers = adGroupEntry.Entry.Properties["member"];
@@ -430,7 +433,7 @@ namespace Disco.Services.Interop.ActiveDirectory
internal class ADManagedGroupScheduledAction
{
private Func<DiscoDataContext, IEnumerable<string>> memberResolver;
private readonly Func<DiscoDataContext, IEnumerable<string>> memberResolver;
public ADManagedGroup ManagedGroup { get; private set; }
public ADManagedGroupScheduledActionType ActionType { get; private set; }
+24
View File
@@ -0,0 +1,24 @@
using System;
using System.Net;
namespace Disco.Services.Interop.DNS
{
public class ADnsRecord : DnsRecord
{
public IPAddress Address { get; }
public ADnsRecord(string name, TimeSpan timeToLive, uint address)
: base(name, DnsRecordType.A, timeToLive, UIntToIPAddress(address).ToString())
{
Address = UIntToIPAddress(address);
}
private static IPAddress UIntToIPAddress(uint address)
{
byte[] bytes = BitConverter.GetBytes(address);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
return new IPAddress(bytes);
}
}
}
@@ -0,0 +1,12 @@
using System;
namespace Disco.Services.Interop.DNS
{
public class CnameDnsRecord : DnsRecord
{
public CnameDnsRecord(string name, TimeSpan timeToLive, string canonicalName)
: base(name, DnsRecordType.Cname, timeToLive, canonicalName)
{
}
}
}
+20
View File
@@ -0,0 +1,20 @@
using System;
namespace Disco.Services.Interop.DNS
{
public abstract class DnsRecord
{
public string Name { get; }
public DnsRecordType Type { get; }
public TimeSpan TimeToLive { get; }
public string Content { get; }
protected DnsRecord(string name, DnsRecordType type, TimeSpan timeToLive, string content)
{
Name = name;
Type = type;
TimeToLive = timeToLive;
Content = content;
}
}
}
@@ -0,0 +1,10 @@
namespace Disco.Services.Interop.DNS
{
public enum DnsRecordType
{
A = 0x01,
Cname = 0x05,
Txt = 0x10,
Srv = 0x21
}
}
+30
View File
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
namespace Disco.Services.Interop.DNS
{
public class DnsService
{
public DnsService()
{
}
public static List<T> Query<T>(string name, bool bypassCache = false) where T : DnsRecord
{
DnsRecordType recordType;
if (typeof(T) == typeof(ADnsRecord))
recordType = DnsRecordType.A;
else if (typeof(T) == typeof(CnameDnsRecord))
recordType = DnsRecordType.Cname;
else if (typeof(T) == typeof(TxtDnsRecord))
recordType = DnsRecordType.Txt;
else if (typeof(T) == typeof(SrvDnsRecord))
recordType = DnsRecordType.Srv;
else
throw new NotSupportedException($"Unsupported DNS record type: {typeof(T).Name}");
var records = NativeDns.QueryRecords(recordType, name, bypassCache);
return records.ConvertAll(r => (T)r);
}
}
}
+202
View File
@@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
namespace Disco.Services.Interop.DNS
{
internal static class NativeDns
{
[DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)] ref string pszName, NativeDnsQueryTypes wType, NativeDnsQueryOptions options, int aipServers, ref IntPtr ppQueryResults, int pReserved);
[DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)]
private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);
private const int DNS_ERROR_RCODE_NAME_ERROR = 0x232B;
private const int DNS_ERROR_BAD_PACKET = 0x251E;
public static List<DnsRecord> QueryRecords(DnsRecordType type, string name, bool bypassCache)
{
NativeDnsQueryTypes queryType;
Func<IntPtr, Tuple<DnsRecord, IntPtr>> marshaller;
switch (type)
{
case DnsRecordType.A:
queryType = NativeDnsQueryTypes.DNS_TYPE_A;
marshaller = MarshalARecord;
break;
case DnsRecordType.Cname:
queryType = NativeDnsQueryTypes.DNS_TYPE_CNAME;
marshaller = MarshalCnameRecord;
break;
case DnsRecordType.Txt:
queryType = NativeDnsQueryTypes.DNS_TYPE_TEXT;
marshaller = MarshalTxtRecord;
break;
case DnsRecordType.Srv:
queryType = NativeDnsQueryTypes.DNS_TYPE_SRV;
marshaller = MarshalSrvRecord;
break;
default:
throw new NotSupportedException($"Unsupported DNS record type: {type}");
}
IntPtr rrPointers = IntPtr.Zero;
var records = new List<DnsRecord>();
var retry = 5;
retry:
try
{
int queryResult = DnsQuery(ref name, queryType, bypassCache ? NativeDnsQueryOptions.DNS_QUERY_BYPASS_CACHE : NativeDnsQueryOptions.DNS_QUERY_STANDARD, 0, ref rrPointers, 0);
if (queryResult != 0)
{
if (queryResult == DNS_ERROR_RCODE_NAME_ERROR)
return records;
else if (queryResult == DNS_ERROR_BAD_PACKET && retry > 0)
{
// Sometimes a BAD_PACKET error is returned, retry a few times
Thread.Sleep(100);
retry--;
goto retry;
}
else
throw new Win32Exception(queryResult);
}
for (var rrPointer = rrPointers; !rrPointer.Equals(IntPtr.Zero);)
{
var (record, rrPointerNext) = marshaller(rrPointer);
records.Add(record);
rrPointer = rrPointerNext;
}
}
finally
{
if (rrPointers != IntPtr.Zero)
DnsRecordListFree(rrPointers, 0);
}
return records;
}
private static Tuple<DnsRecord, IntPtr> MarshalARecord(IntPtr pointer)
{
var native = Marshal.PtrToStructure<NativeDnsAData>(pointer);
var record = new ADnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.IpAddress);
return Tuple.Create((DnsRecord)record, native.pNext);
}
private static Tuple<DnsRecord, IntPtr> MarshalCnameRecord(IntPtr pointer)
{
var native = Marshal.PtrToStructure<NativeDnsPtrData>(pointer);
var record = new CnameDnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.pNameHost);
return Tuple.Create((DnsRecord)record, native.pNext);
}
private static Tuple<DnsRecord, IntPtr> MarshalTxtRecord(IntPtr pointer)
{
var native = Marshal.PtrToStructure<NativeDnsTxtData>(pointer);
var record = new TxtDnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.pStringArray);
return Tuple.Create((DnsRecord)record, native.pNext);
}
private static Tuple<DnsRecord, IntPtr> MarshalSrvRecord(IntPtr pointer)
{
var native = Marshal.PtrToStructure<NativeDnsSrvData>(pointer);
var record = new SrvDnsRecord(native.pName, TimeSpan.FromSeconds(native.dwTtl), native.pNameTarget, native.wPriority, native.wWeight, native.wPort);
return Tuple.Create((DnsRecord)record, native.pNext);
}
private enum NativeDnsQueryOptions
{
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
DNS_QUERY_BYPASS_CACHE = 8,
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
DNS_QUERY_NO_HOSTS_FILE = 0x40,
DNS_QUERY_NO_LOCAL_NAME = 0x20,
DNS_QUERY_NO_NETBT = 0x80,
DNS_QUERY_NO_RECURSION = 4,
DNS_QUERY_NO_WIRE_QUERY = 0x10,
DNS_QUERY_RESERVED = -16777216,
DNS_QUERY_RETURN_MESSAGE = 0x200,
DNS_QUERY_STANDARD = 0,
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
DNS_QUERY_USE_TCP_ONLY = 2,
DNS_QUERY_WIRE_ONLY = 0x100
}
private enum NativeDnsQueryTypes
{
DNS_TYPE_A = 0x0001,
DNS_TYPE_CNAME = 0x0005,
DNS_TYPE_TEXT = 0x0010,
DNS_TYPE_SRV = 0x0021
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsSrvData
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string pNameTarget;
public ushort wPriority;
public ushort wWeight;
public ushort wPort;
public ushort Pad;
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsTxtData
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
public uint dwStringLength;
[MarshalAs(UnmanagedType.LPWStr)]
public string pStringArray;
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsPtrData
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
[MarshalAs(UnmanagedType.LPWStr)]
public string pNameHost;
}
[StructLayout(LayoutKind.Sequential)]
private struct NativeDnsAData
{
public IntPtr pNext;
[MarshalAs(UnmanagedType.LPWStr)]
public string pName;
public ushort wType;
public ushort wDataLength;
public int flags;
public int dwTtl;
public int dwReserved;
public uint IpAddress;
}
}
}
@@ -0,0 +1,21 @@
using System;
namespace Disco.Services.Interop.DNS
{
public class SrvDnsRecord : DnsRecord
{
public string Target { get; }
public ushort Priority { get; }
public ushort Weight { get; }
public ushort Port { get; }
public SrvDnsRecord(string name, TimeSpan timeToLive, string target, ushort priority, ushort weight, ushort port)
: base(name, DnsRecordType.Srv, timeToLive, $"{priority} {weight} {port} {target}")
{
Target = target;
Priority = priority;
Weight = weight;
Port = port;
}
}
}
@@ -0,0 +1,12 @@
using System;
namespace Disco.Services.Interop.DNS
{
public class TxtDnsRecord : DnsRecord
{
public TxtDnsRecord(string name, TimeSpan timeToLive, string text)
: base(name, DnsRecordType.Txt, timeToLive, text)
{
}
}
}
@@ -18,6 +18,7 @@ namespace Disco.Services.Interop.DiscoServices
{
public class ActivationService
{
private static readonly HttpClient httpClient;
private static readonly byte[] onlineServicesActivationKey;
private readonly DiscoDataContext database;
@@ -29,6 +30,10 @@ namespace Disco.Services.Interop.DiscoServices
resourceStream.Read(key, 0, key.Length);
onlineServicesActivationKey = key;
}
httpClient = new HttpClient(new OnlineServicesAuthenticatedHandler())
{
BaseAddress = DiscoServiceHelpers.ActivationServiceUrl
};
}
public ActivationService(DiscoDataContext database)
@@ -42,6 +47,24 @@ namespace Disco.Services.Interop.DiscoServices
public Uri GetCallbackUrl()
=> new Uri(DiscoServiceHelpers.ActivationServiceUrl, "/api/callback");
public string CalculateCallbackProof(Guid correlationId, string userId, long timestamp)
{
var deploymentId = Guid.Parse(database.DiscoConfiguration.DeploymentId);
var secret = Guid.Parse(database.DiscoConfiguration.DeploymentSecret);
using (var hmac = new HMACSHA256(secret.ToByteArray()))
{
var data = new MemoryStream();
data.Write(deploymentId.ToByteArray(), 0, 16);
data.Write(correlationId.ToByteArray(), 0, 16);
var userIdBytes = Encoding.UTF8.GetBytes(userId);
data.Write(BitConverter.GetBytes(userIdBytes.Length), 0, 4);
data.Write(userIdBytes, 0, userIdBytes.Length);
data.Write(BitConverter.GetBytes(timestamp), 0, 8);
var hash = hmac.ComputeHash(data.ToArray());
return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
}
/// <summary>
/// Begin the activation process
/// </summary>
@@ -229,6 +252,38 @@ namespace Disco.Services.Interop.DiscoServices
}
}
public static async Task<T> Post<T>(string url, object request)
{
var stream = new MemoryStream();
using (var jsonWriter = new StreamWriter(stream, new UTF8Encoding(false), 1024, true))
{
var serializer = new JsonSerializer();
serializer.Serialize(jsonWriter, request);
}
stream.Position = 0;
using (var content = new StreamContent(stream))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
using (var response = await httpClient.PostAsync(url, content))
{
response.EnsureSuccessStatusCode();
using (var responseContent = await response.Content.ReadAsStreamAsync())
{
using (var reader = new StreamReader(responseContent, Encoding.UTF8))
{
using (var jsonReader = new JsonTextReader(reader))
{
var serializer = new JsonSerializer();
return serializer.Deserialize<T>(jsonReader);
}
}
}
}
}
}
private static bool ValidateSignature(ChallengeResponse response)
{
var stream = new MemoryStream();
@@ -237,7 +292,7 @@ namespace Disco.Services.Interop.DiscoServices
stream.Write(response.Challenge, 0, response.Challenge.Length);
stream.Write(response.ChallengeIv, 0, response.ChallengeIv.Length);
return ValidateSignature(stream.ToArray(), response.Signature);
return ValidateOnlineServicesSignature(stream.ToArray(), response.Signature);
}
private static bool ValidateSignature(Guid activationId, byte[] challenge, byte[] challengeIv, byte[] signature)
@@ -246,10 +301,10 @@ namespace Disco.Services.Interop.DiscoServices
stream.Write(activationId.ToByteArray(), 0, 16);
stream.Write(challenge, 0, challenge.Length);
stream.Write(challengeIv, 0, challengeIv.Length);
return ValidateSignature(stream.ToArray(), signature);
return ValidateOnlineServicesSignature(stream.ToArray(), signature);
}
private static bool ValidateSignature(byte[] bytes, byte[] signature)
public static bool ValidateOnlineServicesSignature(byte[] bytes, byte[] signature)
{
byte[] hash;
using (var hasher = SHA256.Create())
@@ -295,6 +350,11 @@ namespace Disco.Services.Interop.DiscoServices
}
}
public static byte[] SignSHA256Hash(byte[] hash)
{
return SignHash(OnlineServicesAuthentication.Key, hash);
}
private static byte[] Encrypt(byte[] privateKey, byte[] iv, long timeStamp, byte[] data)
{
var key = DeriveEncryptionKey(privateKey, iv, timeStamp);
@@ -318,6 +378,17 @@ namespace Disco.Services.Interop.DiscoServices
}
}
public static byte[] Encrypt(byte[] data, out byte[] iv, out long timeStamp)
{
iv = new byte[16];
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(iv);
timeStamp = DateTime.UtcNow.Ticks;
return Encrypt(OnlineServicesAuthentication.Key, iv, timeStamp, data);
}
private static byte[] Decrypt(byte[] privateKey, byte[] iv, long timeStamp, byte[] data)
{
var key = DeriveEncryptionKey(privateKey, iv, timeStamp);
@@ -342,6 +413,63 @@ namespace Disco.Services.Interop.DiscoServices
}
}
public static byte[] Decrypt(byte[] data, byte[] iv, long timeStamp)
{
return Decrypt(OnlineServicesAuthentication.Key, iv, timeStamp, data);
}
public static byte[] OneWayDecrypt(byte[] data)
{
var span = data.AsSpan();
if (span.Length < 13)
throw new ArgumentException("Data is too short", nameof(data));
Span<byte> magicBytes = new byte[] { 0xF0, 0x9F, 0x94, 0x8F, 0x44, 0x69, 0x73, 0x63, 0x6F, 0x49, 0x43, 0x54 }.AsSpan();
if (!MemoryExtensions.SequenceEqual(magicBytes, span.Slice(0, 12)))
throw new InvalidOperationException("Invalid format signature");
span = span.Slice(12);
byte[] readChunk(ref Span<byte> source)
{
var length = source[0];
if (source.Length < (length + 1))
throw new ArgumentException("Data is too short", nameof(data));
var buffer = new byte[length];
source.Slice(1, length).CopyTo(buffer);
source = source.Slice(length + 1);
return buffer;
}
var publicKey = readChunk(ref span);
var iv = readChunk(ref span);
var signature = readChunk(ref span);
var key = DeriveOneWayDecryptingKey(OnlineServicesAuthentication.Key, publicKey, iv);
var outputStream = new MemoryStream();
using (var aes = Aes.Create())
{
aes.Key = key;
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var decryptor = aes.CreateDecryptor())
{
var ms = new MemoryStream(span.ToArray());
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
cs.CopyTo(outputStream);
}
}
}
var output = outputStream.ToArray();
if (!ValidateOnlineServicesSignature(output, signature))
throw new InvalidOperationException("Invalid encrypted data signature");
return output;
}
private static byte[] DeriveEncryptionKey(byte[] privateKey, byte[] iv, long timeStamp)
{
using (var serverKey = CngKey.Import(onlineServicesActivationKey, CngKeyBlobFormat.EccPublicBlob))
@@ -359,6 +487,23 @@ namespace Disco.Services.Interop.DiscoServices
}
}
private static byte[] DeriveOneWayDecryptingKey(byte[] privateKey, byte[] publicKey, byte[] iv)
{
using (var ephemeralKey = CngKey.Import(publicKey, CngKeyBlobFormat.EccPublicBlob))
{
using (var clientKey = CngKey.Import(privateKey, CngKeyBlobFormat.EccPrivateBlob))
{
using (var ephemeralEcdh = new ECDiffieHellmanCng(ephemeralKey))
{
using (var ecdh = new ECDiffieHellmanCng(clientKey))
{
return ecdh.DeriveKeyFromHash(ephemeralEcdh.PublicKey, HashAlgorithmName.SHA256, iv, null);
}
}
}
}
}
private static (byte[] privateKey, byte[] publicKey) GenerateActivationKey()
{
using (var key = CngKey.Create(CngAlgorithm.ECDiffieHellmanP521, null, new CngKeyCreationParameters
@@ -0,0 +1,11 @@
using System;
namespace Disco.Services.Interop.DiscoServices
{
[Flags]
public enum AuthenticationSessionScope
{
Ping = 1,
Host = 2,
}
}
@@ -1,7 +1,9 @@
using Disco.Data.Repository;
using Disco.Models.Repository;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -14,7 +16,7 @@ namespace Disco.Services.Interop.DiscoServices
{
public static class OnlineServicesAuthentication
{
private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
private static readonly Guid deploymentId;
private static Guid? activationId;
private static byte[] key;
@@ -31,11 +33,12 @@ namespace Disco.Services.Interop.DiscoServices
}
public static bool IsActivated => activationId.HasValue;
internal static byte[] Key => key.ToArray() ?? throw new InvalidOperationException("Not activated");
public static string GetToken()
=> GetTokenAsync().Result;
public async static Task<string> GetTokenAsync()
public static async Task<string> GetTokenAsync()
{
var localExpires = tokenExpires;
var localToken = token;
@@ -56,11 +59,6 @@ namespace Disco.Services.Interop.DiscoServices
if (!IsActivated)
throw new InvalidOperationException("Not activated");
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = DiscoServiceHelpers.ActivationServiceUrl;
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var timeStamp = DateTime.UtcNow.ToUnixEpoc();
var iv = new byte[32];
using (var rng = RandomNumberGenerator.Create())
@@ -91,6 +89,11 @@ namespace Disco.Services.Interop.DiscoServices
{
request.Headers.ContentType = new MediaTypeHeaderValue("application/json");
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = DiscoServiceHelpers.ActivationServiceUrl;
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await httpClient.PostAsync("/api/authenticate", request);
if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.BadRequest)
@@ -120,6 +123,32 @@ namespace Disco.Services.Interop.DiscoServices
}
}
public static async Task<Uri> CreateSession(AuthenticationSessionScope scope, User user, Uri returnUrl)
{
if (!IsActivated)
throw new InvalidOperationException("Not activated");
var request = new AuthenticationSessionRequest()
{
DeploymentId = deploymentId,
ActivationId = activationId.Value,
TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
Scope = scope,
UserId = user.UserId,
UserName = user.DisplayName ?? string.Empty,
UserEmail = user.EmailAddress,
UserPhone = user.PhoneNumber,
ReturnUrl = returnUrl.ToString(),
};
var response = await ActivationService.Post<AuthenticationSessionResponse>($"/api/authenticate/session", request);
if (response.Success)
return new Uri(response.Endpoint);
else
throw new InvalidOperationException($"Failed to create authentication session: {response.ErrorMessage}");
}
internal static void UpdateActivation(DiscoDataContext database)
{
semaphore.Wait();
@@ -165,5 +194,25 @@ namespace Disco.Services.Interop.DiscoServices
public int? ExpiresInSeconds { get; set; }
public string ErrorMessage { get; set; }
}
private class AuthenticationSessionRequest
{
public Guid DeploymentId { get; set; }
public Guid ActivationId { get; set; }
public long TimeStamp { get; set; }
public AuthenticationSessionScope Scope { get; set; }
public string UserId { get; set; }
public string UserName { get; set; }
public string UserEmail { get; set; }
public string UserPhone { get; set; }
public string ReturnUrl { get; set; }
}
private class AuthenticationSessionResponse
{
public bool Success { get; set; }
public string Endpoint { get; set; }
public string ErrorMessage { get; set; }
}
}
}
@@ -43,7 +43,10 @@ namespace Disco.Services.Interop.DiscoServices
connection.Closed += ex =>
{
if (ex != null)
SystemLog.LogException("Online Services: Connection Closed", ex);
else
SystemLog.LogInformation("Online Services: Connection Closed");
return Task.CompletedTask;
};
connection.Reconnected += connectionId =>
@@ -53,7 +56,11 @@ namespace Disco.Services.Interop.DiscoServices
};
connection.Reconnecting += ex =>
{
if (ex != null)
SystemLog.LogException("Online Services: Connection Reconnecting", ex);
else
SystemLog.LogInformation("Online Services: Connection Reconnecting");
return Task.CompletedTask;
};
}
@@ -1,6 +1,7 @@
using Disco.Data.Repository;
using Disco.Models.Repository;
using Disco.Models.Services.Interop.DiscoServices;
using Disco.Services.Devices.Enrolment;
using Disco.Services.Tasks;
using Newtonsoft.Json;
using System;
@@ -221,6 +222,10 @@ namespace Disco.Services.Interop.DiscoServices
RepairerLogged = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerLoggedDate : j.RepairerLoggedDate,
RepairerCompleted = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerCompletedDate : j.RepairerCompletedDate
}).ToList();
m.Stat_EnrollmentDiscovery = WindowsDeviceEnrolment.GetDiscoveryMethodStatistics()
.Where(s => s.Value != 0)
.Select(s => new StatisticInt() { Key = s.Key.ToString(), Value = s.Value }).ToList();
}
m.InstalledPlugins = Plugins.Plugins.GetPlugins().Select(manifest => new StatisticString() { Key = manifest.Id, Value = manifest.VersionFormatted }).ToList();
@@ -1,13 +1,51 @@
using Disco.Services.Interop.DiscoServices;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Xml.Linq;
namespace Disco.Services.Interop.VicEduDept
{
public class VicSmart
{
public static bool IsVicSmartDeployment()
{
var nics = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up)
.ToList();
bool found10Net = false;
foreach (var nic in nics)
{
if (nic.Supports(NetworkInterfaceComponent.IPv4))
{
var ipProps = nic.GetIPProperties();
var ipv4Props = ipProps.GetIPv4Properties();
if (ipv4Props.IsAutomaticPrivateAddressingActive)
continue;
found10Net = ipProps.UnicastAddresses
.Where(ua =>
ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork &&
ua.Address.GetAddressBytes()[0] == 10)
.Any();
if (found10Net)
break;
}
}
if (!found10Net)
return false;
try
{
var entry = Dns.GetHostEntry("broadband.doe.wan");
return entry.AddressList.Length > 0;
}
catch (Exception)
{ return false; } // Fail on error
}
/// <summary>
/// Queries DoE VicSmart Service to detect the current site.
@@ -16,20 +16,22 @@ namespace Disco.Services.Jobs.Noticeboards
public string DeviceSerialNumber { get; set; }
public string DeviceComputerNameFriendly
{
get
{
return DeviceComputerName == null ? null : ActiveDirectory.FriendlyAccountId(DeviceComputerName);
}
get => DeviceComputerName == null ? null : ActiveDirectory.FriendlyAccountId(DeviceComputerName);
set { } // for XML Serialization
}
public string DeviceComputerName { get; set; }
public string DeviceName
{
get => DeviceComputerNameFriendly ?? DeviceSerialNumber;
set { }
}
public string DeviceLocation { get; set; }
public string DeviceDescription
{
get
{
StringBuilder sb = new StringBuilder(DeviceComputerNameFriendly);
StringBuilder sb = new StringBuilder(DeviceName);
if (UserId != null)
sb.Append(" - ").Append(UserDisplayName).Append(" (").Append(UserIdFriendly).Append(")");
@@ -60,6 +62,7 @@ namespace Disco.Services.Jobs.Noticeboards
}
set { } // for XML Serialization
}
public IEnumerable<int> JobQueueIds { get; set; }
public string UserId { get; set; }
public string UserIdFriendly
@@ -130,6 +133,7 @@ namespace Disco.Services.Jobs.Noticeboards
DeviceLocation = j.Device.Location,
DeviceProfileId = j.Device.DeviceProfileId,
DeviceAddressId = j.Device.DeviceProfile.DefaultOrganisationAddress,
JobQueueIds = j.JobQueues.Where(q => q.RemovedDate == null).Select(q => q.JobQueueId),
UserId = j.Device.AssignedUserId,
UserDisplayName = j.Device.AssignedUser.DisplayName,
WaitingForUserAction = j.WaitingForUserAction.HasValue || ((j.JobMetaNonWarranty.AccountingChargeRequiredDate.HasValue || j.JobMetaNonWarranty.AccountingChargeAddedDate.HasValue) && !j.JobMetaNonWarranty.AccountingChargePaidDate.HasValue),
@@ -15,7 +15,7 @@ namespace Disco.Services.Jobs.Noticeboards
{
public const string Name = "HeldDevices";
private readonly static List<string> MonitorJobProperties = new List<string>() {
private static readonly List<string> MonitorJobProperties = new List<string>() {
"DeviceSerialNumber",
"UserId",
"ExpectedClosedDate",
@@ -25,25 +25,25 @@ namespace Disco.Services.Jobs.Noticeboards
"DeviceReadyForReturn",
"DeviceReturnedDate"
};
private readonly static List<string> MonitorJobMetaNonWarrantyProperties = new List<string>(){
private static readonly List<string> MonitorJobMetaNonWarrantyProperties = new List<string>(){
"AccountingChargeRequiredDate",
"AccountingChargeAddedDate",
"AccountingChargePaidDate"
};
private readonly static List<string> MonitorDeviceProperties = new List<string>(){
private static readonly List<string> MonitorDeviceProperties = new List<string>(){
"Location",
"DeviceProfileId",
"DeviceDomainId",
"AssignedUserId",
};
private readonly static List<string> MonitorDeviceProfileProperties = new List<string>(){
private static readonly List<string> MonitorDeviceProfileProperties = new List<string>(){
"DefaultOrganisationAddress"
};
private readonly static List<string> MonitorUserProperties = new List<string>(){
private static readonly List<string> MonitorUserProperties = new List<string>(){
"DisplayName"
};
private static Subject<Tuple<List<string>, List<string>>> BufferedUpdateStream;
private static readonly Subject<Tuple<List<string>, List<string>>> BufferedUpdateStream;
static HeldDevices()
{
@@ -74,7 +74,9 @@ namespace Disco.Services.Jobs.Noticeboards
) ||
(e.EntityType == typeof(User) &&
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorUserProperties.Contains(p)))
)
) ||
(e.EntityType == typeof(JobQueueJob) &&
(e.EventType == RepositoryMonitorEventType.Added || e.EventType == RepositoryMonitorEventType.Modified))
)
.Subscribe(RepositoryEvent);
}
@@ -147,6 +149,15 @@ namespace Disco.Services.Jobs.Noticeboards
.Select(j => j.DeviceSerialNumber)
);
}
else if (i.EntityType == typeof(JobQueueJob))
{
var jqj = (JobQueueJob)i.Entity;
var j = i.Database.Jobs.Find(jqj.JobId);
if (j != null && j.DeviceSerialNumber != null)
{
deviceSerialNumbers.Add(j.DeviceSerialNumber);
}
}
if (deviceSerialNumbers.Count > 0 || userIds.Count > 0)
{
@@ -53,6 +53,14 @@ namespace Disco.Services.Plugins.Features.DetailsProvider
{
using (var originalImage = Image.FromStream(originalStream))
{
if (originalImage.PropertyIdList.Contains(0x112))
{
var orientation = BitConverter.ToUInt16(originalImage.GetPropertyItem(0x112).Value, 0);
if (orientation > 1 && orientation <= 8)
{
originalImage.RotateFlip(GetRotateFlipTypeByOrientation(orientation));
}
}
using (var resizedImage = originalImage.ResizeImage(192, Brushes.White))
{
using (var savedResizedImage = (MemoryStream)resizedImage.SaveJpg(85))
@@ -99,5 +107,30 @@ namespace Disco.Services.Plugins.Features.DetailsProvider
.ToDictionary(d => d.Key, d => d.Value, StringComparer.OrdinalIgnoreCase);
}
}
private RotateFlipType GetRotateFlipTypeByOrientation(ushort orientation)
{
switch (orientation)
{
case 1:
return RotateFlipType.RotateNoneFlipNone;
case 2:
return RotateFlipType.RotateNoneFlipX;
case 3:
return RotateFlipType.Rotate180FlipNone;
case 4:
return RotateFlipType.RotateNoneFlipY;
case 5:
return RotateFlipType.Rotate270FlipX;
case 6:
return RotateFlipType.Rotate90FlipNone;
case 7:
return RotateFlipType.Rotate90FlipX;
case 8:
return RotateFlipType.Rotate270FlipNone;
default:
return RotateFlipType.RotateNoneFlipNone;
}
}
}
}
+2 -2
View File
@@ -6,7 +6,7 @@
<package id="LumenWorks.Framework.IO" version="3.8.0" targetFramework="net45" />
<package id="Microsoft.AspNet.Mvc" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.SignalR.Core" version="2.1.2" targetFramework="net45" />
<package id="Microsoft.AspNet.SignalR.Core" version="2.4.3" targetFramework="net462" />
<package id="Microsoft.AspNet.WebPages" version="2.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNetCore.Connections.Abstractions" version="9.0.0" targetFramework="net462" />
<package id="Microsoft.AspNetCore.Http.Connections.Client" version="9.0.0" targetFramework="net462" />
@@ -63,6 +63,6 @@
<package id="System.Text.Json" version="9.0.0" targetFramework="net462" />
<package id="System.Threading.Channels" version="9.0.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net462" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net462" />
<package id="System.ValueTuple" version="4.6.2" targetFramework="net462" />
<package id="WebActivatorEx" version="2.0.5" targetFramework="net45" />
</packages>
+4
View File
@@ -56,6 +56,10 @@
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
@@ -12,10 +12,22 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.DiscoAdminAccount)]
public partial class ActivationController : AuthorizedDatabaseController
{
[HttpPost]
public virtual ActionResult TestCallback(CallbackModel model)
[HttpGet]
public virtual async Task<ActionResult> Begin(CallbackModel model)
{
return this.PrecompiledPartialView<Views.Activation._ActivateCallback>(model);
// validate timestamp
var thresholdStart = DateTimeOffset.UtcNow.AddSeconds(-20).ToUnixTimeMilliseconds();
var thresholdEnd = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (model.Timestamp < thresholdStart || model.Timestamp > thresholdEnd)
return new HttpStatusCodeResult(400, "Invalid timestamp");
// validate proof
var service = new ActivationService(Database);
var expectedProof = service.CalculateCallbackProof(model.CorrelationId, model.UserId, model.Timestamp);
if (model.Proof != expectedProof)
return new HttpStatusCodeResult(400, "Invalid proof");
return await Begin();
}
[HttpPost, ValidateAntiForgeryToken]
@@ -33,7 +45,7 @@ namespace Disco.Web.Areas.API.Controllers
RedirectUrl = challengeModel.RedirectUrl
};
return View(model);
return View(MVC.API.Activation.Views.Begin, model);
}
[HttpGet]
@@ -18,22 +18,20 @@ namespace Disco.Web.Areas.API.Controllers
{
public partial class DeviceBatchController : AuthorizedDatabaseController
{
const string pName = "name";
const string pPurchaseDate = "purchasedate";
const string pSupplier = "supplier";
const string pPurchaseDetails = "purchasedetails";
const string pUnitCost = "unitcost";
const string pUnitQuantity = "unitquantity";
const string pDefaultDeviceModelId = "defaultdevicemodelid";
const string pWarrantyValidUntil = "warrantyvaliduntil";
const string pWarrantyDetails = "warrantydetails";
const string pInsuredDate = "insureddate";
const string pInsuranceSupplier = "insurancesupplier";
const string pInsuredUntil = "insureduntil";
const string pInsuranceDetails = "insurancedetails";
const string pComments = "comments";
const string pDevicesLinkedGroup = "deviceslinkedgroup";
const string pAssignedUsersLinkedGroup = "assigneduserslinkedgroup";
private const string pName = "name";
private const string pPurchaseDate = "purchasedate";
private const string pSupplier = "supplier";
private const string pPurchaseDetails = "purchasedetails";
private const string pUnitCost = "unitcost";
private const string pUnitQuantity = "unitquantity";
private const string pDefaultDeviceModelId = "defaultdevicemodelid";
private const string pWarrantyValidUntil = "warrantyvaliduntil";
private const string pWarrantyDetails = "warrantydetails";
private const string pInsuredDate = "insureddate";
private const string pInsuranceSupplier = "insurancesupplier";
private const string pInsuredUntil = "insureduntil";
private const string pInsuranceDetails = "insurancedetails";
private const string pComments = "comments";
[DiscoAuthorize(Claims.Config.DeviceBatch.Configure)]
[HttpPost, ValidateAntiForgeryToken]
@@ -94,12 +92,6 @@ namespace Disco.Web.Areas.API.Controllers
case pComments:
UpdateComments(deviceBatch, value);
break;
case pDevicesLinkedGroup:
UpdateDevicesLinkedGroup(deviceBatch, value);
break;
case pAssignedUsersLinkedGroup:
UpdateAssignedUsersLinkedGroup(deviceBatch, value);
break;
default:
throw new Exception("Invalid Update Key");
}
@@ -224,18 +216,17 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.Config.DeviceBatch.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult UpdateDevicesLinkedGroup(int id, string GroupId = null, bool redirect = false)
public virtual ActionResult UpdateDevicesLinkedGroup(int id, string groupId = null, bool? updateDescription = null, bool redirect = false)
{
try
{
if (id < 0)
throw new ArgumentOutOfRangeException("id");
throw new ArgumentOutOfRangeException(nameof(id));
var deviceBatch = Database.DeviceBatches.Find(id);
if (deviceBatch == null)
throw new ArgumentException("Invalid Device Batch Id", "id");
var deviceBatch = Database.DeviceBatches.Find(id)
?? throw new ArgumentException("Invalid Device Batch Id", nameof(id));
var syncTaskStatus = UpdateDevicesLinkedGroup(deviceBatch, GroupId);
var syncTaskStatus = UpdateDevicesLinkedGroup(deviceBatch, groupId, updateDescription ?? true);
if (redirect)
if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceBatch.Index(deviceBatch.Id));
@@ -257,18 +248,17 @@ namespace Disco.Web.Areas.API.Controllers
}
[DiscoAuthorize(Claims.Config.DeviceBatch.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult UpdateAssignedUsersLinkedGroup(int id, string GroupId = null, bool redirect = false)
public virtual ActionResult UpdateAssignedUsersLinkedGroup(int id, string groupId = null, bool? updateDescription = null, bool redirect = false)
{
try
{
if (id < 0)
throw new ArgumentOutOfRangeException("id");
throw new ArgumentOutOfRangeException(nameof(id));
var deviceBatch = Database.DeviceBatches.Find(id);
if (deviceBatch == null)
throw new ArgumentException("Invalid Device Batch Id", "id");
var deviceBatch = Database.DeviceBatches.Find(id)
?? throw new ArgumentException("Invalid Device Batch Id", nameof(id));
var syncTaskStatus = UpdateAssignedUsersLinkedGroup(deviceBatch, GroupId);
var syncTaskStatus = UpdateAssignedUsersLinkedGroup(deviceBatch, groupId, updateDescription ?? true);
if (redirect)
if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceBatch.Index(deviceBatch.Id));
@@ -486,9 +476,9 @@ namespace Disco.Web.Areas.API.Controllers
Database.SaveChanges();
}
private ScheduledTaskStatus UpdateDevicesLinkedGroup(DeviceBatch DeviceBatch, string devicesLinkedGroup)
private ScheduledTaskStatus UpdateDevicesLinkedGroup(DeviceBatch DeviceBatch, string devicesLinkedGroup, bool updateDescription)
{
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceBatchDevicesManagedGroup.GetKey(DeviceBatch), devicesLinkedGroup, null);
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceBatchDevicesManagedGroup.GetKey(DeviceBatch), devicesLinkedGroup, null, updateDescription);
if (DeviceBatch.DevicesLinkedGroup != configJson)
{
@@ -503,9 +493,9 @@ namespace Disco.Web.Areas.API.Controllers
return null;
}
private ScheduledTaskStatus UpdateAssignedUsersLinkedGroup(DeviceBatch DeviceBatch, string assignedUsersLinkedGroup)
private ScheduledTaskStatus UpdateAssignedUsersLinkedGroup(DeviceBatch DeviceBatch, string assignedUsersLinkedGroup, bool updateDescription)
{
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceBatchAssignedUsersManagedGroup.GetKey(DeviceBatch), assignedUsersLinkedGroup, null);
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceBatchAssignedUsersManagedGroup.GetKey(DeviceBatch), assignedUsersLinkedGroup, null, updateDescription);
if (DeviceBatch.AssignedUsersLinkedGroup != configJson)
{
@@ -582,6 +572,7 @@ namespace Disco.Web.Areas.API.Controllers
var batchesInformation = Database.DeviceBatches.Select(db => new
{
Id = db.Id,
Name = db.Name,
Comments = db.Comments,
PurchaseDate = db.PurchaseDate,
@@ -614,7 +605,7 @@ namespace Disco.Web.Areas.API.Controllers
description = bi.Comments ?? string.Empty,
color = ColorTranslator.ToHtml(color),
image = Url.Action(MVC.API.DeviceModel.Image(bi.DefaultModelId)),
link = Url.Action(MVC.Config.DeviceBatch.Index(bi.DefaultModelId))
link = Url.Action(MVC.Config.DeviceBatch.Index(bi.Id))
});
}
@@ -16,6 +16,8 @@ using Disco.Web.Models.Device;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
@@ -26,15 +28,15 @@ namespace Disco.Web.Areas.API.Controllers
{
public partial class DeviceController : AuthorizedDatabaseController
{
const string pDeviceProfileId = "deviceprofileid";
const string pDeviceBatchId = "devicebatchid";
const string pAssetNumber = "assetnumber";
const string pAssignedUserId = "assigneduserid";
const string pLocation = "location";
const string pAllowUnauthenticatedEnrol = "allowunauthenticatedenrol";
const string pDetailACAdapter = "detailacadapter";
const string pDetailBattery = "detailbattery";
const string pDetailKeyboard = "detailkeyboard";
private const string pDeviceProfileId = "deviceprofileid";
private const string pDeviceBatchId = "devicebatchid";
private const string pAssetNumber = "assetnumber";
private const string pAssignedUserId = "assigneduserid";
private const string pLocation = "location";
private const string pAllowUnauthenticatedEnrol = "allowunauthenticatedenrol";
private const string pDetailACAdapter = "detailacadapter";
private const string pDetailBattery = "detailbattery";
private const string pDetailKeyboard = "detailkeyboard";
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult Update(string id, string key, string value = null, bool redirect = false)
@@ -545,7 +547,7 @@ namespace Disco.Web.Areas.API.Controllers
#region Device Attachments
[DiscoAuthorize(Claims.Device.ShowAttachments), OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
public virtual ActionResult AttachmentDownload(int id)
public virtual ActionResult AttachmentDownload(int id, bool? inline = null)
{
var da = Database.DeviceAttachments.Find(id);
if (da != null)
@@ -553,7 +555,7 @@ namespace Disco.Web.Areas.API.Controllers
var filePath = da.RepositoryFilename(Database);
if (System.IO.File.Exists(filePath))
{
return File(filePath, da.MimeType, da.Filename);
return File(filePath, da.MimeType, (inline ?? false) ? null : da.Filename);
}
else
{
@@ -563,6 +565,44 @@ namespace Disco.Web.Areas.API.Controllers
return HttpNotFound("Invalid Attachment Number");
}
[DiscoAuthorize(Claims.Device.ShowAttachments)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult AttachmentDownloadAll(string id)
{
var device = Database.Devices
.Include(u => u.DeviceAttachments)
.Where(u => u.SerialNumber == id)
.FirstOrDefault();
if (device == null || device.DeviceAttachments.Count == 0)
return NotFound();
var responseStream = new MemoryStream();
using (var archive = new ZipArchive(responseStream, ZipArchiveMode.Create, true))
{
foreach (var attachment in device.DeviceAttachments)
{
var repoFileName = attachment.RepositoryFilename(Database);
if (System.IO.File.Exists(repoFileName))
{
var fileName = $"{Path.GetFileNameWithoutExtension(attachment.Filename)}-{attachment.Timestamp:yyyyMMdd-HHmmss}{Path.GetExtension(attachment.Filename)}";
var entry = archive.CreateEntry(fileName, CompressionLevel.Fastest);
entry.LastWriteTime = attachment.Timestamp;
using (var entryStream = entry.Open())
{
using (var attachmentStream = System.IO.File.OpenRead(repoFileName))
{
attachmentStream.CopyTo(entryStream);
}
}
}
}
}
responseStream.Position = 0;
return File(responseStream, "application/zip", $"{device.SerialNumber}_DeviceAttachments_{DateTime.Now:yyyyMMdd-HHmmss}.zip");
}
[DiscoAuthorize(Claims.Device.ShowAttachments), OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
public virtual ActionResult AttachmentThumbnail(int id)
{
@@ -166,19 +166,17 @@ namespace Disco.Web.Areas.API.Controllers
}
[DiscoAuthorize(Claims.Config.DeviceFlag.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult UpdateDevicesLinkedGroup(int id, string GroupId = null, DateTime? FilterBeginDate = null, bool redirect = false)
public virtual ActionResult UpdateDevicesLinkedGroup(int id, string groupId = null, DateTime? filterBeginDate = null, bool? updateDescription = null, bool redirect = false)
{
try
{
if (id < 0)
throw new ArgumentOutOfRangeException("id");
throw new ArgumentOutOfRangeException(nameof(id));
var deviceFlag = Database.DeviceFlags.Find(id);
if (deviceFlag == null)
throw new ArgumentException("Invalid Device Flag Id", "id");
var deviceFlag = Database.DeviceFlags.Find(id)
?? throw new ArgumentException("Invalid Device Flag Id", nameof(id));
var syncTaskStatus = UpdateDevicesLinkedGroup(deviceFlag, GroupId, FilterBeginDate);
var syncTaskStatus = UpdateDevicesLinkedGroup(deviceFlag, groupId, filterBeginDate, updateDescription ?? true);
if (redirect)
if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceFlag.Index(deviceFlag.Id));
@@ -200,25 +198,23 @@ namespace Disco.Web.Areas.API.Controllers
}
[DiscoAuthorize(Claims.Config.DeviceFlag.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult UpdateAssignedUserLinkedGroup(int id, string GroupId = null, DateTime? FilterBeginDate = null, bool redirect = false)
public virtual ActionResult UpdateAssignedUserLinkedGroup(int id, string groupId = null, DateTime? filterBeginDate = null, bool? updateDescription = null, bool redirect = false)
{
try
{
if (id < 0)
throw new ArgumentOutOfRangeException("id");
throw new ArgumentOutOfRangeException(nameof(id));
var DeviceFlag = Database.DeviceFlags.Find(id);
if (DeviceFlag == null)
throw new ArgumentException("Invalid Device Flag Id", "id");
var deviceFlag = Database.DeviceFlags.Find(id)
?? throw new ArgumentException("Invalid Device Flag Id", nameof(id));
var syncTaskStatus = UpdateAssignedUserLinkedGroup(DeviceFlag, GroupId, FilterBeginDate);
var syncTaskStatus = UpdateAssignedUserLinkedGroup(deviceFlag, groupId, filterBeginDate, updateDescription ?? true);
if (redirect)
if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceFlag.Index(DeviceFlag.Id));
return RedirectToAction(MVC.Config.DeviceFlag.Index(deviceFlag.Id));
else
{
syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.DeviceFlag.Index(DeviceFlag.Id)));
syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.DeviceFlag.Index(deviceFlag.Id)));
return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId));
}
else
@@ -340,9 +336,9 @@ namespace Disco.Web.Areas.API.Controllers
DeviceFlagService.Update(Database, deviceFlag);
}
private ScheduledTaskStatus UpdateDevicesLinkedGroup(DeviceFlag deviceFlag, string devicesLinkedGroup, DateTime? filterBeginDate)
private ScheduledTaskStatus UpdateDevicesLinkedGroup(DeviceFlag deviceFlag, string devicesLinkedGroup, DateTime? filterBeginDate, bool updateDescription)
{
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceFlagDevicesManagedGroup.GetKey(deviceFlag), devicesLinkedGroup, filterBeginDate);
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceFlagDevicesManagedGroup.GetKey(deviceFlag), devicesLinkedGroup, filterBeginDate, updateDescription);
if (deviceFlag.DevicesLinkedGroup != configJson)
{
@@ -358,9 +354,9 @@ namespace Disco.Web.Areas.API.Controllers
return null;
}
private ScheduledTaskStatus UpdateAssignedUserLinkedGroup(DeviceFlag deviceFlag, string assignedUserLinkedGroup, DateTime? filterBeginDate)
private ScheduledTaskStatus UpdateAssignedUserLinkedGroup(DeviceFlag deviceFlag, string assignedUserLinkedGroup, DateTime? filterBeginDate, bool updateDescription)
{
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceFlagDeviceAssignedUsersManagedGroup.GetKey(deviceFlag), assignedUserLinkedGroup, filterBeginDate);
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceFlagDeviceAssignedUsersManagedGroup.GetKey(deviceFlag), assignedUserLinkedGroup, filterBeginDate, updateDescription);
if (deviceFlag.DeviceUsersLinkedGroup != configJson)
{
@@ -38,8 +38,6 @@ namespace Disco.Web.Areas.API.Controllers
private const string pAssignedUserLocalAdmin = "assigneduserlocaladmin";
private const string pSetAssignedUserForLogon = "setassigneduserforlogon";
private const string pAllowUntrustedReimageJobEnrolment = "allowuntrustedreimagejobrnrolment";
private const string pDevicesLinkedGroup = "deviceslinkedgroup";
private const string pAssignedUsersLinkedGroup = "assigneduserslinkedgroup";
[DiscoAuthorize(Claims.Config.DeviceProfile.Configure)]
[HttpPost, ValidateAntiForgeryToken]
@@ -106,12 +104,6 @@ namespace Disco.Web.Areas.API.Controllers
case pAllowUntrustedReimageJobEnrolment:
UpdateAllowUntrustedReimageJobEnrolment(deviceProfile, value);
break;
case pDevicesLinkedGroup:
UpdateDevicesLinkedGroup(deviceProfile, value);
break;
case pAssignedUsersLinkedGroup:
UpdateAssignedUsersLinkedGroup(deviceProfile, value);
break;
default:
throw new Exception("Invalid Update Key");
}
@@ -385,18 +377,17 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.Config.DeviceProfile.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult UpdateDevicesLinkedGroup(int id, string GroupId = null, bool redirect = false)
public virtual ActionResult UpdateDevicesLinkedGroup(int id, string groupId = null, bool? updateDescription = null, bool redirect = false)
{
try
{
if (id < 0)
throw new ArgumentOutOfRangeException("id");
throw new ArgumentOutOfRangeException(nameof(id));
var deviceProfile = Database.DeviceProfiles.Find(id);
if (deviceProfile == null)
throw new ArgumentException("Invalid Device Profile Id", "id");
var deviceProfile = Database.DeviceProfiles.Find(id)
?? throw new ArgumentException("Invalid Device Profile Id", nameof(id));
var syncTaskStatus = UpdateDevicesLinkedGroup(deviceProfile, GroupId);
var syncTaskStatus = UpdateDevicesLinkedGroup(deviceProfile, groupId, updateDescription ?? true);
if (redirect)
if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceProfile.Index(deviceProfile.Id));
@@ -418,18 +409,17 @@ namespace Disco.Web.Areas.API.Controllers
}
[DiscoAuthorize(Claims.Config.DeviceProfile.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult UpdateAssignedUsersLinkedGroup(int id, string GroupId = null, bool redirect = false)
public virtual ActionResult UpdateAssignedUsersLinkedGroup(int id, string groupId = null, bool? updateDescription = null, bool redirect = false)
{
try
{
if (id < 0)
throw new ArgumentOutOfRangeException("id");
throw new ArgumentOutOfRangeException(nameof(id));
var deviceProfile = Database.DeviceProfiles.Find(id);
if (deviceProfile == null)
throw new ArgumentException("Invalid Device Profile Id", "id");
var deviceProfile = Database.DeviceProfiles.Find(id)
?? throw new ArgumentException("Invalid Device Profile Id", nameof(id));
var syncTaskStatus = UpdateAssignedUsersLinkedGroup(deviceProfile, GroupId);
var syncTaskStatus = UpdateAssignedUsersLinkedGroup(deviceProfile, groupId, updateDescription ?? true);
if (redirect)
if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceProfile.Index(deviceProfile.Id));
@@ -725,9 +715,9 @@ namespace Disco.Web.Areas.API.Controllers
throw new Exception("Invalid Boolean Value");
}
private ScheduledTaskStatus UpdateDevicesLinkedGroup(DeviceProfile deviceProfile, string devicesLinkedGroup)
private ScheduledTaskStatus UpdateDevicesLinkedGroup(DeviceProfile deviceProfile, string devicesLinkedGroup, bool updateDescription)
{
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceProfileDevicesManagedGroup.GetKey(deviceProfile), devicesLinkedGroup, null);
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceProfileDevicesManagedGroup.GetKey(deviceProfile), devicesLinkedGroup, null, updateDescription);
if (deviceProfile.DevicesLinkedGroup != configJson)
{
@@ -742,9 +732,9 @@ namespace Disco.Web.Areas.API.Controllers
return null;
}
private ScheduledTaskStatus UpdateAssignedUsersLinkedGroup(DeviceProfile deviceProfile, string assignedUsersLinkedGroup)
private ScheduledTaskStatus UpdateAssignedUsersLinkedGroup(DeviceProfile deviceProfile, string assignedUsersLinkedGroup, bool updateDescription)
{
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceProfileAssignedUsersManagedGroup.GetKey(deviceProfile), assignedUsersLinkedGroup, null);
var configJson = ADManagedGroup.ValidConfigurationToJson(DeviceProfileAssignedUsersManagedGroup.GetKey(deviceProfile), assignedUsersLinkedGroup, null, updateDescription);
if (deviceProfile.AssignedUsersLinkedGroup != configJson)
{
@@ -20,6 +20,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
@@ -254,18 +255,17 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult UpdateDevicesLinkedGroup(string id, string GroupId = null, DateTime? FilterBeginDate = null, bool redirect = false)
public virtual ActionResult UpdateDevicesLinkedGroup(string id, string groupId = null, DateTime? filterBeginDate = null, bool? updateDescription = null, bool redirect = false)
{
try
{
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException("id");
throw new ArgumentNullException(nameof(id));
var documentTemplate = Database.DocumentTemplates.Find(id);
if (documentTemplate == null)
throw new ArgumentException("Invalid Document Template Id", "id");
var documentTemplate = Database.DocumentTemplates.Find(id)
?? throw new ArgumentException("Invalid Document Template Id", nameof(id));
var syncTaskStatus = UpdateDevicesLinkedGroup(documentTemplate, GroupId, FilterBeginDate);
var syncTaskStatus = UpdateDevicesLinkedGroup(documentTemplate, groupId, filterBeginDate, updateDescription ?? true);
if (redirect)
if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id));
@@ -288,18 +288,17 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult UpdateUsersLinkedGroup(string id, string GroupId = null, DateTime? FilterBeginDate = null, bool redirect = false)
public virtual ActionResult UpdateUsersLinkedGroup(string id, string groupId = null, DateTime? filterBeginDate = null, bool? updateDescription = null, bool redirect = false)
{
try
{
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException("id");
throw new ArgumentNullException(nameof(id));
var documentTemplate = Database.DocumentTemplates.Find(id);
if (documentTemplate == null)
throw new ArgumentException("Invalid Document Template Id", "id");
var documentTemplate = Database.DocumentTemplates.Find(id)
?? throw new ArgumentException("Invalid Document Template Id", nameof(id));
var syncTaskStatus = UpdateUsersLinkedGroup(documentTemplate, GroupId, FilterBeginDate);
var syncTaskStatus = UpdateUsersLinkedGroup(documentTemplate, groupId, filterBeginDate, updateDescription ?? true);
if (redirect)
if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id));
@@ -469,16 +468,16 @@ namespace Disco.Web.Areas.API.Controllers
Database.SaveChanges();
}
private ScheduledTaskStatus UpdateDevicesLinkedGroup(DocumentTemplate DocumentTemplate, string DevicesLinkedGroup, DateTime? FilterBeginDate)
private ScheduledTaskStatus UpdateDevicesLinkedGroup(DocumentTemplate documentTemplate, string devicesLinkedGroup, DateTime? filterBeginDate, bool updateDescription)
{
var configJson = ADManagedGroup.ValidConfigurationToJson(DocumentTemplateDevicesManagedGroup.GetKey(DocumentTemplate), DevicesLinkedGroup, FilterBeginDate);
var configJson = ADManagedGroup.ValidConfigurationToJson(DocumentTemplateDevicesManagedGroup.GetKey(documentTemplate), devicesLinkedGroup, filterBeginDate, updateDescription);
if (DocumentTemplate.DevicesLinkedGroup != configJson)
if (documentTemplate.DevicesLinkedGroup != configJson)
{
DocumentTemplate.DevicesLinkedGroup = configJson;
documentTemplate.DevicesLinkedGroup = configJson;
Database.SaveChanges();
var managedGroup = DocumentTemplateDevicesManagedGroup.Initialize(DocumentTemplate);
var managedGroup = DocumentTemplateDevicesManagedGroup.Initialize(documentTemplate);
if (managedGroup != null) // Sync Group
return ADManagedGroupsSyncTask.ScheduleSync(managedGroup);
}
@@ -486,16 +485,16 @@ namespace Disco.Web.Areas.API.Controllers
return null;
}
private ScheduledTaskStatus UpdateUsersLinkedGroup(DocumentTemplate DocumentTemplate, string UsersLinkedGroup, DateTime? FilterBeginDate)
private ScheduledTaskStatus UpdateUsersLinkedGroup(DocumentTemplate documentTemplate, string usersLinkedGroup, DateTime? filterBeginDate, bool updateDescription)
{
var configJson = ADManagedGroup.ValidConfigurationToJson(DocumentTemplateUsersManagedGroup.GetKey(DocumentTemplate), UsersLinkedGroup, FilterBeginDate);
var configJson = ADManagedGroup.ValidConfigurationToJson(DocumentTemplateUsersManagedGroup.GetKey(documentTemplate), usersLinkedGroup, filterBeginDate, updateDescription);
if (DocumentTemplate.UsersLinkedGroup != configJson)
if (documentTemplate.UsersLinkedGroup != configJson)
{
DocumentTemplate.UsersLinkedGroup = configJson;
documentTemplate.UsersLinkedGroup = configJson;
Database.SaveChanges();
var managedGroup = DocumentTemplateUsersManagedGroup.Initialize(DocumentTemplate);
var managedGroup = DocumentTemplateUsersManagedGroup.Initialize(documentTemplate);
if (managedGroup != null) // Sync Group
return ADManagedGroupsSyncTask.ScheduleSync(managedGroup);
}
@@ -1775,7 +1774,7 @@ namespace Disco.Web.Areas.API.Controllers
}
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult Generate(string id, string targetId)
public virtual ActionResult Generate(string id, string targetId, bool? inline = null)
{
Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, id, targetId, out var template, out var target, out _);
@@ -1788,7 +1787,7 @@ namespace Disco.Web.Areas.API.Controllers
}
Database.SaveChanges();
return File(document, "application/pdf", $"{template.Id}_{target.AttachmentReferenceId.Replace('\\', '_')}_{timestamp:yyyyMMdd-HHmmss}.pdf");
return File(document, "application/pdf", (inline ?? false) ? null : $"{template.Id}_{target.AttachmentReferenceId.Replace('\\', '_')}_{timestamp:yyyyMMdd-HHmmss}.pdf");
}
[DiscoAuthorize(Claims.Config.DocumentTemplate.Delete)]
@@ -1880,13 +1879,89 @@ namespace Disco.Web.Areas.API.Controllers
}
}
[DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult BulkDownload([Required] string id, bool? latestOnly = null, DateTime? threshold = null)
{
var template = Database.DocumentTemplates.FirstOrDefault(t => t.Id == id)
?? throw new ArgumentException("Unknown document template", nameof(id));
var attachments = BulkDownloadRetrieveAttachments(template, latestOnly ?? false, threshold);
var responseStream = new MemoryStream();
using (var archive = new ZipArchive(responseStream, ZipArchiveMode.Create, true))
{
foreach (var attachment in attachments)
{
var repoFileName = attachment.RepositoryFilename(Database);
if (System.IO.File.Exists(repoFileName))
{
var entry = archive.CreateEntry($"{attachment.Reference.ToString().Replace('\\', '_')}-{attachment.Timestamp:yyyyMMdd-HHmmss}_{attachment.Filename}", CompressionLevel.Fastest);
entry.LastWriteTime = attachment.Timestamp;
using (var entryStream = entry.Open())
{
using (var attachmentStream = System.IO.File.OpenRead(repoFileName))
{
attachmentStream.CopyTo(entryStream);
}
}
}
}
}
responseStream.Position = 0;
return File(responseStream, "application/zip", $"{template.Id}_Attachments_{DateTime.Now:yyyyMMdd-HHmmss}.zip");
}
private List<IAttachment> BulkDownloadRetrieveAttachments(DocumentTemplate template, bool latestOnly, DateTime? threshold)
{
List<IAttachment> attachments;
switch (template.Scope)
{
case DocumentTemplate.DocumentTemplateScopes.Device:
Authorization.Require(Claims.Device.ShowAttachments);
var deviceQuery = Database.DeviceAttachments
.Where(a => a.DocumentTemplateId == template.Id);
if (threshold.HasValue)
deviceQuery = deviceQuery.Where(a => a.Timestamp >= threshold.Value);
attachments = deviceQuery.OrderBy(a => a.Timestamp).ToList<IAttachment>();
break;
case DocumentTemplate.DocumentTemplateScopes.Job:
Authorization.Require(Claims.Job.ShowAttachments);
var jobQuery = Database.JobAttachments
.Where(a => a.DocumentTemplateId == template.Id);
if (threshold.HasValue)
jobQuery = jobQuery.Where(a => a.Timestamp >= threshold.Value);
attachments = jobQuery.OrderBy(a => a.Timestamp).ToList<IAttachment>();
break;
case DocumentTemplate.DocumentTemplateScopes.User:
Authorization.Require(Claims.User.ShowAttachments);
var userQuery = Database.UserAttachments
.Where(a => a.DocumentTemplateId == template.Id);
if (threshold.HasValue)
userQuery = userQuery.Where(a => a.Timestamp >= threshold.Value);
attachments = userQuery.OrderBy(a => a.Timestamp).ToList<IAttachment>();
break;
default:
throw new NotSupportedException();
}
if (latestOnly)
{
attachments.Reverse();
attachments = attachments.GroupBy(a => a.Reference).Select(a => a.First()).OrderBy(a => a.Timestamp).ToList();
}
return attachments;
}
#endregion
#region Handlers
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult GenerateDocumentHandlerUi(string templateId, string targetId, string handlerId)
public virtual ActionResult GenerateDocumentHandlerUi(string id, string targetId, string handlerId)
{
Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, templateId, targetId, out var template, out var target, out var targetUser);
Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, id, targetId, out var template, out var target, out var targetUser);
var handlerManifest = Plugins.GetPluginFeature(handlerId, typeof(DocumentHandlerProviderFeature));
@@ -1907,9 +1982,9 @@ namespace Disco.Web.Areas.API.Controllers
}
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult DocumentHandlers(string templateId, string targetId)
public virtual ActionResult DocumentHandlers(string id, string targetId)
{
Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, templateId, targetId, out var template, out var target, out _);
Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, id, targetId, out var template, out var target, out _);
var handlers = Plugins.GetPluginFeatures(typeof(DocumentHandlerProviderFeature))
.SelectMany(f =>
@@ -4,8 +4,11 @@ using Disco.Models.Services.Documents;
using Disco.Services;
using Disco.Services.Authorization;
using Disco.Services.Documents;
using Disco.Services.Plugins;
using Disco.Services.Plugins.Features.DocumentHandlerProvider;
using Disco.Services.Users;
using Disco.Services.Web;
using Disco.Web.Areas.API.Models.DocumentTemplate;
using System;
using System.Collections.Generic;
using System.IO;
@@ -16,12 +19,12 @@ namespace Disco.Web.Areas.API.Controllers
{
public partial class DocumentTemplatePackageController : AuthorizedDatabaseController
{
const string pDescription = "description";
const string pScope = "scope";
const string pFilterExpression = "filterexpression";
const string pOnGenerateExpression = "ongenerateexpression";
const string pIsHidden = "ishidden";
const string pInsertBlankPages = "insertblankpages";
private const string pDescription = "description";
private const string pScope = "scope";
private const string pFilterExpression = "filterexpression";
private const string pOnGenerateExpression = "ongenerateexpression";
private const string pIsHidden = "ishidden";
private const string pInsertBlankPages = "insertblankpages";
[DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)]
[HttpPost, ValidateAntiForgeryToken]
@@ -393,7 +396,7 @@ namespace Disco.Web.Areas.API.Controllers
}
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult Generate(string id, string targetId)
public virtual ActionResult Generate(string id, string targetId, bool? inline = false)
{
if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException(nameof(id));
@@ -432,7 +435,7 @@ namespace Disco.Web.Areas.API.Controllers
}
Database.SaveChanges();
return File(document, "application/pdf", $"{package.Id}_{target.AttachmentReferenceId.Replace('\\', '_')}_{timestamp:yyyyMMdd-HHmmss}.pdf");
return File(document, "application/pdf", (inline ?? false) ? null : $"{package.Id}_{target.AttachmentReferenceId.Replace('\\', '_')}_{timestamp:yyyyMMdd-HHmmss}.pdf");
}
[DiscoAuthorize(Claims.Config.DocumentTemplate.Delete)]
@@ -470,5 +473,65 @@ namespace Disco.Web.Areas.API.Controllers
}
#endregion
#region Handlers
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult GenerateDocumentHandlerUi(string id, string targetId, string handlerId)
{
DocumentTemplatePackageExtensions.GetPackageAndTarget(Database, Authorization, id, targetId, out var package, out var target, out var targetUser);
var handlerManifest = Plugins.GetPluginFeature(handlerId, typeof(DocumentHandlerProviderFeature));
using (var handler = handlerManifest.CreateInstance<DocumentHandlerProviderFeature>())
{
if (!handler.CanHandle(package, target))
throw new NotSupportedException("Handler does not support this Document Template and Target");
var handlerPartialView = handler.GenerationOptionsUi;
if (handlerPartialView == null)
throw new NotSupportedException("Handler does not have a Generation Options UI");
var model = handler.GetGenerationOptionsUiModel(package, target, targetUser, CurrentUser);
return this.PrecompiledPartialView(handlerPartialView, model);
}
}
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult DocumentHandlers(string id, string targetId)
{
DocumentTemplatePackageExtensions.GetPackageAndTarget(Database, Authorization, id, targetId, out var package, out var target, out _);
var handlers = Plugins.GetPluginFeatures(typeof(DocumentHandlerProviderFeature))
.SelectMany(f =>
{
using (var handler = f.CreateInstance<DocumentHandlerProviderFeature>())
{
if (handler.CanHandle(package, target))
return OneOf.Create(new DocumentHandlerModel()
{
Id = f.Id,
Title = handler.HandlerTitle,
Description = handler.HandlerDescription,
UiUrl = handler.GenerationOptionsUi == null ? null : Url.Action(MVC.API.DocumentTemplatePackage.GenerateDocumentHandlerUi(package.Id, target.AttachmentReferenceId, f.Id)),
Icon = handler.GenerationOptionsIcon,
});
}
return Enumerable.Empty<DocumentHandlerModel>();
}).ToList();
var model = new DocumentHandlersModel()
{
TemplateId = package.Id,
TemplateName = package.Description,
TargetId = target.AttachmentReferenceId,
TargetName = target.ToString(),
Handlers = handlers,
};
return Json(model);
}
#endregion
}
}
@@ -88,5 +88,21 @@ namespace Disco.Web.Areas.API.Controllers
return BadRequest(ex.Message);
}
}
[DiscoAuthorize(Claims.Config.Enrolment.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult LegacyDiscovery(bool enabled)
{
try
{
Database.DiscoConfiguration.Devices.EnrollmentLegacyDiscoveryDisabled = !enabled;
Database.SaveChanges();
return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
}
@@ -16,6 +16,8 @@ using Disco.Web.Models.Job;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Caching;
@@ -1925,7 +1927,7 @@ namespace Disco.Web.Areas.API.Controllers
#region Job Attachments
[DiscoAuthorize(Claims.Job.ShowAttachments), OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
public virtual ActionResult AttachmentDownload(int id)
public virtual ActionResult AttachmentDownload(int id, bool? inline = null)
{
var ja = Database.JobAttachments.Find(id);
if (ja != null)
@@ -1933,7 +1935,7 @@ namespace Disco.Web.Areas.API.Controllers
var filePath = ja.RepositoryFilename(Database);
if (System.IO.File.Exists(filePath))
{
return File(filePath, ja.MimeType, ja.Filename);
return File(filePath, ja.MimeType, (inline ?? false) ? null : ja.Filename);
}
else
{
@@ -1943,6 +1945,44 @@ namespace Disco.Web.Areas.API.Controllers
return HttpNotFound("Invalid Attachment Number");
}
[DiscoAuthorize(Claims.Job.ShowAttachments)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult AttachmentDownloadAll(int id)
{
var job = Database.Jobs
.Include(u => u.JobAttachments)
.Where(u => u.Id == id)
.FirstOrDefault();
if (job == null || job.JobAttachments.Count == 0)
return NotFound();
var responseStream = new MemoryStream();
using (var archive = new ZipArchive(responseStream, ZipArchiveMode.Create, true))
{
foreach (var attachment in job.JobAttachments)
{
var repoFileName = attachment.RepositoryFilename(Database);
if (System.IO.File.Exists(repoFileName))
{
var fileName = $"{Path.GetFileNameWithoutExtension(attachment.Filename)}-{attachment.Timestamp:yyyyMMdd-HHmmss}{Path.GetExtension(attachment.Filename)}";
var entry = archive.CreateEntry(fileName, CompressionLevel.Fastest);
entry.LastWriteTime = attachment.Timestamp;
using (var entryStream = entry.Open())
{
using (var attachmentStream = System.IO.File.OpenRead(repoFileName))
{
attachmentStream.CopyTo(entryStream);
}
}
}
}
}
responseStream.Position = 0;
return File(responseStream, "application/zip", $"{job.Id}_JobAttachments_{DateTime.Now:yyyyMMdd-HHmmss}.zip");
}
[DiscoAuthorize(Claims.Job.ShowAttachments), OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
public virtual ActionResult AttachmentThumbnail(int id)
{
@@ -7,6 +7,7 @@ using Disco.Services.Messaging;
using Disco.Services.Users;
using Disco.Services.Web;
using Disco.Web.Areas.API.Models.Shared;
using Disco.Web.Models.Shared;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -309,16 +310,99 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorizeAny(Claims.Config.System.ConfigureActiveDirectory, Claims.Config.DeviceProfile.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult DomainOrganisationalUnits()
public virtual ActionResult DomainOrganisationalUnitTree(string expandNode = null)
{
var domainOUs = ActiveDirectory.RetrieveADOrganisationalUnitStructure()
.Select(d => new Models.System.DomainOrganisationalUnitsModel() { Domain = d.Item1, OrganisationalUnits = d.Item2 })
.Select(ous => ous.ToFancyTreeNode()).ToList();
List<FancyTreeNode> nodes;
nodes = ActiveDirectory.Context.Domains
.Select(d => new FancyTreeNode()
{
key = d.DistinguishedName,
title = d.NetBiosName,
folder = true,
tooltip = d.Name,
children = d.GetAvailableDomainController().RetrieveADOrganisationUnits()
.Select(ou => new FancyTreeNode()
{
key = ou.DistinguishedName,
title = ou.Name,
folder = true,
tooltip = ou.DistinguishedName,
unselectable = false,
expanded = false,
lazy = true,
}).ToArray(),
unselectable = true,
expanded = true,
lazy = false,
}).ToList();
if (!string.IsNullOrWhiteSpace(expandNode) && ActiveDirectory.Context.TryGetDomainFromDistinguishedName(expandNode, out var domain))
{
// domain node
var node = nodes.FirstOrDefault(n => n.key.Equals(domain.DistinguishedName, StringComparison.OrdinalIgnoreCase));
if (node != null)
{
var domainController = domain.GetAvailableDomainController();
var ouIndex = expandNode.Length;
do
{
ouIndex = expandNode.LastIndexOf("OU=", ouIndex - 1, StringComparison.OrdinalIgnoreCase);
if (ouIndex >= 0)
{
var dn = expandNode.Substring(ouIndex);
node = node.children.FirstOrDefault(n => n.key.Equals(dn, StringComparison.OrdinalIgnoreCase));
if (node != null)
{
node.children = domainController.RetrieveADOrganisationUnits(dn).Select(ou => new FancyTreeNode()
{
key = ou.DistinguishedName,
title = ou.Name,
folder = true,
tooltip = ou.DistinguishedName,
unselectable = false,
expanded = false,
lazy = true,
}).ToArray();
node.expanded = true;
node.lazy = false;
}
}
} while (node != null && ouIndex > 0);
}
}
return new JsonResult()
{
Data = domainOUs,
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = nodes,
MaxJsonLength = int.MaxValue
};
}
[DiscoAuthorizeAny(Claims.Config.System.ConfigureActiveDirectory, Claims.Config.DeviceProfile.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult DomainOrganisationalUnits(string node)
{
if (string.IsNullOrWhiteSpace(node))
throw new ArgumentNullException("node");
if (!ActiveDirectory.Context.TryGetDomainFromDistinguishedName(node, out var domain))
throw new ArgumentException("Invalid node distinguished name", "node");
var domainController = domain.GetAvailableDomainController();
var nodes = domainController.RetrieveADOrganisationUnits(node).Select(ou => new FancyTreeNode()
{
key = ou.DistinguishedName,
title = ou.Name,
folder = true,
tooltip = ou.DistinguishedName,
unselectable = false,
expanded = false,
lazy = true,
}).ToArray();
return new JsonResult()
{
Data = nodes,
MaxJsonLength = int.MaxValue
};
}
@@ -9,6 +9,8 @@ using Disco.Services.Users;
using Disco.Services.Web;
using System;
using System.Data.Entity;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Mvc;
@@ -106,7 +108,7 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.User.ShowAttachments)]
[OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
public virtual ActionResult AttachmentDownload(int id)
public virtual ActionResult AttachmentDownload(int id, bool? inline = null)
{
var ua = Database.UserAttachments.Find(id);
if (ua != null)
@@ -114,7 +116,7 @@ namespace Disco.Web.Areas.API.Controllers
var filePath = ua.RepositoryFilename(Database);
if (System.IO.File.Exists(filePath))
{
return File(filePath, ua.MimeType, ua.Filename);
return File(filePath, ua.MimeType, (inline ?? false) ? null : ua.Filename);
}
else
{
@@ -124,6 +126,46 @@ namespace Disco.Web.Areas.API.Controllers
return HttpNotFound("Invalid Attachment Number");
}
[DiscoAuthorize(Claims.User.ShowAttachments)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult AttachmentDownloadAll(string id)
{
id = ActiveDirectory.ParseDomainAccountId(id);
var user = Database.Users
.Include(u => u.UserAttachments)
.Where(u => u.UserId == id)
.FirstOrDefault();
if (user == null || user.UserAttachments.Count == 0)
return NotFound();
var responseStream = new MemoryStream();
using (var archive = new ZipArchive(responseStream, ZipArchiveMode.Create, true))
{
foreach (var attachment in user.UserAttachments)
{
var repoFileName = attachment.RepositoryFilename(Database);
if (System.IO.File.Exists(repoFileName))
{
var fileName = $"{Path.GetFileNameWithoutExtension(attachment.Filename)}-{attachment.Timestamp:yyyyMMdd-HHmmss}{Path.GetExtension(attachment.Filename)}";
var entry = archive.CreateEntry(fileName, CompressionLevel.Fastest);
entry.LastWriteTime = attachment.Timestamp;
using (var entryStream = entry.Open())
{
using (var attachmentStream = System.IO.File.OpenRead(repoFileName))
{
attachmentStream.CopyTo(entryStream);
}
}
}
}
}
responseStream.Position = 0;
return File(responseStream, "application/zip", $"{user.UserId.Replace('\\', '_')}_UserAttachments_{DateTime.Now:yyyyMMdd-HHmmss}.zip");
}
[DiscoAuthorize(Claims.User.ShowAttachments)]
[OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
public virtual ActionResult AttachmentThumbnail(int id)
@@ -166,25 +166,23 @@ namespace Disco.Web.Areas.API.Controllers
}
[DiscoAuthorize(Claims.Config.UserFlag.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult UpdateAssignedUsersLinkedGroup(int id, string GroupId = null, DateTime? FilterBeginDate = null, bool redirect = false)
public virtual ActionResult UpdateAssignedUsersLinkedGroup(int id, string groupId = null, DateTime? filterBeginDate = null, bool? updateDescription = null, bool redirect = false)
{
try
{
if (id < 0)
throw new ArgumentOutOfRangeException(nameof(id));
var UserFlag = Database.UserFlags.Find(id);
if (UserFlag == null)
throw new ArgumentException("Invalid User Flag Id", nameof(id));
var userFlag = Database.UserFlags.Find(id)
?? throw new ArgumentException("Invalid User Flag Id", nameof(id));
var syncTaskStatus = UpdateAssignedUsersLinkedGroup(UserFlag, GroupId, FilterBeginDate);
var syncTaskStatus = UpdateAssignedUsersLinkedGroup(userFlag, groupId, filterBeginDate, updateDescription ?? true);
if (redirect)
if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.UserFlag.Index(UserFlag.Id));
return RedirectToAction(MVC.Config.UserFlag.Index(userFlag.Id));
else
{
syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.UserFlag.Index(UserFlag.Id)));
syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.UserFlag.Index(userFlag.Id)));
return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId));
}
else
@@ -200,25 +198,23 @@ namespace Disco.Web.Areas.API.Controllers
}
[DiscoAuthorize(Claims.Config.UserFlag.Configure)]
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult UpdateAssignedUserDevicesLinkedGroup(int id, string GroupId = null, DateTime? FilterBeginDate = null, bool redirect = false)
public virtual ActionResult UpdateAssignedUserDevicesLinkedGroup(int id, string groupId = null, DateTime? filterBeginDate = null, bool? updateDescription = null, bool redirect = false)
{
try
{
if (id < 0)
throw new ArgumentOutOfRangeException(nameof(id));
var UserFlag = Database.UserFlags.Find(id);
if (UserFlag == null)
throw new ArgumentException("Invalid User Flag Id", nameof(id));
var userFlag = Database.UserFlags.Find(id)
?? throw new ArgumentException("Invalid User Flag Id", nameof(id));
var syncTaskStatus = UpdateAssignedUserDevicesLinkedGroup(UserFlag, GroupId, FilterBeginDate);
var syncTaskStatus = UpdateAssignedUserDevicesLinkedGroup(userFlag, groupId, filterBeginDate, updateDescription ?? true);
if (redirect)
if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.UserFlag.Index(UserFlag.Id));
return RedirectToAction(MVC.Config.UserFlag.Index(userFlag.Id));
else
{
syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.UserFlag.Index(UserFlag.Id)));
syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.UserFlag.Index(userFlag.Id)));
return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId));
}
else
@@ -340,19 +336,19 @@ namespace Disco.Web.Areas.API.Controllers
UserFlagService.Update(Database, UserFlag);
}
private ScheduledTaskStatus UpdateAssignedUsersLinkedGroup(UserFlag UserFlag, string AssignedUsersLinkedGroup, DateTime? FilterBeginDate)
private ScheduledTaskStatus UpdateAssignedUsersLinkedGroup(UserFlag userFlag, string assignedUsersLinkedGroup, DateTime? filterBeginDate, bool updateDescription)
{
var configJson = ADManagedGroup.ValidConfigurationToJson(UserFlagUsersManagedGroup.GetKey(UserFlag), AssignedUsersLinkedGroup, FilterBeginDate);
var configJson = ADManagedGroup.ValidConfigurationToJson(UserFlagUsersManagedGroup.GetKey(userFlag), assignedUsersLinkedGroup, filterBeginDate, updateDescription);
if (UserFlag.UsersLinkedGroup != configJson)
if (userFlag.UsersLinkedGroup != configJson)
{
UserFlag.UsersLinkedGroup = configJson;
UserFlagService.Update(Database, UserFlag);
userFlag.UsersLinkedGroup = configJson;
UserFlagService.Update(Database, userFlag);
if (UserFlag.UsersLinkedGroup != null)
if (userFlag.UsersLinkedGroup != null)
{
// Sync Group
if (UserFlagUsersManagedGroup.TryGetManagedGroup(UserFlag, out var managedGroup))
if (UserFlagUsersManagedGroup.TryGetManagedGroup(userFlag, out var managedGroup))
{
return ADManagedGroupsSyncTask.ScheduleSync(managedGroup);
}
@@ -361,19 +357,19 @@ namespace Disco.Web.Areas.API.Controllers
return null;
}
private ScheduledTaskStatus UpdateAssignedUserDevicesLinkedGroup(UserFlag UserFlag, string AssignedUserDevicesLinkedGroup, DateTime? FilterBeginDate)
private ScheduledTaskStatus UpdateAssignedUserDevicesLinkedGroup(UserFlag userFlag, string assignedUserDevicesLinkedGroup, DateTime? filterBeginDate, bool updateDescription)
{
var configJson = ADManagedGroup.ValidConfigurationToJson(UserFlagUserDevicesManagedGroup.GetKey(UserFlag), AssignedUserDevicesLinkedGroup, FilterBeginDate);
var configJson = ADManagedGroup.ValidConfigurationToJson(UserFlagUserDevicesManagedGroup.GetKey(userFlag), assignedUserDevicesLinkedGroup, filterBeginDate, updateDescription);
if (UserFlag.UserDevicesLinkedGroup != configJson)
if (userFlag.UserDevicesLinkedGroup != configJson)
{
UserFlag.UserDevicesLinkedGroup = configJson;
UserFlagService.Update(Database, UserFlag);
userFlag.UserDevicesLinkedGroup = configJson;
UserFlagService.Update(Database, userFlag);
if (UserFlag.UserDevicesLinkedGroup != null)
if (userFlag.UserDevicesLinkedGroup != null)
{
// Sync Group
if (UserFlagUserDevicesManagedGroup.TryGetManagedGroup(UserFlag, out var managedGroup))
if (UserFlagUserDevicesManagedGroup.TryGetManagedGroup(userFlag, out var managedGroup))
{
return ADManagedGroupsSyncTask.ScheduleSync(managedGroup);
}
@@ -5,9 +5,12 @@ namespace Disco.Web.Areas.API.Models.Activation
{
public class CallbackModel
{
public string Origin { get; set; }
public Guid DeploymentId { get; set; }
public Guid CorrelationId { get; set; }
[StringLength(50)]
public string UserId { get; set; }
public long Timestamp { get; set; }
public string Proof { get; set; }
}
}
@@ -1,45 +0,0 @@
using Disco.Services.Interop.ActiveDirectory;
using Disco.Web.Models.Shared;
using System.Collections.Generic;
using System.Linq;
namespace Disco.Web.Areas.API.Models.System
{
public class DomainOrganisationalUnitsModel
{
public ADDomain Domain { get; set; }
public List<ADOrganisationalUnit> OrganisationalUnits { get; set; }
public FancyTreeNode ToFancyTreeNode()
{
FancyTreeNode[] children = OrganisationalUnits.Select(ou => OrganisationalUnitToFancyTreeNode(ou)).ToArray();
return new FancyTreeNode()
{
key = Domain.DistinguishedName,
title = Domain.NetBiosName,
folder = true,
tooltip = Domain.Name,
children = children,
unselectable = false,
expanded = true
};
}
private FancyTreeNode OrganisationalUnitToFancyTreeNode(ADOrganisationalUnit OrganisationalUnit)
{
FancyTreeNode[] children = OrganisationalUnit.Children == null
? null
: OrganisationalUnit.Children.Select(ou => OrganisationalUnitToFancyTreeNode(ou)).ToArray();
return new FancyTreeNode()
{
key = OrganisationalUnit.DistinguishedName,
title = OrganisationalUnit.Name,
folder = true,
tooltip = OrganisationalUnit.DistinguishedName,
children = children,
unselectable = false
};
}
}
}
@@ -1,17 +0,0 @@
@model Disco.Web.Areas.API.Models.Activation.CallbackModel
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body data-deploymentid="@Model.DeploymentId" data-correlationid="@Model.CorrelationId" data-userid="@Model.UserId">
<script>
const deploymentId = document.body.dataset.deploymentid;
const correlationId = document.body.dataset.correlationid;
const userId = document.body.dataset.userid;
window.parent.activateCallbackResponse(deploymentId, correlationId, userId);
</script>
</body>
</html>
@@ -1,107 +0,0 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Disco.Web.Areas.API.Views.Activation
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Helpers;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using System.Web.Security;
using System.Web.UI;
using System.Web.WebPages;
using Disco;
using Disco.Models.Repository;
using Disco.Services;
using Disco.Services.Authorization;
using Disco.Services.Web;
using Disco.Web;
using Disco.Web.Extensions;
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")]
[System.Web.WebPages.PageVirtualPathAttribute("~/Areas/API/Views/Activation/_ActivateCallback.cshtml")]
public partial class _ActivateCallback : Disco.Services.Web.WebViewPage<Disco.Web.Areas.API.Models.Activation.CallbackModel>
{
public _ActivateCallback()
{
}
public override void Execute()
{
#line 2 "..\..\Areas\API\Views\Activation\_ActivateCallback.cshtml"
Layout = null;
#line default
#line hidden
WriteLiteral("\r\n<!DOCTYPE html>\r\n<html");
WriteLiteral(" lang=\"en\"");
WriteLiteral(">\r\n<head>\r\n</head>\r\n<body");
WriteLiteral(" data-deploymentid=\"");
#line 9 "..\..\Areas\API\Views\Activation\_ActivateCallback.cshtml"
Write(Model.DeploymentId);
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-correlationid=\"");
#line 9 "..\..\Areas\API\Views\Activation\_ActivateCallback.cshtml"
Write(Model.CorrelationId);
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-userid=\"");
#line 9 "..\..\Areas\API\Views\Activation\_ActivateCallback.cshtml"
Write(Model.UserId);
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(@">
<script>
const deploymentId = document.body.dataset.deploymentid;
const correlationId = document.body.dataset.correlationid;
const userId = document.body.dataset.userid;
window.parent.activateCallbackResponse(deploymentId, correlationId, userId);
</script>
</body>
</html>
");
}
}
}
#pragma warning restore 1591
@@ -1,7 +1,13 @@
using Disco.Models.UI.Config.Enrolment;
using Disco.Services.Authorization;
using Disco.Services.Devices.Enrolment;
using Disco.Services.Interop.ActiveDirectory;
using Disco.Services.Interop.DNS;
using Disco.Services.Interop.VicEduDept;
using Disco.Services.Plugins;
using Disco.Services.Plugins.Features.UIExtension;
using Disco.Services.Web;
using System;
using System.Linq;
using System.Web.Mvc;
@@ -12,10 +18,30 @@ namespace Disco.Web.Areas.Config.Controllers
[DiscoAuthorize(Claims.Config.Enrolment.Show)]
public virtual ActionResult Index()
{
var serverUrl = Request.Url;
if ((serverUrl.HostNameType == UriHostNameType.Dns && serverUrl.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) ||
serverUrl.HostNameType == UriHostNameType.IPv4 || serverUrl.HostNameType == UriHostNameType.IPv6)
{
serverUrl = new UriBuilder(serverUrl)
{
Host = Environment.MachineName
}.Uri;
}
var srvRecord = DnsService.Query<SrvDnsRecord>(WindowsDeviceEnrolment.GetDnsServiceLocationRecordName(), true).FirstOrDefault();
var srvValue = srvRecord == null ? null : (srvRecord.Port == 443 ? srvRecord.Target : $"{srvRecord.Target}:{srvRecord.Port}");
var m = new Models.Enrolment.IndexModel()
{
MacSshUsername = Database.DiscoConfiguration.Bootstrapper.MacSshUsername,
PendingTimeoutMinutes = (int)Database.DiscoConfiguration.Bootstrapper.PendingTimeout.TotalMinutes,
MacEnrolUrl = new Uri(serverUrl, Url.Action(MVC.Services.Client.Unauthenticated("MacSecureEnrol"))),
HostingPluginInstalled = Plugins.PluginInstalled("Hosting"),
IsServicesEducationVicGovAuDomain = ActiveDirectory.Context.PrimaryDomain.Name.Equals("services.education.vic.gov.au", StringComparison.OrdinalIgnoreCase),
IsVicSmartDeployment = VicSmart.IsVicSmartDeployment(),
DnsSrvRecordName = WindowsDeviceEnrolment.GetDnsServiceLocationRecordName(),
DnsSrvRecordValue = srvValue,
LegacyDiscoveryEnabled = !Database.DiscoConfiguration.Devices.EnrollmentLegacyDiscoveryDisabled,
};
// UI Extensions
@@ -1,7 +1,9 @@
using Disco.Models.UI.Config.JobPreferences;
using Disco.Services.Authorization;
using Disco.Services.Jobs.JobQueues;
using Disco.Services.Plugins.Features.UIExtension;
using Disco.Services.Web;
using System.Linq;
using System.Web.Mvc;
namespace Disco.Web.Areas.Config.Controllers
@@ -23,6 +25,9 @@ namespace Disco.Web.Areas.Config.Controllers
OnCreateExpression = Database.DiscoConfiguration.JobPreferences.OnCreateExpression,
OnDeviceReadyForReturnExpression = Database.DiscoConfiguration.JobPreferences.OnDeviceReadyForReturnExpression,
OnCloseExpression = Database.DiscoConfiguration.JobPreferences.OnCloseExpression,
DeviceProfiles = Database.DeviceProfiles.OrderBy(dp => dp.Name).ToList(),
OrganisationAddresses = Database.DiscoConfiguration.OrganisationAddresses.Addresses.OrderBy(a => a.Name).ToList(),
JobQueues = JobQueueService.GetQueues().Select(q => q.JobQueue).OrderBy(q => q.Name).ToList(),
};
// UI Extensions
@@ -30,7 +30,9 @@ namespace Disco.Web.Areas.Config.Controllers
DeploymentId = Guid.Parse(Database.DiscoConfiguration.DeploymentId),
CorrelationId = Guid.NewGuid(),
UserId = CurrentUser.UserId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
};
model.Proof = service.CalculateCallbackProof(model.CorrelationId, model.UserId, model.Timestamp);
return View(model);
}
@@ -1,4 +1,5 @@
using Disco.Models.UI.Config.Enrolment;
using System;
namespace Disco.Web.Areas.Config.Models.Enrolment
{
@@ -6,5 +7,12 @@ namespace Disco.Web.Areas.Config.Models.Enrolment
{
public string MacSshUsername { get; set; }
public int PendingTimeoutMinutes { get; set; }
public Uri MacEnrolUrl { get; set; }
public bool HostingPluginInstalled { get; set; }
public bool IsVicSmartDeployment { get; set; }
public bool IsServicesEducationVicGovAuDomain { get; set; }
public string DnsSrvRecordName { get; set; }
public string DnsSrvRecordValue { get; set; }
public bool LegacyDiscoveryEnabled { get; set; }
}
}
@@ -1,5 +1,4 @@
using Disco.Data.Repository;
using Disco.Models.Services.Jobs;
using Disco.Models.Services.Jobs;
using Disco.Models.UI.Config.JobPreferences;
using Disco.Services.Extensions;
using System;
@@ -32,21 +31,9 @@ namespace Disco.Web.Areas.Config.Models.JobPreferences
return UIHelpers.NoticeboardThemes.ToList();
}
public Lazy<List<Disco.Models.Repository.DeviceProfile>> DeviceProfiles = new Lazy<List<Disco.Models.Repository.DeviceProfile>>(() =>
{
using (var database = new DiscoDataContext())
{
return database.DeviceProfiles.OrderBy(a => a.Description).ToList();
}
});
public Lazy<List<Disco.Models.BI.Config.OrganisationAddress>> OrganisationAddresses = new Lazy<List<Disco.Models.BI.Config.OrganisationAddress>>(() =>
{
using (var database = new DiscoDataContext())
{
return database.DiscoConfiguration.OrganisationAddresses.Addresses.OrderBy(a => a.Name).ToList();
}
});
public List<Disco.Models.Repository.DeviceProfile> DeviceProfiles { get; set; }
public List<Disco.Models.BI.Config.OrganisationAddress> OrganisationAddresses { get; set; }
public List<Disco.Models.Repository.JobQueue> JobQueues { get; set; }
public List<KeyValuePair<int, string>> LongRunningJobDaysThresholdOptions()
{
@@ -4,9 +4,11 @@ namespace Disco.Web.Areas.Config.Models.SystemConfig
{
public class ActivateModel
{
public Uri CallbackUrl { get; set; }
public Guid DeploymentId { get; set; }
public Guid CorrelationId { get; set; }
public string UserId { get; set; }
public long Timestamp { get; set; }
public string Proof { get; set; }
public Uri CallbackUrl { get; set; }
}
}
@@ -620,7 +620,7 @@
</span>
</div>
<button id="changeOrganisationalUnit" type="button" class="button small">Change</button>@AjaxHelpers.AjaxLoader()
<div id="dialogOrganisationalUnit" title="Organisational Unit" class="dialog" data-url="@(Url.Action(MVC.API.System.DomainOrganisationalUnits()))">
<div id="dialogOrganisationalUnit" title="Organisational Unit" class="dialog" data-url="@(Url.Action(MVC.API.System.DomainOrganisationalUnitTree()))" data-urllazy="@(Url.Action(MVC.API.System.DomainOrganisationalUnits()))">
<div id="dialogOrganisationalUnit_Loading">
@AjaxHelpers.AjaxLoader() Loading Organisational Units
</div>
@@ -690,6 +690,7 @@
$enforceCheckbox = $('#enforceOrganisationalUnit');
const body = new FormData();
body.append('expandNode', ouValue);
body.append('__RequestVerificationToken', document.body.dataset.antiforgery);
const response = await fetch($dialog.attr('data-url'), {
method: 'POST',
@@ -698,17 +699,23 @@
const data = await response.json();
$loading.hide();
// Make 'Domains' unselectable
$.each(data, function (i, node) {
node.unselectable = true;
});
ouTree = $ouTree.fancytree({
source: data,
checkbox: false,
selectMode: 1,
keyboard: false,
fx: null
fx: null,
lazyload: function (event, data) {
data.result = {
url: $dialog.attr('data-urllazy'),
method: 'POST',
cache: false,
data: {
node: data.node.key,
'__RequestVerificationToken': document.body.dataset.antiforgery
}
};
}
}).fancytree('getTree');
ouTree.$container.css('position', 'relative');
@@ -2040,6 +2040,17 @@ WriteLiteral(" class=\"dialog\"");
WriteLiteral(" data-url=\"");
#line 623 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Url.Action(MVC.API.System.DomainOrganisationalUnitTree()));
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-urllazy=\"");
#line 623 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Url.Action(MVC.API.System.DomainOrganisationalUnits()));
@@ -2181,52 +2192,59 @@ WriteLiteral(">\r\n $(function () {\r\n
" = $(\'#treeOrganisationalUnit\');\r\n $dialog.cs" +
"s(\'overflow\', \'visible\');\r\n\r\n $enforceCheckbo" +
"x = $(\'#enforceOrganisationalUnit\');\r\n\r\n cons" +
"t body = new FormData();\r\n body.append(\'__Req" +
"uestVerificationToken\', document.body.dataset.antiforgery);\r\n " +
"t body = new FormData();\r\n body.append(\'expan" +
"dNode\', ouValue);\r\n body.append(\'__RequestVer" +
"ificationToken\', document.body.dataset.antiforgery);\r\n " +
" const response = await fetch($dialog.attr(\'data-url\'), {\r\n " +
" method: \'POST\',\r\n " +
" body: body\r\n });\r\n " +
" const data = await response.json();\r\n " +
" $loading.hide();\r\n\r\n // Make \'D" +
"omains\' unselectable\r\n $.each(data, function " +
"(i, node) {\r\n node.unselectable = true;\r\n" +
" });\r\n\r\n o" +
"uTree = $ouTree.fancytree({\r\n source: dat" +
"a,\r\n checkbox: false,\r\n " +
" selectMode: 1,\r\n ke" +
"yboard: false,\r\n fx: null\r\n " +
" $loading.hide();\r\n\r\n ouTree = $ouTree." +
"fancytree({\r\n source: data,\r\n " +
" checkbox: false,\r\n " +
" selectMode: 1,\r\n keyboard: false,\r\n" +
" fx: null,\r\n " +
" lazyload: function (event, data) {\r\n " +
" data.result = {\r\n url:" +
" $dialog.attr(\'data-urllazy\'),\r\n " +
"method: \'POST\',\r\n cache: false,\r\n" +
" data: {\r\n " +
" node: data.node.key,\r\n " +
" \'__RequestVerificationToken\': document.body.dataset.antif" +
"orgery\r\n }\r\n " +
" };\r\n }\r\n " +
" }).fancytree(\'getTree\');\r\n\r\n " +
" ouTree.$container.css(\'position\', \'relative\');\r\n\r\n " +
" // Set Buttons\r\n $dialog.dialog(\'" +
"option\', \'buttons\', {\r\n \'Use Default Comp" +
"uters Container\': function () {\r\n var" +
" $this = $(this);\r\n $this.css(\'overfl" +
"ow\', \'hidden\');\r\n $this.dialog(\"disab" +
"le\");\r\n $this.dialog(\"option\", \"butto" +
"ns\", null);\r\n ouSet(\'\');\r\n " +
" },\r\n \'Save\':" +
" function () {\r\n var node = ouTree.ge" +
"tActiveNode();\r\n if (node && node.key" +
".substr(0, 3).toLowerCase() == \'ou=\') {\r\n " +
" var $this = $(this);\r\n $" +
"this.css(\'overflow\', \'hidden\');\r\n " +
" $this.dialog(\"disable\");\r\n $this" +
".dialog(\"option\", \"buttons\", null);\r\n " +
" ouSet(node.key);\r\n } else {\r\n " +
" alert(\'Select an Organisational Uni" +
"t to Save\')\r\n }\r\n " +
" // Set Buttons\r\n $dialog.dial" +
"og(\'option\', \'buttons\', {\r\n \'Use Default " +
"Computers Container\': function () {\r\n " +
" var $this = $(this);\r\n $this.css(\'ov" +
"erflow\', \'hidden\');\r\n $this.dialog(\"d" +
"isable\");\r\n $this.dialog(\"option\", \"b" +
"uttons\", null);\r\n ouSet(\'\');\r\n " +
" },\r\n \'Sa" +
"ve\': function () {\r\n var node = ouTre" +
"e.getActiveNode();\r\n if (node && node" +
".key.substr(0, 3).toLowerCase() == \'ou=\') {\r\n " +
" var $this = $(this);\r\n " +
" $this.css(\'overflow\', \'hidden\');\r\n " +
" $this.dialog(\"disable\");\r\n $" +
"this.dialog(\"option\", \"buttons\", null);\r\n " +
" ouSet(node.key);\r\n } else {\r" +
"\n alert(\'Select an Organisational" +
" Unit to Save\')\r\n }\r\n " +
" }\r\n });\r\n\r\n " +
" // Expand\r\n expandAndFo" +
"cusNode(ouValue);\r\n\r\n ouTree.options.fx = { h" +
"eight: \"toggle\", duration: 200 };\r\n }\r\n " +
" // Expand\r\n expandA" +
"ndFocusNode(ouValue);\r\n\r\n ouTree.options.fx =" +
" { height: \"toggle\", duration: 200 };\r\n }\r\n " +
" $dialog.dialog(\'open\');\r\n\r\n " +
" $enforceCheckbox.prop(\'checked\', $(\'#DeviceProfile_EnforceOrganisationalUnit\')." +
"prop(\'checked\'));\r\n };\r\n\r\n " +
" $(\'#changeOrganisationalUnit\').click(ouChange);\r\n });\r\n " +
" </script>\r\n");
" $enforceCheckbox.prop(\'checked\', $(\'#DeviceProfile_EnforceOrganisationalUni" +
"t\').prop(\'checked\'));\r\n };\r\n\r\n " +
" $(\'#changeOrganisationalUnit\').click(ouChange);\r\n })" +
";\r\n </script>\r\n");
#line 752 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 759 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
else
{
@@ -2245,7 +2263,7 @@ WriteLiteral(">\r\n <span>\r\n");
WriteLiteral(" ");
#line 757 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 764 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Model.FriendlyOrganisationalUnitName);
@@ -2254,7 +2272,7 @@ WriteLiteral(" ");
WriteLiteral("\r\n </span>\r\n </div>\r\n");
#line 760 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 767 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -2263,7 +2281,7 @@ WriteLiteral("\r\n </span>\r\n </div>\
WriteLiteral(" ");
#line 761 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 768 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (!Model.OrganisationalUnitExists)
{
@@ -2286,7 +2304,7 @@ WriteLiteral("></i>The Organisational Unit could not be found.\r\n
" </div>\r\n");
#line 768 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 775 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -2299,13 +2317,13 @@ WriteLiteral(" style=\"margin-top: 8px;\"");
WriteLiteral(">\r\n");
#line 770 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 777 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 770 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 777 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig)
{
@@ -2321,7 +2339,7 @@ WriteLiteral(" type=\"checkbox\"");
WriteLiteral(" ");
#line 772 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 779 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Model.DeviceProfile.EnforceOrganisationalUnit ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty));
@@ -2341,7 +2359,7 @@ WriteLiteral(@">
'");
#line 778 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 785 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Url.Action(MVC.API.DeviceProfile.UpdateEnforceOrganisationalUnit(Model.DeviceProfile.Id)));
@@ -2352,7 +2370,7 @@ WriteLiteral("\',\r\n \'EnforceOrganisational
" </script>\r\n");
#line 783 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 790 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
else
{
@@ -2369,7 +2387,7 @@ WriteLiteral(" type=\"checkbox\"");
WriteLiteral(" ");
#line 786 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 793 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Model.DeviceProfile.EnforceOrganisationalUnit ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty));
@@ -2378,7 +2396,7 @@ WriteLiteral(" ");
WriteLiteral(" disabled=\"disabled\" />\r\n");
#line 787 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 794 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -2394,7 +2412,7 @@ WriteLiteral(">\r\n Enforce Organisational Unit\r\n
WriteLiteral(" ");
#line 791 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 798 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(AjaxHelpers.AjaxLoader());
@@ -2425,13 +2443,13 @@ WriteLiteral(@"></i>When an Active Directory account is provisioned it will be p
");
#line 805 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 812 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 805 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 812 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && (Model.CertificateProviders.Count > 0 || Model.CertificateAuthorityProviders.Count > 0))
{
@@ -2441,20 +2459,20 @@ WriteLiteral(@"></i>When an Active Directory account is provisioned it will be p
WriteLiteral(" <br />\r\n");
#line 808 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 815 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 808 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 815 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(AjaxHelpers.AjaxLoader("DeviceProfile_CertificateProviders"));
#line default
#line hidden
#line 808 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 815 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -2464,7 +2482,7 @@ WriteLiteral(" <br />\r\n");
WriteLiteral(" ");
#line 810 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 817 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.CertificateProviders.Count > 0)
{
@@ -2489,7 +2507,7 @@ WriteLiteral(@">
fetch('");
#line 823 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 830 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Url.Action(MVC.API.DeviceProfile.UpdateCertificateProviders(Model.DeviceProfile.Id)));
@@ -2515,7 +2533,7 @@ WriteLiteral(@"', {
");
#line 840 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 847 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -2524,7 +2542,7 @@ WriteLiteral(@"', {
WriteLiteral(" ");
#line 841 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 848 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.CertificateProviders.Count > 0)
{
@@ -2549,7 +2567,7 @@ WriteLiteral(@">
fetch('");
#line 854 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 861 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Url.Action(MVC.API.DeviceProfile.UpdateCertificateAuthorityProviders(Model.DeviceProfile.Id)));
@@ -2575,7 +2593,7 @@ WriteLiteral(@"', {
");
#line 871 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 878 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -2585,13 +2603,13 @@ WriteLiteral(" </th>\r\n <td>\r\n <h4>Devic
"tes</h4>\r\n");
#line 875 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 882 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 875 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 882 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.CertificateProviders.Count > 0)
{
@@ -2599,14 +2617,14 @@ WriteLiteral(" </th>\r\n <td>\r\n <h4>Devic
#line default
#line hidden
#line 877 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 884 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(CommonHelpers.CheckBoxList("DeviceProfile_CertificateProviders", "DeviceProfile_CertificateProviders", Model.CertificateProviders.ToSelectListItems(Model.DeviceProfile.GetCertificateProviders())));
#line default
#line hidden
#line 877 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 884 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
else
@@ -2626,7 +2644,7 @@ WriteLiteral(" class=\"smallMessage\"");
WriteLiteral(">&lt;None Allocated&gt;</span>\r\n");
#line 886 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 893 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
else
{
@@ -2637,13 +2655,13 @@ WriteLiteral(">&lt;None Allocated&gt;</span>\r\n");
WriteLiteral(" <ul>\r\n");
#line 890 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 897 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 890 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 897 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
foreach (var certificateProvider in certificateProviders)
{
@@ -2653,7 +2671,7 @@ WriteLiteral(" <ul>\r\n");
WriteLiteral(" <li>");
#line 892 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 899 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(certificateProvider.Name);
@@ -2662,7 +2680,7 @@ WriteLiteral(" <li>");
WriteLiteral("</li>\r\n");
#line 893 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 900 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -2671,7 +2689,7 @@ WriteLiteral("</li>\r\n");
WriteLiteral(" </ul>\r\n");
#line 895 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 902 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
}
@@ -2685,13 +2703,13 @@ WriteLiteral(" style=\"margin-top: 4px;\"");
WriteLiteral(">Authority Certificates</h4>\r\n");
#line 898 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 905 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 898 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 905 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.CertificateAuthorityProviders.Count > 0)
{
@@ -2699,14 +2717,14 @@ WriteLiteral(">Authority Certificates</h4>\r\n");
#line default
#line hidden
#line 900 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 907 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(CommonHelpers.CheckBoxList("DeviceProfile_CertificateAuthorityProviders", "DeviceProfile_CertificateAuthorityProviders", Model.CertificateAuthorityProviders.ToSelectListItems(Model.DeviceProfile.GetCertificateAuthorityProviders())));
#line default
#line hidden
#line 900 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 907 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
else
@@ -2726,7 +2744,7 @@ WriteLiteral(" class=\"smallMessage\"");
WriteLiteral(">&lt;None Allocated&gt;</span>\r\n");
#line 909 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 916 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
else
{
@@ -2737,13 +2755,13 @@ WriteLiteral(">&lt;None Allocated&gt;</span>\r\n");
WriteLiteral(" <ul>\r\n");
#line 913 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 920 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 913 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 920 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
foreach (var certificateProvider in certificateProviders)
{
@@ -2753,7 +2771,7 @@ WriteLiteral(" <ul>\r\n");
WriteLiteral(" <li>");
#line 915 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 922 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(certificateProvider.Name);
@@ -2762,7 +2780,7 @@ WriteLiteral(" <li>");
WriteLiteral("</li>\r\n");
#line 916 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 923 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -2771,7 +2789,7 @@ WriteLiteral("</li>\r\n");
WriteLiteral(" </ul>\r\n");
#line 918 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 925 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
}
@@ -2781,7 +2799,7 @@ WriteLiteral(" </ul>\r\n");
WriteLiteral(" ");
#line 920 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 927 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canViewPlugins)
{
@@ -2802,21 +2820,21 @@ WriteLiteral(" class=\"fa fa-info-circle\"");
WriteLiteral("></i>View the <a");
WriteAttribute("href", Tuple.Create(" href=\"", 53296), Tuple.Create("\"", 53346)
WriteAttribute("href", Tuple.Create(" href=\"", 53936), Tuple.Create("\"", 53986)
#line 924 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 53303), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.Plugins.Install())
#line 931 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 53943), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.Plugins.Install())
#line default
#line hidden
, 53303), false)
, 53943), false)
);
WriteLiteral(">Plugin Catalogue</a> to discover and install certificate provider plugins.\r\n " +
" </p>\r\n </div>\r\n");
#line 927 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 934 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -2826,13 +2844,13 @@ WriteLiteral(" </td>\r\n </tr>\r\n <tr>\r\n
" Provision Wireless Profiles:\r\n");
#line 933 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 940 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 933 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 940 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.WirelessProfileProviders.Count > 0)
{
@@ -2842,20 +2860,20 @@ WriteLiteral(" </td>\r\n </tr>\r\n <tr>\r\n
WriteLiteral(" <br />\r\n");
#line 936 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 943 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 936 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 943 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(AjaxHelpers.AjaxLoader("DeviceProfile_WirelessProfileProviders"));
#line default
#line hidden
#line 936 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 943 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
@@ -2879,7 +2897,7 @@ WriteLiteral(@">
fetch('");
#line 948 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 955 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Url.Action(MVC.API.DeviceProfile.UpdateWirelessProfileProviders(Model.DeviceProfile.Id)));
@@ -2905,7 +2923,7 @@ WriteLiteral(@"', {
");
#line 965 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 972 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -2914,13 +2932,13 @@ WriteLiteral(@"', {
WriteLiteral(" </th>\r\n <td>\r\n");
#line 968 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 975 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 968 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 975 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.WirelessProfileProviders.Count > 0)
{
@@ -2928,14 +2946,14 @@ WriteLiteral(" </th>\r\n <td>\r\n");
#line default
#line hidden
#line 970 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 977 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(CommonHelpers.CheckBoxList("DeviceProfile_WirelessProfileProviders", "DeviceProfile_WirelessProfileProviders", Model.WirelessProfileProviders.ToSelectListItems(Model.DeviceProfile.GetWirelessProfileProviders())));
#line default
#line hidden
#line 970 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 977 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
else
@@ -2955,7 +2973,7 @@ WriteLiteral(" class=\"smallMessage\"");
WriteLiteral(">&lt;None Allocated&gt;</span>\r\n");
#line 979 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 986 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
else
{
@@ -2966,13 +2984,13 @@ WriteLiteral(">&lt;None Allocated&gt;</span>\r\n");
WriteLiteral(" <ul>\r\n");
#line 983 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 990 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 983 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 990 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
foreach (var wirelessProfileProvider in wirelessProfileProviders)
{
@@ -2982,7 +3000,7 @@ WriteLiteral(" <ul>\r\n");
WriteLiteral(" <li>");
#line 985 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 992 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(wirelessProfileProvider.Name);
@@ -2991,7 +3009,7 @@ WriteLiteral(" <li>");
WriteLiteral("</li>\r\n");
#line 986 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 993 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -3000,7 +3018,7 @@ WriteLiteral("</li>\r\n");
WriteLiteral(" </ul>\r\n");
#line 988 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 995 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
}
@@ -3010,7 +3028,7 @@ WriteLiteral(" </ul>\r\n");
WriteLiteral(" ");
#line 990 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 997 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canViewPlugins)
{
@@ -3031,21 +3049,21 @@ WriteLiteral(" class=\"fa fa-info-circle\"");
WriteLiteral("></i>View the <a");
WriteAttribute("href", Tuple.Create(" href=\"", 56954), Tuple.Create("\"", 57004)
WriteAttribute("href", Tuple.Create(" href=\"", 57594), Tuple.Create("\"", 57644)
#line 994 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 56961), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.Plugins.Install())
#line 1001 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 57601), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.Plugins.Install())
#line default
#line hidden
, 56961), false)
, 57601), false)
);
WriteLiteral(">Plugin Catalogue</a> to discover and install wireless profile provider plugins.\r" +
"\n </p>\r\n </div>\r\n");
#line 997 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1004 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -3054,13 +3072,13 @@ WriteLiteral(">Plugin Catalogue</a> to discover and install wireless profile pro
WriteLiteral(" </td>\r\n </tr>\r\n");
#line 1000 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1007 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 1000 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1007 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (hideAdvanced)
{
@@ -3094,7 +3112,7 @@ WriteLiteral(@">Show Advanced Options</button>
");
#line 1016 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1023 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -3110,7 +3128,7 @@ WriteLiteral(">\r\n <th>\r\n Linked Groups:\r\n
WriteLiteral(" ");
#line 1023 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1030 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel()
{
CanConfigure = canConfig,
@@ -3128,7 +3146,7 @@ WriteLiteral("\r\n");
WriteLiteral(" ");
#line 1031 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1038 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel()
{
CanConfigure = canConfig,
@@ -3144,13 +3162,13 @@ WriteLiteral(" ");
WriteLiteral("\r\n");
#line 1039 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1046 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 1039 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1046 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig)
{
@@ -3158,14 +3176,14 @@ WriteLiteral("\r\n");
#line default
#line hidden
#line 1041 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1048 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupShared));
#line default
#line hidden
#line 1041 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1048 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -3175,7 +3193,7 @@ WriteLiteral("\r\n");
WriteLiteral(" </div>\r\n </td>\r\n </tr>\r\n </table>\r\n</div>\r\n");
#line 1048 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1055 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Html.Partial(MVC.Config.Shared.Views._DeviceGroupDocumentBulkGenerate, Model));
@@ -3188,13 +3206,13 @@ WriteLiteral(" class=\"actionBar\"");
WriteLiteral(">\r\n");
#line 1050 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1057 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 1050 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1057 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (Model.CanDecommission)
{
@@ -3220,13 +3238,13 @@ WriteLiteral(" title=\"Profile Device Decommission\"");
WriteLiteral(">\r\n");
#line 1054 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1061 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 1054 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1061 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
using (Html.BeginForm(MVC.API.Device.DeviceProfileDecommission(Model.DeviceProfile.Id)))
{
@@ -3234,14 +3252,14 @@ WriteLiteral(">\r\n");
#line default
#line hidden
#line 1056 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1063 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Html.AntiForgeryToken());
#line default
#line hidden
#line 1056 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1063 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
@@ -3266,13 +3284,13 @@ WriteLiteral(" class=\"none\"");
WriteLiteral(">\r\n");
#line 1062 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1069 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 1062 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1069 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
foreach (DecommissionReasons decommissionReason in Enum.GetValues(typeof(DecommissionReasons)).Cast<DecommissionReasons>().OrderBy(r => r.ToString()))
{
@@ -3283,33 +3301,33 @@ WriteLiteral(" <li>\r\n
WriteLiteral(" type=\"radio\"");
WriteAttribute("id", Tuple.Create(" id=\"", 60656), Tuple.Create("\"", 60728)
, Tuple.Create(Tuple.Create("", 60661), Tuple.Create("DeviceProfile_Decommission_Dialog_Reason_", 60661), true)
WriteAttribute("id", Tuple.Create(" id=\"", 61296), Tuple.Create("\"", 61368)
, Tuple.Create(Tuple.Create("", 61301), Tuple.Create("DeviceProfile_Decommission_Dialog_Reason_", 61301), true)
#line 1065 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 60702), Tuple.Create<System.Object, System.Int32>((int)decommissionReason
#line 1072 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 61342), Tuple.Create<System.Object, System.Int32>((int)decommissionReason
#line default
#line hidden
, 60702), false)
, 61342), false)
);
WriteLiteral("\r\n name=\"decommissionReason\"");
WriteAttribute("value", Tuple.Create(" value=\"", 60795), Tuple.Create("\"", 60829)
WriteAttribute("value", Tuple.Create(" value=\"", 61435), Tuple.Create("\"", 61469)
#line 1066 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 60803), Tuple.Create<System.Object, System.Int32>((int)decommissionReason
#line 1073 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 61443), Tuple.Create<System.Object, System.Int32>((int)decommissionReason
#line default
#line hidden
, 60803), false)
, 61443), false)
);
WriteLiteral(" ");
#line 1066 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1073 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write((decommissionReason == DecommissionReasons.EndOfLife) ? "checked=\"checked\"" : string.Empty);
@@ -3317,21 +3335,21 @@ WriteLiteral(" ");
#line hidden
WriteLiteral(" />\r\n <label");
WriteAttribute("for", Tuple.Create(" for=\"", 60969), Tuple.Create("\"", 61042)
, Tuple.Create(Tuple.Create("", 60975), Tuple.Create("DeviceProfile_Decommission_Dialog_Reason_", 60975), true)
WriteAttribute("for", Tuple.Create(" for=\"", 61609), Tuple.Create("\"", 61682)
, Tuple.Create(Tuple.Create("", 61615), Tuple.Create("DeviceProfile_Decommission_Dialog_Reason_", 61615), true)
#line 1067 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 61016), Tuple.Create<System.Object, System.Int32>((int)decommissionReason
#line 1074 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 61656), Tuple.Create<System.Object, System.Int32>((int)decommissionReason
#line default
#line hidden
, 61016), false)
, 61656), false)
);
WriteLiteral(">");
#line 1067 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1074 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(decommissionReason.ReasonMessage());
@@ -3340,7 +3358,7 @@ WriteLiteral(">");
WriteLiteral("</label>\r\n </li>\r\n");
#line 1069 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1076 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -3359,7 +3377,7 @@ WriteLiteral(" />\r\n Unassign devices users\r\n
"\r\n </div>\r\n");
#line 1077 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1084 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -3403,7 +3421,7 @@ WriteLiteral(@">
");
#line 1108 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1115 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -3412,7 +3430,7 @@ WriteLiteral(@">
WriteLiteral(" ");
#line 1109 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1116 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canDelete)
{
@@ -3440,13 +3458,13 @@ WriteLiteral(" title=\"Delete this Device Profile?\"");
WriteLiteral(">\r\n");
#line 1113 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1120 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default
#line hidden
#line 1113 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1120 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
using (Html.BeginForm(MVC.API.DeviceProfile.Delete(Model.DeviceProfile.Id, true)))
{
@@ -3454,14 +3472,14 @@ WriteLiteral(">\r\n");
#line default
#line hidden
#line 1115 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1122 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Html.AntiForgeryToken());
#line default
#line hidden
#line 1115 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1122 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -3509,7 +3527,7 @@ WriteLiteral(@">
");
#line 1149 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1156 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -3518,7 +3536,7 @@ WriteLiteral(@">
WriteLiteral(" ");
#line 1150 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1157 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (Authorization.Has(Claims.Device.Actions.Export))
{
@@ -3526,14 +3544,14 @@ WriteLiteral(" ");
#line default
#line hidden
#line 1152 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1159 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Html.ActionLinkButton("Export Devices", MVC.Device.Export(null, Disco.Models.Services.Devices.DeviceExportTypes.Profile, Model.DeviceProfile.Id)));
#line default
#line hidden
#line 1152 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1159 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -3543,7 +3561,7 @@ WriteLiteral(" ");
WriteLiteral(" ");
#line 1154 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1161 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (Authorization.Has(Claims.Device.Search) && Model.DeviceCount > 0)
{
@@ -3551,14 +3569,14 @@ WriteLiteral(" ");
#line default
#line hidden
#line 1156 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1163 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Html.ActionLinkButton(string.Format("View {0} Device{1}", Model.DeviceCount, (Model.DeviceCount != 1 ? "s" : null)), MVC.Search.Query(Model.DeviceProfile.Id.ToString(), "DeviceProfile")));
#line default
#line hidden
#line 1156 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line 1163 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
}
@@ -16,26 +16,25 @@
Model.TemplateExpressions.All(e => e.All(p => !p.ParseError) &&
!Model.OnImportUserFlagRules.Any());
#region Can Bulk Generate
var canBulkGenerate = Authorization.Has(Claims.Config.DocumentTemplate.BulkGenerate);
if (canBulkGenerate)
{
var canBulkGenerate = false;
var canBulkDownload = false;
switch (Model.DocumentTemplate.Scope)
{
case DocumentTemplate.DocumentTemplateScopes.Device:
canBulkGenerate = Authorization.Has(Claims.Device.Actions.GenerateDocuments);
canBulkGenerate = Authorization.Has(Claims.Config.DocumentTemplate.BulkGenerate) && Authorization.Has(Claims.Device.Actions.GenerateDocuments);
canBulkDownload = Authorization.Has(Claims.Device.ShowAttachments) && Model.StoredInstanceCount > 0;
break;
case DocumentTemplate.DocumentTemplateScopes.Job:
canBulkGenerate = Authorization.Has(Claims.Job.Actions.GenerateDocuments);
canBulkGenerate = Authorization.Has(Claims.Config.DocumentTemplate.BulkGenerate) && Authorization.Has(Claims.Job.Actions.GenerateDocuments);
canBulkDownload = Authorization.Has(Claims.Job.ShowAttachments) && Model.StoredInstanceCount > 0;
break;
case DocumentTemplate.DocumentTemplateScopes.User:
canBulkGenerate = Authorization.Has(Claims.User.Actions.GenerateDocuments);
canBulkGenerate = Authorization.Has(Claims.Config.DocumentTemplate.BulkGenerate) && Authorization.Has(Claims.User.Actions.GenerateDocuments);
canBulkDownload = Authorization.Has(Claims.User.ShowAttachments) && Model.StoredInstanceCount > 0;
break;
default:
throw new InvalidOperationException("Invalid DocumentType Scope");
}
}
#endregion
ViewBag.Title = Html.ToBreadcrumb("Configuration", MVC.Config.Config.Index(), "Document Templates", MVC.Config.DocumentTemplate.Index(null), Model.DocumentTemplate.Description);
@@ -1037,6 +1036,66 @@
{
@Html.ActionLinkButton("Export Instances", MVC.Config.DocumentTemplate.Export(Model.DocumentTemplate.Id, null))
}
@if (canBulkDownload)
{
<button id="dialogBulkDownloadButton" type="button" class="button">Download Instances</button>
<div id="dialogBulkDownload" class="dialog" title="Download Instances: @(Model.DocumentTemplate.Id)">
@using (Html.BeginForm(MVC.API.DocumentTemplate.BulkDownload(Model.DocumentTemplate.Id)))
{
@Html.AntiForgeryToken()
<h3>Scope</h3>
<ul class="none">
<li>
<label><input type="radio" name="latestOnly" value="True" checked /> Latest @Model.DocumentTemplate.Scope Attachment</label>
</li>
<li>
<label><input type="radio" name="latestOnly" value="False" /> All @Model.DocumentTemplate.Scope Attachments</label>
</li>
</ul>
<br />
<h3>Threshold</h3>
<div>
<label>Only On or After <input type="date" name="threshold" value="@DateTime.Now.ToString("yyyy")-01-01" /></label>
</div>
}
</div>
<script>
$(function () {
let dialog;
$('#dialogBulkDownloadButton').on('click', function () {
if (!dialog) {
dialog = $('#dialogBulkDownload').dialog({
resizable: false,
modal: true,
autoOpen: false,
width: 460,
buttons: {
Close: function () {
$(this).dialog("close");
},
"Download Instances": function () {
const $this = $(this);
const $form = $this.find('form');
$form.trigger('submit');
$form.find('input').prop('disabled', true);
$this.closest('.ui-dialog').find('.ui-dialog-buttonset button').prop('disabled', true).addClass('ui-state-disabled');
window.setTimeout(function () {
$this.dialog("close");
}, 1500);
}
}
});
}
dialog.dialog('open');
dialog.find('form').find('input').prop('disabled', false);
dialog.closest('.ui-dialog').find('.ui-dialog-buttonset button').prop('disabled', false).removeClass('ui-state-disabled');
return false;
});
});
</script>
}
@if (canBulkGenerate)
{
if (Model.DocumentTemplate.Scope == DocumentTemplate.DocumentTemplateScopes.User || Model.DocumentTemplate.Scope == DocumentTemplate.DocumentTemplateScopes.Device)
@@ -1045,7 +1104,7 @@
}
else
{
<a id="buttonBulkGenerate" href="#" class="button">Bulk Generate</a>
<button id="buttonBulkGenerate" type="button" class="button">Bulk Generate</button>
<div id="dialogBulkGenerate" class="dialog dialog-bulk-generate" title="Bulk Generate: @(Model.DocumentTemplate.Id)">
<div class="brief">
@switch (Model.DocumentTemplate.Scope)
File diff suppressed because it is too large Load Diff
@@ -224,7 +224,7 @@
<div>
Add all devices in the selected batch
</div>
})
}
</div>
@using (Html.BeginForm(MVC.API.DocumentTemplate.BulkGenerateAddDeviceBatch()))
{
@@ -1288,9 +1288,10 @@ WriteLiteral(" <div>\r\n Add all devices in th
#line 227 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
}
#line default
#line hidden
WriteLiteral(")\r\n </div>\r\n");
WriteLiteral(" </div>\r\n");
#line 229 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
@@ -1336,15 +1337,15 @@ WriteLiteral(">\r\n");
#line hidden
WriteLiteral(" <div");
WriteAttribute("class", Tuple.Create(" class=\"", 9870), Tuple.Create("\"", 9922)
, Tuple.Create(Tuple.Create("", 9878), Tuple.Create("item", 9878), true)
WriteAttribute("class", Tuple.Create(" class=\"", 9869), Tuple.Create("\"", 9921)
, Tuple.Create(Tuple.Create("", 9877), Tuple.Create("item", 9877), true)
#line 235 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
, Tuple.Create(Tuple.Create(" ", 9882), Tuple.Create<System.Object, System.Int32>(batch.Count == 0 ? "disabled" : null
, Tuple.Create(Tuple.Create(" ", 9881), Tuple.Create<System.Object, System.Int32>(batch.Count == 0 ? "disabled" : null
#line default
#line hidden
, 9883), false)
, 9882), false)
);
WriteLiteral(" data-id=\"");
@@ -1413,14 +1414,14 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"scope\"");
WriteAttribute("value", Tuple.Create(" value=\"", 10226), Tuple.Create("\"", 10240)
WriteAttribute("value", Tuple.Create(" value=\"", 10225), Tuple.Create("\"", 10239)
#line 240 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
, Tuple.Create(Tuple.Create("", 10234), Tuple.Create<System.Object, System.Int32>(scope
, Tuple.Create(Tuple.Create("", 10233), Tuple.Create<System.Object, System.Int32>(scope
#line default
#line hidden
, 10234), false)
, 10233), false)
);
WriteLiteral(" />\r\n");
@@ -1471,19 +1472,19 @@ WriteLiteral(" id=\"DocumentTemplate_BulkGenerate_Dialog_AddDocumentAttachment\"
WriteLiteral(" class=\"dialog dialog-bulk-generate\"");
WriteAttribute("title", Tuple.Create(" title=\"", 10476), Tuple.Create("\"", 10549)
WriteAttribute("title", Tuple.Create(" title=\"", 10475), Tuple.Create("\"", 10548)
#line 248 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
, Tuple.Create(Tuple.Create("", 10484), Tuple.Create<System.Object, System.Int32>(Model.DocumentTemplate.Description
, Tuple.Create(Tuple.Create("", 10483), Tuple.Create<System.Object, System.Int32>(Model.DocumentTemplate.Description
#line default
#line hidden
, 10484), false)
, Tuple.Create(Tuple.Create("", 10521), Tuple.Create(":", 10521), true)
, Tuple.Create(Tuple.Create(" ", 10522), Tuple.Create("Add", 10523), true)
, Tuple.Create(Tuple.Create(" ", 10526), Tuple.Create("by", 10527), true)
, Tuple.Create(Tuple.Create(" ", 10529), Tuple.Create("Document", 10530), true)
, Tuple.Create(Tuple.Create(" ", 10538), Tuple.Create("Attachment", 10539), true)
, 10483), false)
, Tuple.Create(Tuple.Create("", 10520), Tuple.Create(":", 10520), true)
, Tuple.Create(Tuple.Create(" ", 10521), Tuple.Create("Add", 10522), true)
, Tuple.Create(Tuple.Create(" ", 10525), Tuple.Create("by", 10526), true)
, Tuple.Create(Tuple.Create(" ", 10528), Tuple.Create("Document", 10529), true)
, Tuple.Create(Tuple.Create(" ", 10537), Tuple.Create("Attachment", 10538), true)
);
WriteLiteral(">\r\n <div");
@@ -1581,15 +1582,15 @@ WriteLiteral(">\r\n");
#line hidden
WriteLiteral(" <div");
WriteAttribute("class", Tuple.Create(" class=\"", 11364), Tuple.Create("\"", 11419)
, Tuple.Create(Tuple.Create("", 11372), Tuple.Create("item", 11372), true)
WriteAttribute("class", Tuple.Create(" class=\"", 11363), Tuple.Create("\"", 11418)
, Tuple.Create(Tuple.Create("", 11371), Tuple.Create("item", 11371), true)
#line 269 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
, Tuple.Create(Tuple.Create(" ", 11376), Tuple.Create<System.Object, System.Int32>(template.Count == 0 ? "disabled" : null
, Tuple.Create(Tuple.Create(" ", 11375), Tuple.Create<System.Object, System.Int32>(template.Count == 0 ? "disabled" : null
#line default
#line hidden
, 11377), false)
, 11376), false)
);
WriteLiteral(" data-id=\"");
@@ -1686,14 +1687,14 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"scope\"");
WriteAttribute("value", Tuple.Create(" value=\"", 12106), Tuple.Create("\"", 12120)
WriteAttribute("value", Tuple.Create(" value=\"", 12105), Tuple.Create("\"", 12119)
#line 278 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
, Tuple.Create(Tuple.Create("", 12114), Tuple.Create<System.Object, System.Int32>(scope
, Tuple.Create(Tuple.Create("", 12113), Tuple.Create<System.Object, System.Int32>(scope
#line default
#line hidden
, 12114), false)
, 12113), false)
);
WriteLiteral(" />\r\n");
@@ -1744,19 +1745,19 @@ WriteLiteral(" id=\"DocumentTemplate_BulkGenerate_Dialog_AddUserDetail\"");
WriteLiteral(" class=\"dialog dialog-bulk-generate\"");
WriteAttribute("title", Tuple.Create(" title=\"", 12342), Tuple.Create("\"", 12407)
WriteAttribute("title", Tuple.Create(" title=\"", 12341), Tuple.Create("\"", 12406)
#line 286 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
, Tuple.Create(Tuple.Create("", 12350), Tuple.Create<System.Object, System.Int32>(Model.DocumentTemplate.Description
, Tuple.Create(Tuple.Create("", 12349), Tuple.Create<System.Object, System.Int32>(Model.DocumentTemplate.Description
#line default
#line hidden
, 12350), false)
, Tuple.Create(Tuple.Create("", 12387), Tuple.Create(":", 12387), true)
, Tuple.Create(Tuple.Create(" ", 12388), Tuple.Create("Add", 12389), true)
, Tuple.Create(Tuple.Create(" ", 12392), Tuple.Create("by", 12393), true)
, Tuple.Create(Tuple.Create(" ", 12395), Tuple.Create("User", 12396), true)
, Tuple.Create(Tuple.Create(" ", 12400), Tuple.Create("Detail", 12401), true)
, 12349), false)
, Tuple.Create(Tuple.Create("", 12386), Tuple.Create(":", 12386), true)
, Tuple.Create(Tuple.Create(" ", 12387), Tuple.Create("Add", 12388), true)
, Tuple.Create(Tuple.Create(" ", 12391), Tuple.Create("by", 12392), true)
, Tuple.Create(Tuple.Create(" ", 12394), Tuple.Create("User", 12395), true)
, Tuple.Create(Tuple.Create(" ", 12399), Tuple.Create("Detail", 12400), true)
);
WriteLiteral(">\r\n <div");
@@ -1874,15 +1875,15 @@ WriteLiteral(">\r\n");
#line hidden
WriteLiteral(" <div");
WriteAttribute("class", Tuple.Create(" class=\"", 13278), Tuple.Create("\"", 13328)
, Tuple.Create(Tuple.Create("", 13286), Tuple.Create("item", 13286), true)
WriteAttribute("class", Tuple.Create(" class=\"", 13277), Tuple.Create("\"", 13327)
, Tuple.Create(Tuple.Create("", 13285), Tuple.Create("item", 13285), true)
#line 309 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
, Tuple.Create(Tuple.Create(" ", 13290), Tuple.Create<System.Object, System.Int32>(key.Count == 0 ? "disabled" : null
, Tuple.Create(Tuple.Create(" ", 13289), Tuple.Create<System.Object, System.Int32>(key.Count == 0 ? "disabled" : null
#line default
#line hidden
, 13291), false)
, 13290), false)
);
WriteLiteral(" data-id=\"");
@@ -1951,14 +1952,14 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"scope\"");
WriteAttribute("value", Tuple.Create(" value=\"", 13635), Tuple.Create("\"", 13649)
WriteAttribute("value", Tuple.Create(" value=\"", 13634), Tuple.Create("\"", 13648)
#line 314 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
, Tuple.Create(Tuple.Create("", 13643), Tuple.Create<System.Object, System.Int32>(scope
, Tuple.Create(Tuple.Create("", 13642), Tuple.Create<System.Object, System.Int32>(scope
#line default
#line hidden
, 13643), false)
, 13642), false)
);
WriteLiteral(" />\r\n");
@@ -121,7 +121,7 @@
able to connect to the requesting Apple Mac client via <a target="_blank" href="http://en.wikipedia.org/wiki/Secure_Shell">SSH</a>. Enter/Script the following command:
</span>
<div class="code">
curl&nbsp;<a target="_blank" href="http://disco:9292/Services/Client/Unauthenticated/MacSecureEnrol">http://disco:9292/Services/Client/Unauthenticated/MacSecureEnrol</a>
curl&nbsp;<a target="_blank" href="@Model.MacEnrolUrl">@Model.MacEnrolUrl</a>
</div>
<span class="smallText">This url will return a <a target="_blank" href="http://json.org/">JSON</a> response containing basic information about the enrolment.</span><br />
<span class="smallMessage">
@@ -133,6 +133,167 @@
</tr>
</table>
</div>
<div class="form" style="width: 530px; margin-top: 15px">
<h2>Bootstrapper Server Discovery</h2>
<table>
<tr>
<td>
<div>
The Disco ICT
@if (Authorization.Has(Claims.Config.Enrolment.DownloadBootstrapper))
{
@Html.ActionLink("Bootstrapper", MVC.Services.Client.Bootstrapper())
}
else
{
<text>Bootstrapper</text>
}
is used to enrol devices. It is strongly recommended that HTTPS be used for all communication.
the
The @Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install())
plugin can be used to automate deployment of HTTPS certificates.
</div>
<div>
The Bootstrapper discovers the server using the first successful method (in order):
</div>
<ol>
<li>
<h5>Manually Specified</h5>
<div>
The server url can be specified at the command line. The url must use HTTPS. For example:
</div>
<div class="code">Disco.ClientBootstrapper.exe https://@Request.Url.Authority</div>
</li>
<li>
<h5>DNS Service Location (SRV) Record</h5>
Expected Record Name: <strong><code>@Model.DnsSrvRecordName</code></strong>
@if (Model.IsServicesEducationVicGovAuDomain)
{
<div class="smallText">
This mechanism is not supported in the shared education.vic.gov.au domain and can be ignored.
</div>
}
else
{
if (Model.DnsSrvRecordValue == null)
{
<div class="info-box">
<span class="error">
No Service Location (SRV) record found.
</span>
@if (Request.IsSecureConnection)
{
<span>
Please create a DNS Service Location (SRV) record:
</span>
<table class="none">
<tr>
<th>Service:</th>
<td><code>_discoict</code></td>
</tr>
<tr>
<th>Protocol:</th>
<td><code>_tcp</code></td>
</tr>
<tr>
<th>Priority:</th>
<td><code>0</code></td>
</tr>
<tr>
<th>Weight:</th>
<td><code>0</code></td>
</tr>
<tr>
<th>Port:</th>
<td><code>@Request.Url.Port</code></td>
</tr>
<tr>
<th>Host offering this service:</th>
<td><code>@Request.Url.Host</code></td>
</tr>
</table>
}
else
{
<div>
Please configure and connect with HTTPS.
<span>
You can enable HTTPS automation using the
@Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install())
plugin.
</span>
</div>
}
</div>
}
else
{
<div>
Value: <strong><code>https://@Model.DnsSrvRecordValue</code></strong>
@if (Request.IsSecureConnection && !string.Equals(Model.DnsSrvRecordValue, Request.Url.Authority, StringComparison.OrdinalIgnoreCase))
{
<div class="info-box error">
<i class="fa fa-exclamation"></i> The Service Location (SRV) record does not match the way you are currently accessing the server: <code>@Request.Url.Authority</code>.
</div>
}
</div>
}
}
</li>
@if (Model.IsVicSmartDeployment)
{
<li>
<h5>Victorian Government Schools VicSmart Discovery</h5>
If the Bootstrapper detects it is running inside the VicSmart network, it will query Online Services for the Disco ICT server address based on the subnets assigned to each school.
This is configured in the @Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install())
plugin.
</li>
}
<li>
<h5>Legacy Discovery</h5>
<div>
The Bootstrapper will attempt to send an ICMP ping to &quot;<code>disco</code>&quot;. If the ping is successful, it will attempt to connect to <code>http://disco:9292/</code>.
</div>
<div>
@if (canConfig)
{
<input id="Enrolment_LegacyDiscovery" type="checkbox" @(Model.LegacyDiscoveryEnabled ? "checked" : null) />
<script type="text/javascript">
$(function () {
document.DiscoFunctions.PropertyChangeHelper(
$('#Enrolment_LegacyDiscovery'),
null,
'@Url.Action(MVC.API.Enrolment.LegacyDiscovery())',
'enabled'
);
});
</script>
}
else
{
<input id="Enrolment_LegacyDiscovery" type="checkbox" @(Model.LegacyDiscoveryEnabled ? "checked" : null) disabled="disabled" />
}
<label for="Enrolment_LegacyDiscovery">
Legacy Discovery Enabled
</label>
@AjaxHelpers.AjaxLoader()
</div>
@if ((Model.IsServicesEducationVicGovAuDomain || Model.DnsSrvRecordValue != null) && Model.LegacyDiscoveryEnabled)
{
<div class="info-box error">
<i class="fa fa-exclamation-triangle"></i>
It is not recommended to have Legacy Discovery enabled. Please use the latest Bootstrapper and disable this option.
</div>
}
<div>
This method is not secure and is only provided for backwards compatibility. In time this method will be removed.
</div>
</li>
</ol>
</td>
</tr>
</table>
</div>
@if (canShowStatus && Authorization.Has(Claims.Config.Logging.Show))
{
<h2>Live Enrolment Logging</h2>
@@ -451,10 +451,26 @@ WriteLiteral(">\r\n curl&nbsp;<a");
WriteLiteral(" target=\"_blank\"");
WriteLiteral(" href=\"http://disco:9292/Services/Client/Unauthenticated/MacSecureEnrol\"");
WriteAttribute("href", Tuple.Create(" href=\"", 4881), Tuple.Create("\"", 4906)
WriteLiteral(">http://disco:9292/Services/Client/Unauthenticated/MacSecureEnrol</a>\r\n " +
" </div>\r\n <span");
#line 124 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
, Tuple.Create(Tuple.Create("", 4888), Tuple.Create<System.Object, System.Int32>(Model.MacEnrolUrl
#line default
#line hidden
, 4888), false)
);
WriteLiteral(">");
#line 124 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Model.MacEnrolUrl);
#line default
#line hidden
WriteLiteral("</a>\r\n </div>\r\n <span");
WriteLiteral(" class=\"smallText\"");
@@ -486,10 +502,521 @@ WriteLiteral(" class=\"code\"");
WriteLiteral(">&lt;script&gt;</span>\r\n tag embedded on the organisation\'s in" +
"tranet.\r\n </span>\r\n </td>\r\n </tr>\r\n </table>" +
"\r\n</div>\r\n");
"\r\n</div>\r\n<div");
WriteLiteral(" class=\"form\"");
WriteLiteral(" style=\"width: 530px; margin-top: 15px\"");
WriteLiteral(">\r\n <h2>Bootstrapper Server Discovery</h2>\r\n <table>\r\n <tr>\r\n " +
" <td>\r\n <div>\r\n The Disco ICT\r\n");
#line 136 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 143 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 143 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Authorization.Has(Claims.Config.Enrolment.DownloadBootstrapper))
{
#line default
#line hidden
#line 145 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLink("Bootstrapper", MVC.Services.Client.Bootstrapper()));
#line default
#line hidden
#line 145 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
else
{
#line default
#line hidden
WriteLiteral(" ");
WriteLiteral("Bootstrapper");
WriteLiteral("\r\n");
#line 150 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" is used to enrol devices. It is strongly recommended that HTT" +
"PS be used for all communication.\r\n the\r\n " +
"The ");
#line 153 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install()));
#line default
#line hidden
WriteLiteral(@"
plugin can be used to automate deployment of HTTPS certificates.
</div>
<div>
The Bootstrapper discovers the server using the first successful method (in order):
</div>
<ol>
<li>
<h5>Manually Specified</h5>
<div>
The server url can be specified at the command line. The url must use HTTPS. For example:
</div>
<div");
WriteLiteral(" class=\"code\"");
WriteLiteral(">Disco.ClientBootstrapper.exe https://");
#line 165 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Request.Url.Authority);
#line default
#line hidden
WriteLiteral("</div>\r\n </li>\r\n <li>\r\n " +
" <h5>DNS Service Location (SRV) Record</h5>\r\n Expected" +
" Record Name: <strong><code>");
#line 169 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Model.DnsSrvRecordName);
#line default
#line hidden
WriteLiteral("</code></strong>\r\n");
#line 170 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 170 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Model.IsServicesEducationVicGovAuDomain)
{
#line default
#line hidden
WriteLiteral(" <div");
WriteLiteral(" class=\"smallText\"");
WriteLiteral(">\r\n This mechanism is not supported in the shared " +
"education.vic.gov.au domain and can be ignored.\r\n </d" +
"iv>\r\n");
#line 175 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
else
{
if (Model.DnsSrvRecordValue == null)
{
#line default
#line hidden
WriteLiteral(" <div");
WriteLiteral(" class=\"info-box\"");
WriteLiteral(">\r\n <span");
WriteLiteral(" class=\"error\"");
WriteLiteral(">\r\n No Service Location (SRV) record found" +
".\r\n </span>\r\n");
#line 184 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 184 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Request.IsSecureConnection)
{
#line default
#line hidden
WriteLiteral(" <span>\r\n " +
" Please create a DNS Service Location (SRV) record:\r\n " +
" </span>\r\n");
WriteLiteral(" <table");
WriteLiteral(" class=\"none\"");
WriteLiteral(@">
<tr>
<th>Service:</th>
<td><code>_discoict</code></td>
</tr>
<tr>
<th>Protocol:</th>
<td><code>_tcp</code></td>
</tr>
<tr>
<th>Priority:</th>
<td><code>0</code></td>
</tr>
<tr>
<th>Weight:</th>
<td><code>0</code></td>
</tr>
<tr>
<th>Port:</th>
<td><code>");
#line 208 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Request.Url.Port);
#line default
#line hidden
WriteLiteral(@"</code></td>
</tr>
<tr>
<th>Host offering this service:</th>
<td><code>");
#line 212 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Request.Url.Host);
#line default
#line hidden
WriteLiteral("</code></td>\r\n </tr>\r\n " +
" </table>\r\n");
#line 215 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
else
{
#line default
#line hidden
WriteLiteral(@" <div>
Please configure and connect with HTTPS.
<span>
You can enable HTTPS automation using the
");
WriteLiteral(" ");
#line 222 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install()));
#line default
#line hidden
WriteLiteral("\r\n plugin.\r\n " +
" </span>\r\n </div>\r\n");
#line 226 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" </div>\r\n");
#line 228 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
else
{
#line default
#line hidden
WriteLiteral(" <div>\r\n Value:" +
" <strong><code>https://");
#line 232 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Model.DnsSrvRecordValue);
#line default
#line hidden
WriteLiteral("</code></strong>\r\n");
#line 233 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 233 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Request.IsSecureConnection && !string.Equals(Model.DnsSrvRecordValue, Request.Url.Authority, StringComparison.OrdinalIgnoreCase))
{
#line default
#line hidden
WriteLiteral(" <div");
WriteLiteral(" class=\"info-box error\"");
WriteLiteral(">\r\n <i");
WriteLiteral(" class=\"fa fa-exclamation\"");
WriteLiteral("></i> The Service Location (SRV) record does not match the way you are currently " +
"accessing the server: <code>");
#line 236 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Request.Url.Authority);
#line default
#line hidden
WriteLiteral("</code>.\r\n </div>\r\n");
#line 238 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" </div>\r\n");
#line 240 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
}
#line default
#line hidden
WriteLiteral(" </li>\r\n");
#line 243 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 243 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Model.IsVicSmartDeployment)
{
#line default
#line hidden
WriteLiteral(@" <li>
<h5>Victorian Government Schools VicSmart Discovery</h5>
If the Bootstrapper detects it is running inside the VicSmart network, it will query Online Services for the Disco ICT server address based on the subnets assigned to each school.
This is configured in the ");
#line 248 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLink("Hosting", Model.HostingPluginInstalled ? MVC.Config.Plugins.Configure("Hosting") : MVC.Config.Plugins.Install()));
#line default
#line hidden
WriteLiteral("\r\n plugin.\r\n </li>\r\n");
#line 251 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(@" <li>
<h5>Legacy Discovery</h5>
<div>
The Bootstrapper will attempt to send an ICMP ping to &quot;<code>disco</code>&quot;. If the ping is successful, it will attempt to connect to <code>http://disco:9292/</code>.
</div>
<div>
");
#line 258 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 258 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (canConfig)
{
#line default
#line hidden
WriteLiteral(" <input");
WriteLiteral(" id=\"Enrolment_LegacyDiscovery\"");
WriteLiteral(" type=\"checkbox\"");
WriteLiteral(" ");
#line 260 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Model.LegacyDiscoveryEnabled ? "checked" : null);
#line default
#line hidden
WriteLiteral(" />\r\n");
WriteLiteral(" <script");
WriteLiteral(" type=\"text/javascript\"");
WriteLiteral(@">
$(function () {
document.DiscoFunctions.PropertyChangeHelper(
$('#Enrolment_LegacyDiscovery'),
null,
'");
#line 266 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Url.Action(MVC.API.Enrolment.LegacyDiscovery()));
#line default
#line hidden
WriteLiteral("\',\r\n \'enabled\'\r\n " +
" );\r\n });\r\n " +
" </script>\r\n");
#line 271 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
else
{
#line default
#line hidden
WriteLiteral(" <input");
WriteLiteral(" id=\"Enrolment_LegacyDiscovery\"");
WriteLiteral(" type=\"checkbox\"");
WriteLiteral(" ");
#line 274 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Model.LegacyDiscoveryEnabled ? "checked" : null);
#line default
#line hidden
WriteLiteral(" disabled=\"disabled\" />\r\n");
#line 275 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" <label");
WriteLiteral(" for=\"Enrolment_LegacyDiscovery\"");
WriteLiteral(">\r\n Legacy Discovery Enabled\r\n " +
" </label>\r\n");
WriteLiteral(" ");
#line 279 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(AjaxHelpers.AjaxLoader());
#line default
#line hidden
WriteLiteral("\r\n </div>\r\n");
#line 281 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 281 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if ((Model.IsServicesEducationVicGovAuDomain || Model.DnsSrvRecordValue != null) && Model.LegacyDiscoveryEnabled)
{
#line default
#line hidden
WriteLiteral(" <div");
WriteLiteral(" class=\"info-box error\"");
WriteLiteral(">\r\n <i");
WriteLiteral(" class=\"fa fa-exclamation-triangle\"");
WriteLiteral("></i>\r\n It is not recommended to have Legacy Disco" +
"very enabled. Please use the latest Bootstrapper and disable this option.\r\n " +
" </div>\r\n");
#line 287 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(@" <div>
This method is not secure and is only provided for backwards compatibility. In time this method will be removed.
</div>
</li>
</ol>
</td>
</tr>
</table>
</div>
");
#line 297 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (canShowStatus && Authorization.Has(Claims.Config.Logging.Show))
{
@@ -499,13 +1026,13 @@ WriteLiteral(">&lt;script&gt;</span>\r\n tag embedded on the
WriteLiteral(" <h2>Live Enrolment Logging</h2>\r\n");
#line 139 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 300 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 139 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 300 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.Partial(MVC.Config.Shared.Views.LogEvents, new Disco.Web.Areas.Config.Models.Shared.LogEventsModel()
{
IsLive = true,
@@ -519,7 +1046,7 @@ Write(Html.Partial(MVC.Config.Shared.Views.LogEvents, new Disco.Web.Areas.Config
#line default
#line hidden
#line 146 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 307 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
@@ -533,13 +1060,13 @@ WriteLiteral(" class=\"actionBar\"");
WriteLiteral(">\r\n");
#line 149 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 310 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default
#line hidden
#line 149 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 310 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (Authorization.Has(Claims.Config.Enrolment.DownloadBootstrapper))
{
@@ -547,14 +1074,14 @@ WriteLiteral(">\r\n");
#line default
#line hidden
#line 151 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 312 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLinkButton("Download Bootstrapper", MVC.Services.Client.Bootstrapper()));
#line default
#line hidden
#line 151 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 312 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
@@ -564,7 +1091,7 @@ WriteLiteral(">\r\n");
WriteLiteral(" ");
#line 153 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 314 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (canShowStatus)
{
@@ -572,14 +1099,14 @@ WriteLiteral(" ");
#line default
#line hidden
#line 155 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 316 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
Write(Html.ActionLinkButton("Enrolment Status", MVC.Config.Enrolment.Status()));
#line default
#line hidden
#line 155 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line 316 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
}
@@ -77,23 +77,12 @@ else
{
<div class="form" style="width: 450px; padding: 100px 0;">
<h2>No saved exports are configured</h2>
<div>
@if (Authorization.Has(Claims.Device.Actions.Export))
{
<a href="@Url.Action(MVC.Device.Export())" class="button small">Device Export</a>
}
@if (Authorization.Has(Claims.Job.Actions.Export))
{
<a href="@Url.Action(MVC.Job.Export())" class="button small">Job Export</a>
}
@if (Authorization.Has(Claims.Config.UserFlag.Export))
{
<a href="@Url.Action(MVC.Config.UserFlag.Export())" class="button small">User Flag Export</a>
}
@if (Authorization.Has(Claims.Config.DeviceFlag.Export))
{
<a href="@Url.Action(MVC.Config.DeviceFlag.Export())" class="button small">Device Flag Export</a>
}
<div class="info-box">
<p class="fa-p">
<i class="fa fa-fw fa-info-circle"></i>
Visit any export page (eg. <a href="@Url.Action(MVC.Device.Export())">Device Export</a>)
and click "Save Export" to create a saved export that can be scheduled or run on demand.
</p>
</div>
</div>
}
@@ -420,151 +420,39 @@ WriteLiteral(" class=\"form\"");
WriteLiteral(" style=\"width: 450px; padding: 100px 0;\"");
WriteLiteral(">\r\n <h2>No saved exports are configured</h2>\r\n <div>\r\n");
WriteLiteral(">\r\n <h2>No saved exports are configured</h2>\r\n <div");
WriteLiteral(" class=\"info-box\"");
#line 81 "..\..\Areas\Config\Views\Export\Index.cshtml"
WriteLiteral(">\r\n <p");
WriteLiteral(" class=\"fa-p\"");
#line default
#line hidden
WriteLiteral(">\r\n <i");
#line 81 "..\..\Areas\Config\Views\Export\Index.cshtml"
if (Authorization.Has(Claims.Device.Actions.Export))
{
WriteLiteral(" class=\"fa fa-fw fa-info-circle\"");
WriteLiteral("></i>\r\n Visit any export page (eg. <a");
#line default
#line hidden
WriteLiteral(" <a");
WriteAttribute("href", Tuple.Create(" href=\"", 3626), Tuple.Create("\"", 3665)
WriteAttribute("href", Tuple.Create(" href=\"", 3675), Tuple.Create("\"", 3714)
#line 83 "..\..\Areas\Config\Views\Export\Index.cshtml"
, Tuple.Create(Tuple.Create("", 3633), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Device.Export())
, Tuple.Create(Tuple.Create("", 3682), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Device.Export())
#line default
#line hidden
, 3633), false)
, 3682), false)
);
WriteLiteral(" class=\"button small\"");
WriteLiteral(">Device Export</a>\r\n");
#line 84 "..\..\Areas\Config\Views\Export\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" ");
#line 85 "..\..\Areas\Config\Views\Export\Index.cshtml"
if (Authorization.Has(Claims.Job.Actions.Export))
{
#line default
#line hidden
WriteLiteral(" <a");
WriteAttribute("href", Tuple.Create(" href=\"", 3819), Tuple.Create("\"", 3855)
#line 87 "..\..\Areas\Config\Views\Export\Index.cshtml"
, Tuple.Create(Tuple.Create("", 3826), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Job.Export())
#line default
#line hidden
, 3826), false)
);
WriteLiteral(" class=\"button small\"");
WriteLiteral(">Job Export</a>\r\n");
WriteLiteral(">Device Export</a>)\r\n and click \"Save Export\" to create a saved ex" +
"port that can be scheduled or run on demand.\r\n </p>\r\n </div>\r\n" +
" </div>\r\n");
#line 88 "..\..\Areas\Config\Views\Export\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" ");
#line 89 "..\..\Areas\Config\Views\Export\Index.cshtml"
if (Authorization.Has(Claims.Config.UserFlag.Export))
{
#line default
#line hidden
WriteLiteral(" <a");
WriteAttribute("href", Tuple.Create(" href=\"", 4010), Tuple.Create("\"", 4058)
#line 91 "..\..\Areas\Config\Views\Export\Index.cshtml"
, Tuple.Create(Tuple.Create("", 4017), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.UserFlag.Export())
#line default
#line hidden
, 4017), false)
);
WriteLiteral(" class=\"button small\"");
WriteLiteral(">User Flag Export</a>\r\n");
#line 92 "..\..\Areas\Config\Views\Export\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" ");
#line 93 "..\..\Areas\Config\Views\Export\Index.cshtml"
if (Authorization.Has(Claims.Config.DeviceFlag.Export))
{
#line default
#line hidden
WriteLiteral(" <a");
WriteAttribute("href", Tuple.Create(" href=\"", 4221), Tuple.Create("\"", 4271)
#line 95 "..\..\Areas\Config\Views\Export\Index.cshtml"
, Tuple.Create(Tuple.Create("", 4228), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.DeviceFlag.Export())
#line default
#line hidden
, 4228), false)
);
WriteLiteral(" class=\"button small\"");
WriteLiteral(">Device Flag Export</a>\r\n");
#line 96 "..\..\Areas\Config\Views\Export\Index.cshtml"
}
#line default
#line hidden
WriteLiteral(" </div>\r\n </div>\r\n");
#line 99 "..\..\Areas\Config\Views\Export\Index.cshtml"
}
#line default
#line hidden
}
@@ -93,8 +93,18 @@
<h3>Filter</h3>
<select id="Config_ReportPrefs_Builder_Filter">
<option value="">&lt;None&gt;</option>
@if (Model.DeviceProfiles.Any())
{
<option value="DeviceProfile">Device Profile</option>
}
@if (Model.OrganisationAddresses.Any())
{
<option value="DeviceAddress">Device Profile Address</option>
}
@if (Model.JobQueues.Any())
{
<option value="JobQueue">Job Queue</option>
}
</select>
<div class="options">
<div class="method">
@@ -103,17 +113,17 @@
</div>
<div class="filter-option filter-DeviceProfile">
<ul class="none">
@foreach (var deviceProfile in Model.DeviceProfiles.Value)
@foreach (var deviceProfile in Model.DeviceProfiles)
{
<li>
<input id="Config_ReportPrefs_Builder_DP_@(deviceProfile.Id)" type="checkbox" value="@deviceProfile.Id" /><label for="Config_ReportPrefs_Builder_DP_@(deviceProfile.Id)">@deviceProfile.Description</label>
<input id="Config_ReportPrefs_Builder_DP_@(deviceProfile.Id)" type="checkbox" value="@deviceProfile.Id" /><label for="Config_ReportPrefs_Builder_DP_@(deviceProfile.Id)">@deviceProfile.Name</label>
</li>
}
</ul>
</div>
<div class="filter-option filter-DeviceAddress">
<ul class="none">
@foreach (var address in Model.OrganisationAddresses.Value)
@foreach (var address in Model.OrganisationAddresses)
{
<li>
<input id="Config_ReportPrefs_Builder_OA_@(address.Id)" type="checkbox" value="@address.ShortName" /><label for="Config_ReportPrefs_Builder_OA_@(address.Id)">@address.Name (@address.ShortName)</label>
@@ -121,6 +131,16 @@
}
</ul>
</div>
<div class="filter-option filter-JobQueue">
<ul class="none">
@foreach (var queue in Model.JobQueues)
{
<li>
<input id="Config_ReportPrefs_Builder_JQ_@(queue.Id)" type="checkbox" value="@queue.Id" /><label for="Config_ReportPrefs_Builder_JQ_@(queue.Id)"><i class="fa fa-@(queue.Icon) d-@(queue.IconColour)"></i> @queue.Name</label>
</li>
}
</ul>
</div>
</div>
</div>
</form>
@@ -436,16 +436,82 @@ WriteLiteral(">\r\n <option");
WriteLiteral(" value=\"\"");
WriteLiteral(">&lt;None&gt;</option>\r\n <option");
WriteLiteral(">&lt;None&gt;</option>\r\n");
#line 96 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line default
#line hidden
#line 96 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
if (Model.DeviceProfiles.Any())
{
#line default
#line hidden
WriteLiteral(" <option");
WriteLiteral(" value=\"DeviceProfile\"");
WriteLiteral(">Device Profile</option>\r\n <option");
WriteLiteral(">Device Profile</option>\r\n");
#line 99 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
}
#line default
#line hidden
WriteLiteral(" ");
#line 100 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
if (Model.OrganisationAddresses.Any())
{
#line default
#line hidden
WriteLiteral(" <option");
WriteLiteral(" value=\"DeviceAddress\"");
WriteLiteral(">Device Profile Address</option>\r\n </select>\r\n " +
" <div");
WriteLiteral(">Device Profile Address</option>\r\n");
#line 103 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
}
#line default
#line hidden
WriteLiteral(" ");
#line 104 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
if (Model.JobQueues.Any())
{
#line default
#line hidden
WriteLiteral(" <option");
WriteLiteral(" value=\"JobQueue\"");
WriteLiteral(">Job Queue</option>\r\n");
#line 107 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
}
#line default
#line hidden
WriteLiteral(" </select>\r\n <div");
WriteLiteral(" class=\"options\"");
@@ -495,14 +561,14 @@ WriteLiteral(" class=\"none\"");
WriteLiteral(">\r\n");
#line 106 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line 116 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line default
#line hidden
#line 106 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
foreach (var deviceProfile in Model.DeviceProfiles.Value)
#line 116 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
foreach (var deviceProfile in Model.DeviceProfiles)
{
@@ -511,47 +577,47 @@ WriteLiteral(">\r\n");
WriteLiteral(" <li>\r\n " +
" <input");
WriteAttribute("id", Tuple.Create(" id=\"", 7313), Tuple.Create("\"", 7367)
, Tuple.Create(Tuple.Create("", 7318), Tuple.Create("Config_ReportPrefs_Builder_DP_", 7318), true)
WriteAttribute("id", Tuple.Create(" id=\"", 7806), Tuple.Create("\"", 7860)
, Tuple.Create(Tuple.Create("", 7811), Tuple.Create("Config_ReportPrefs_Builder_DP_", 7811), true)
#line 109 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 7348), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 7841), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id
#line default
#line hidden
, 7348), false)
, 7841), false)
);
WriteLiteral(" type=\"checkbox\"");
WriteAttribute("value", Tuple.Create(" value=\"", 7384), Tuple.Create("\"", 7409)
WriteAttribute("value", Tuple.Create(" value=\"", 7877), Tuple.Create("\"", 7902)
#line 109 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 7392), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 7885), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id
#line default
#line hidden
, 7392), false)
, 7885), false)
);
WriteLiteral(" /><label");
WriteAttribute("for", Tuple.Create(" for=\"", 7419), Tuple.Create("\"", 7474)
, Tuple.Create(Tuple.Create("", 7425), Tuple.Create("Config_ReportPrefs_Builder_DP_", 7425), true)
WriteAttribute("for", Tuple.Create(" for=\"", 7912), Tuple.Create("\"", 7967)
, Tuple.Create(Tuple.Create("", 7918), Tuple.Create("Config_ReportPrefs_Builder_DP_", 7918), true)
#line 109 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 7455), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 7948), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id
#line default
#line hidden
, 7455), false)
, 7948), false)
);
WriteLiteral(">");
#line 109 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
Write(deviceProfile.Description);
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
Write(deviceProfile.Name);
#line default
@@ -559,7 +625,7 @@ WriteLiteral(">");
WriteLiteral("</label>\r\n </li>\r\n");
#line 111 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line 121 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
}
@@ -577,14 +643,14 @@ WriteLiteral(" class=\"none\"");
WriteLiteral(">\r\n");
#line 116 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line 126 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line default
#line hidden
#line 116 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
foreach (var address in Model.OrganisationAddresses.Value)
#line 126 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
foreach (var address in Model.OrganisationAddresses)
{
@@ -593,46 +659,46 @@ WriteLiteral(">\r\n");
WriteLiteral(" <li>\r\n " +
" <input");
WriteAttribute("id", Tuple.Create(" id=\"", 8074), Tuple.Create("\"", 8122)
, Tuple.Create(Tuple.Create("", 8079), Tuple.Create("Config_ReportPrefs_Builder_OA_", 8079), true)
WriteAttribute("id", Tuple.Create(" id=\"", 8554), Tuple.Create("\"", 8602)
, Tuple.Create(Tuple.Create("", 8559), Tuple.Create("Config_ReportPrefs_Builder_OA_", 8559), true)
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 8109), Tuple.Create<System.Object, System.Int32>(address.Id
#line 129 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 8589), Tuple.Create<System.Object, System.Int32>(address.Id
#line default
#line hidden
, 8109), false)
, 8589), false)
);
WriteLiteral(" type=\"checkbox\"");
WriteAttribute("value", Tuple.Create(" value=\"", 8139), Tuple.Create("\"", 8165)
WriteAttribute("value", Tuple.Create(" value=\"", 8619), Tuple.Create("\"", 8645)
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 8147), Tuple.Create<System.Object, System.Int32>(address.ShortName
#line 129 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 8627), Tuple.Create<System.Object, System.Int32>(address.ShortName
#line default
#line hidden
, 8147), false)
, 8627), false)
);
WriteLiteral(" /><label");
WriteAttribute("for", Tuple.Create(" for=\"", 8175), Tuple.Create("\"", 8224)
, Tuple.Create(Tuple.Create("", 8181), Tuple.Create("Config_ReportPrefs_Builder_OA_", 8181), true)
WriteAttribute("for", Tuple.Create(" for=\"", 8655), Tuple.Create("\"", 8704)
, Tuple.Create(Tuple.Create("", 8661), Tuple.Create("Config_ReportPrefs_Builder_OA_", 8661), true)
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 8211), Tuple.Create<System.Object, System.Int32>(address.Id
#line 129 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 8691), Tuple.Create<System.Object, System.Int32>(address.Id
#line default
#line hidden
, 8211), false)
, 8691), false)
);
WriteLiteral(">");
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line 129 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
Write(address.Name);
@@ -641,7 +707,7 @@ WriteLiteral(">");
WriteLiteral(" (");
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line 129 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
Write(address.ShortName);
@@ -650,7 +716,111 @@ WriteLiteral(" (");
WriteLiteral(")</label>\r\n </li>\r\n");
#line 121 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line 131 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
}
#line default
#line hidden
WriteLiteral(" </ul>\r\n </div>" +
"\r\n <div");
WriteLiteral(" class=\"filter-option filter-JobQueue\"");
WriteLiteral(">\r\n <ul");
WriteLiteral(" class=\"none\"");
WriteLiteral(">\r\n");
#line 136 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line default
#line hidden
#line 136 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
foreach (var queue in Model.JobQueues)
{
#line default
#line hidden
WriteLiteral(" <li>\r\n " +
" <input");
WriteAttribute("id", Tuple.Create(" id=\"", 9287), Tuple.Create("\"", 9333)
, Tuple.Create(Tuple.Create("", 9292), Tuple.Create("Config_ReportPrefs_Builder_JQ_", 9292), true)
#line 139 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 9322), Tuple.Create<System.Object, System.Int32>(queue.Id
#line default
#line hidden
, 9322), false)
);
WriteLiteral(" type=\"checkbox\"");
WriteAttribute("value", Tuple.Create(" value=\"", 9350), Tuple.Create("\"", 9367)
#line 139 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 9358), Tuple.Create<System.Object, System.Int32>(queue.Id
#line default
#line hidden
, 9358), false)
);
WriteLiteral(" /><label");
WriteAttribute("for", Tuple.Create(" for=\"", 9377), Tuple.Create("\"", 9424)
, Tuple.Create(Tuple.Create("", 9383), Tuple.Create("Config_ReportPrefs_Builder_JQ_", 9383), true)
#line 139 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 9413), Tuple.Create<System.Object, System.Int32>(queue.Id
#line default
#line hidden
, 9413), false)
);
WriteLiteral("><i");
WriteAttribute("class", Tuple.Create(" class=\"", 9428), Tuple.Create("\"", 9477)
, Tuple.Create(Tuple.Create("", 9436), Tuple.Create("fa", 9436), true)
, Tuple.Create(Tuple.Create(" ", 9438), Tuple.Create("fa-", 9439), true)
#line 139 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 9442), Tuple.Create<System.Object, System.Int32>(queue.Icon
#line default
#line hidden
, 9442), false)
, Tuple.Create(Tuple.Create(" ", 9455), Tuple.Create("d-", 9456), true)
#line 139 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 9458), Tuple.Create<System.Object, System.Int32>(queue.IconColour
#line default
#line hidden
, 9458), false)
);
WriteLiteral("></i> ");
#line 139 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
Write(queue.Name);
#line default
#line hidden
WriteLiteral("</label>\r\n </li>\r\n");
#line 141 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
}
@@ -22,7 +22,7 @@
<div class="code" title="@group.Id">
<i class="fa fa-fw fa-lg fa-link success"></i>@group.Domain.FriendlyDistinguishedNamePath(group.DistinguishedName)
</div>
<button type="button" class="button small Config_LinkedGroup_LinkButton" data-linkedgroupid="@(Model.ManagedGroup.Configuration.GroupId)" data-linkedgroupfilterdateoption="@(Model.ManagedGroup.IncludeFilterBeginDate)" data-linkedgroupfilterdate="@(Model.ManagedGroup.Configuration.FilterBeginDate)" data-linkedroupdescription="@(Model.CategoryDescription)" data-linkedroupupdateurl="@(Model.UpdateUrl)">Change Link</button>
<button type="button" class="button small Config_LinkedGroup_LinkButton" data-linkedgroupid="@(Model.ManagedGroup.Configuration.GroupId)" data-linkedgroupfilterdateoption="@(Model.ManagedGroup.IncludeFilterBeginDate)" data-linkedgroupfilterdate="@(Model.ManagedGroup.Configuration.FilterBeginDate)" data-linkedgroupdescription="@(Model.CategoryDescription)" data-linkedgroupupdateurl="@(Model.UpdateUrl)" data-linkedgroupupdatedescription="@(Model.ManagedGroup.Configuration.UpdateDescription)">Change Link</button>
using (Html.BeginForm(MVC.API.System.SyncActiveDirectoryManagedGroup(Model.ManagedGroup.Key, Context.Request.Path)))
{
@Html.AntiForgeryToken()
@@ -34,12 +34,12 @@
<div class="code error">
<i class="fa fa-fw fa-lg fa-unlink error"></i>Group Not Found: <strong class="code">@Model.ManagedGroup.Configuration.GroupId</strong>
</div>
<button type="button" class="button small Config_LinkedGroup_LinkButton" data-linkedgroupid="@(Model.ManagedGroup.Configuration.GroupId)" data-linkedgroupfilterdateoption="@(Model.ManagedGroup.IncludeFilterBeginDate)" data-linkedgroupfilterdate="@(Model.ManagedGroup.Configuration.FilterBeginDate)" data-linkedroupdescription="@(Model.CategoryDescription)" data-linkedroupupdateurl="@(Model.UpdateUrl)">Change Link</button>
<button type="button" class="button small Config_LinkedGroup_LinkButton" data-linkedgroupid="@(Model.ManagedGroup.Configuration.GroupId)" data-linkedgroupfilterdateoption="@(Model.ManagedGroup.IncludeFilterBeginDate)" data-linkedgroupfilterdate="@(Model.ManagedGroup.Configuration.FilterBeginDate)" data-linkedgroupdescription="@(Model.CategoryDescription)" data-linkedgroupupdateurl="@(Model.UpdateUrl)" data-linkedgroupupdatedescription="@(Model.ManagedGroup.Configuration.UpdateDescription)">Change Link</button>
}
}
else
{
<button type="button" class="button small Config_LinkedGroup_LinkButton" data-linkedgroupid="" data-linkedgroupfilterdateoption="@(Model.IncludeFilterBeginDate)" data-linkedroupdescription="@(Model.CategoryDescription)" data-linkedroupupdateurl="@(Model.UpdateUrl)">Link Group</button>
<button type="button" class="button small Config_LinkedGroup_LinkButton" data-linkedgroupid="" data-linkedgroupfilterdateoption="@(Model.IncludeFilterBeginDate)" data-linkedgroupdescription="@(Model.CategoryDescription)" data-linkedgroupupdateurl="@(Model.UpdateUrl)" data-linkedgroupupdatedescription="True">Link Group</button>
}
}
else
@@ -177,7 +177,7 @@ WriteLiteral(" data-linkedgroupfilterdate=\"");
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedroupdescription=\"");
WriteLiteral(" data-linkedgroupdescription=\"");
#line 25 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
@@ -188,7 +188,7 @@ WriteLiteral(" data-linkedroupdescription=\"");
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedroupupdateurl=\"");
WriteLiteral(" data-linkedgroupupdateurl=\"");
#line 25 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
@@ -199,6 +199,17 @@ WriteLiteral(" data-linkedroupupdateurl=\"");
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedgroupupdatedescription=\"");
#line 25 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
Write(Model.ManagedGroup.Configuration.UpdateDescription);
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(">Change Link</button>\r\n");
@@ -303,7 +314,7 @@ WriteLiteral(" data-linkedgroupfilterdate=\"");
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedroupdescription=\"");
WriteLiteral(" data-linkedgroupdescription=\"");
#line 37 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
@@ -314,7 +325,7 @@ WriteLiteral(" data-linkedroupdescription=\"");
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedroupupdateurl=\"");
WriteLiteral(" data-linkedgroupupdateurl=\"");
#line 37 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
@@ -325,6 +336,17 @@ WriteLiteral(" data-linkedroupupdateurl=\"");
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedgroupupdatedescription=\"");
#line 37 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
Write(Model.ManagedGroup.Configuration.UpdateDescription);
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(">Change Link</button>\r\n");
@@ -356,7 +378,7 @@ WriteLiteral(" data-linkedgroupfilterdateoption=\"");
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedroupdescription=\"");
WriteLiteral(" data-linkedgroupdescription=\"");
#line 42 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
@@ -367,7 +389,7 @@ WriteLiteral(" data-linkedroupdescription=\"");
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedroupupdateurl=\"");
WriteLiteral(" data-linkedgroupupdateurl=\"");
#line 42 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
@@ -378,6 +400,8 @@ WriteLiteral(" data-linkedroupupdateurl=\"");
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedgroupupdatedescription=\"True\"");
WriteLiteral(">Link Group</button>\r\n");
@@ -398,14 +422,14 @@ WriteLiteral(" <div");
WriteLiteral(" class=\"code\"");
WriteAttribute("title", Tuple.Create(" title=\"", 2966), Tuple.Create("\"", 2983)
WriteAttribute("title", Tuple.Create(" title=\"", 3193), Tuple.Create("\"", 3210)
#line 51 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
, Tuple.Create(Tuple.Create("", 2974), Tuple.Create<System.Object, System.Int32>(group.Id
, Tuple.Create(Tuple.Create("", 3201), Tuple.Create<System.Object, System.Int32>(group.Id
#line default
#line hidden
, 2974), false)
, 3201), false)
);
WriteLiteral(">\r\n <i");
@@ -12,7 +12,7 @@
<label for="Config_LinkedGroup_Id">Linked Group:</label>
</th>
<td>
<input id="Config_LinkedGroup_Id" type="text" name="GroupId" data-sourceurl="@(Url.Action(MVC.API.System.SearchGroupSubjects()))" />
<input id="Config_LinkedGroup_Id" type="text" name="groupId" data-sourceurl="@(Url.Action(MVC.API.System.SearchGroupSubjects()))" />
</td>
</tr>
<tr>
@@ -20,7 +20,15 @@
<label for="Config_LinkedGroup_FilterDate">Filter Date: </label>
</th>
<td>
<input id="Config_LinkedGroup_FilterDate" type="text" name="FilterBeginDate" placeholder="No Filter" autocomplete="off" />
<input id="Config_LinkedGroup_FilterDate" type="text" name="filterBeginDate" placeholder="No Filter" autocomplete="off" />
</td>
</tr>
<tr>
<th>&nbsp;</th>
<td>
<input id="Config_LinkedGroup_UpdateDescriptionOff" type="hidden" name="updateDescription" value="False" disabled />
<input id="Config_LinkedGroup_UpdateDescription" type="checkbox" name="updateDescription" value="True" checked />
<label for="Config_LinkedGroup_UpdateDescription">Update Group Description</label>
</td>
</tr>
</tbody>
@@ -30,6 +38,7 @@
<p class="fa-p">
<i class="fa fa-exclamation-circle"></i><strong>Warning:</strong> This group will be managed by Disco ICT.<br />
Any <strong>existing members will be removed from the group</strong>, and it will be automatically synchronized with related members.
If the 'Update Group Description' option is selected, the group's description will also be updated to reflect its managed status.
</p>
</div>
</div>
@@ -38,9 +47,10 @@
let dialog;
let dialogGroupId;
let dialogFilterDate;
let dialogUpdateDescription;
let dialogTitle;
function showDialog(groupId, filterDateOption, filterDateValue, updateUrl, title) {
function showDialog(groupId, filterDateOption, filterDateValue, updateUrl, title, updateDescription) {
if (dialog == null) {
dialog = $('#Config_LinkedGroup_Dialog').dialog({
width: 450,
@@ -57,6 +67,10 @@
dateFormat: 'yy/mm/dd'
});
dialogUpdateDescription = $('#Config_LinkedGroup_UpdateDescription').on('change', function () {
$('#Config_LinkedGroup_UpdateDescriptionOff').prop('disabled', $(this).prop('checked'));
});
dialogGroupId = $('#Config_LinkedGroup_Id');
dialogGroupId.focus(function () { $(this).select(); });
dialogGroupId.autocomplete({
@@ -109,6 +123,8 @@
dialogFilterDate.closest('tr').hide();
}
dialogUpdateDescription.prop('checked', updateDescription);
dialogTitle.text(title);
dialog.dialog('option', 'buttons', dialogButtons);
dialog.dialog('option', 'title', 'Linked Group: ' + title);
@@ -120,11 +136,12 @@
var configuredGroupId = $this.attr('data-linkedgroupid');
var configuredFilterBeginDate = $this.attr('data-linkedgroupfilterdate');
var filterDateOption = $this.attr('data-linkedgroupfilterdateoption') == 'True';
var description = $this.attr('data-linkedroupdescription');
var updateUrl = $this.attr('data-linkedroupupdateurl');
var filterDateOption = $this.attr('data-linkedgroupfilterdateoption') === 'True';
var description = $this.attr('data-linkedgroupdescription');
var updateUrl = $this.attr('data-linkedgroupupdateurl');
var updateDescription = $this.attr('data-linkedgroupupdatedescription') === 'True';
showDialog(configuredGroupId, filterDateOption, configuredFilterBeginDate, updateUrl, description);
showDialog(configuredGroupId, filterDateOption, configuredFilterBeginDate, updateUrl, description, updateDescription);
return false;
});
@@ -96,7 +96,7 @@ WriteLiteral(" id=\"Config_LinkedGroup_Id\"");
WriteLiteral(" type=\"text\"");
WriteLiteral(" name=\"GroupId\"");
WriteLiteral(" name=\"groupId\"");
WriteLiteral(" data-sourceurl=\"");
@@ -121,14 +121,40 @@ WriteLiteral(" id=\"Config_LinkedGroup_FilterDate\"");
WriteLiteral(" type=\"text\"");
WriteLiteral(" name=\"FilterBeginDate\"");
WriteLiteral(" name=\"filterBeginDate\"");
WriteLiteral(" placeholder=\"No Filter\"");
WriteLiteral(" autocomplete=\"off\"");
WriteLiteral(" />\r\n </td>\r\n </tr>\r\n </tbody>\r\n " +
" </table>\r\n </form>\r\n <div");
WriteLiteral(" />\r\n </td>\r\n </tr>\r\n <tr>\r\n " +
" <th>&nbsp;</th>\r\n <td>\r\n " +
" <input");
WriteLiteral(" id=\"Config_LinkedGroup_UpdateDescriptionOff\"");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"updateDescription\"");
WriteLiteral(" value=\"False\"");
WriteLiteral(" disabled />\r\n <input");
WriteLiteral(" id=\"Config_LinkedGroup_UpdateDescription\"");
WriteLiteral(" type=\"checkbox\"");
WriteLiteral(" name=\"updateDescription\"");
WriteLiteral(" value=\"True\"");
WriteLiteral(" checked />\r\n <label");
WriteLiteral(" for=\"Config_LinkedGroup_UpdateDescription\"");
WriteLiteral(">Update Group Description</label>\r\n </td>\r\n </t" +
"r>\r\n </tbody>\r\n </table>\r\n </form>\r\n <div");
WriteLiteral(" class=\"info-box error\"");
@@ -142,56 +168,64 @@ WriteLiteral(" class=\"fa fa-exclamation-circle\"");
WriteLiteral("></i><strong>Warning:</strong> This group will be managed by Disco ICT.<br />\r\n " +
" Any <strong>existing members will be removed from the group</strong>, " +
"and it will be automatically synchronized with related members.\r\n </p>\r\n " +
" </div>\r\n</div>\r\n<script>\r\n $(function () {\r\n let dialog;\r\n l" +
"et dialogGroupId;\r\n let dialogFilterDate;\r\n let dialogTitle;\r\n\r\n " +
" function showDialog(groupId, filterDateOption, filterDateValue, updateUrl," +
" title) {\r\n if (dialog == null) {\r\n dialog = $(\'#Confi" +
"g_LinkedGroup_Dialog\').dialog({\r\n width: 450,\r\n " +
" resizable: false,\r\n modal: true,\r\n a" +
"utoOpen: false\r\n });\r\n\r\n dialogFilterDate = $(\'#Co" +
"nfig_LinkedGroup_FilterDate\');\r\n dialogFilterDate.datetimepicker(" +
"{\r\n ampm: true,\r\n changeYear: true,\r\n " +
" changeMonth: true,\r\n dateFormat: \'yy/mm/dd\'\r\n " +
" });\r\n\r\n dialogGroupId = $(\'#Config_LinkedGroup_Id\'" +
");\r\n dialogGroupId.focus(function () { $(this).select(); });\r\n " +
" dialogGroupId.autocomplete({\r\n source: dialogGro" +
"upId.attr(\'data-sourceurl\'),\r\n minLength: 2,\r\n " +
" select: function (e, ui) {\r\n dialogGroupId.val(ui.it" +
"em.Id);\r\n return false;\r\n }\r\n " +
" }).data(\'ui-autocomplete\')._renderItem = function (ul, item) {\r\n " +
" return $(\"<li>\")\r\n .data(\"item.autocomplete\", " +
"item)\r\n .append(\"<a><strong>\" + item.Name + \"</strong><br" +
">\" + item.Id + \" (\" + item.Type + \")</a>\")\r\n .appendTo(ul" +
");\r\n };\r\n\r\n dialogTitle = $(\'#Config_LinkedGroup_T" +
"itle\');\r\n }\r\n\r\n var dialogButtons = {};\r\n if (!" +
"!groupId) {\r\n dialogButtons[\'Remove Link\'] = function () {\r\n " +
" $(this).dialog(\'disable\');\r\n dialogGroupId.val" +
"(\'\');\r\n dialogGroupId.closest(\'form\').attr(\'action\', updateUr" +
"l).submit();\r\n }\r\n }\r\n dialogButtons[(!!gro" +
"upId ? \'Save Changes\' : \'Link Group\')] = function () {\r\n if (!dia" +
"logGroupId.val()) {\r\n alert(\'A Linked Group must be specified" +
"\');\r\n return;\r\n }\r\n $(this).dia" +
"log(\'disable\');\r\n dialogGroupId.closest(\'form\').attr(\'action\', up" +
"dateUrl).submit();\r\n }\r\n dialogButtons[\'Cancel\'] = functio" +
"n () {\r\n $(this).dialog(\'close\');\r\n };\r\n\r\n " +
"dialogGroupId.val(groupId);\r\n\r\n if (!!filterDateOption) {\r\n " +
" if (!!filterDateValue) {\r\n dialogFilterDate.datetimepic" +
"ker(\'setDate\', moment(filterDateValue).toDate());\r\n } else {\r\n " +
" dialogFilterDate.val(\'\');\r\n }\r\n d" +
"ialogFilterDate.closest(\'tr\').show();\r\n } else {\r\n dia" +
"logFilterDate.closest(\'tr\').hide();\r\n }\r\n\r\n dialogTitle.te" +
"xt(title);\r\n dialog.dialog(\'option\', \'buttons\', dialogButtons);\r\n " +
" dialog.dialog(\'option\', \'title\', \'Linked Group: \' + title);\r\n " +
" dialog.dialog(\'open\');\r\n }\r\n\r\n $(document).on(\'click\', \'.Config_L" +
"inkedGroup_LinkButton\', function () {\r\n $this = $(this);\r\n\r\n " +
" var configuredGroupId = $this.attr(\'data-linkedgroupid\');\r\n var co" +
"nfiguredFilterBeginDate = $this.attr(\'data-linkedgroupfilterdate\');\r\n " +
" var filterDateOption = $this.attr(\'data-linkedgroupfilterdateoption\') == \'True\'" +
";\r\n var description = $this.attr(\'data-linkedroupdescription\');\r\n " +
" var updateUrl = $this.attr(\'data-linkedroupupdateurl\');\r\n\r\n s" +
"howDialog(configuredGroupId, filterDateOption, configuredFilterBeginDate, update" +
"Url, description);\r\n\r\n return false;\r\n });\r\n });\r\n</script>" +
"and it will be automatically synchronized with related members.\r\n If " +
"the \'Update Group Description\' option is selected, the group\'s description will " +
"also be updated to reflect its managed status.\r\n </p>\r\n </div>\r\n</div>" +
"\r\n<script>\r\n $(function () {\r\n let dialog;\r\n let dialogGroupId;" +
"\r\n let dialogFilterDate;\r\n let dialogUpdateDescription;\r\n l" +
"et dialogTitle;\r\n\r\n function showDialog(groupId, filterDateOption, filter" +
"DateValue, updateUrl, title, updateDescription) {\r\n if (dialog == nul" +
"l) {\r\n dialog = $(\'#Config_LinkedGroup_Dialog\').dialog({\r\n " +
" width: 450,\r\n resizable: false,\r\n " +
" modal: true,\r\n autoOpen: false\r\n });\r\n\r\n " +
" dialogFilterDate = $(\'#Config_LinkedGroup_FilterDate\');\r\n " +
" dialogFilterDate.datetimepicker({\r\n ampm: true,\r\n " +
" changeYear: true,\r\n changeMonth: true,\r\n " +
" dateFormat: \'yy/mm/dd\'\r\n });\r\n\r\n di" +
"alogUpdateDescription = $(\'#Config_LinkedGroup_UpdateDescription\').on(\'change\', " +
"function () {\r\n $(\'#Config_LinkedGroup_UpdateDescriptionOff\')" +
".prop(\'disabled\', $(this).prop(\'checked\'));\r\n });\r\n\r\n " +
" dialogGroupId = $(\'#Config_LinkedGroup_Id\');\r\n dialogGroupId." +
"focus(function () { $(this).select(); });\r\n dialogGroupId.autocom" +
"plete({\r\n source: dialogGroupId.attr(\'data-sourceurl\'),\r\n " +
" minLength: 2,\r\n select: function (e, ui) {\r\n " +
" dialogGroupId.val(ui.item.Id);\r\n r" +
"eturn false;\r\n }\r\n }).data(\'ui-autocomplete\')." +
"_renderItem = function (ul, item) {\r\n return $(\"<li>\")\r\n " +
" .data(\"item.autocomplete\", item)\r\n .ap" +
"pend(\"<a><strong>\" + item.Name + \"</strong><br>\" + item.Id + \" (\" + item.Type + " +
"\")</a>\")\r\n .appendTo(ul);\r\n };\r\n\r\n " +
" dialogTitle = $(\'#Config_LinkedGroup_Title\');\r\n }\r\n\r\n " +
" var dialogButtons = {};\r\n if (!!groupId) {\r\n dialo" +
"gButtons[\'Remove Link\'] = function () {\r\n $(this).dialog(\'dis" +
"able\');\r\n dialogGroupId.val(\'\');\r\n dialogG" +
"roupId.closest(\'form\').attr(\'action\', updateUrl).submit();\r\n }\r\n " +
" }\r\n dialogButtons[(!!groupId ? \'Save Changes\' : \'Link Grou" +
"p\')] = function () {\r\n if (!dialogGroupId.val()) {\r\n " +
" alert(\'A Linked Group must be specified\');\r\n return;\r\n" +
" }\r\n $(this).dialog(\'disable\');\r\n d" +
"ialogGroupId.closest(\'form\').attr(\'action\', updateUrl).submit();\r\n }\r" +
"\n dialogButtons[\'Cancel\'] = function () {\r\n $(this).di" +
"alog(\'close\');\r\n };\r\n\r\n dialogGroupId.val(groupId);\r\n\r\n " +
" if (!!filterDateOption) {\r\n if (!!filterDateValue) {\r\n " +
" dialogFilterDate.datetimepicker(\'setDate\', moment(filterDateVa" +
"lue).toDate());\r\n } else {\r\n dialogFilterDate." +
"val(\'\');\r\n }\r\n dialogFilterDate.closest(\'tr\').show" +
"();\r\n } else {\r\n dialogFilterDate.closest(\'tr\').hide()" +
";\r\n }\r\n\r\n dialogUpdateDescription.prop(\'checked\', updateDe" +
"scription);\r\n\r\n dialogTitle.text(title);\r\n dialog.dialog(\'" +
"option\', \'buttons\', dialogButtons);\r\n dialog.dialog(\'option\', \'title\'" +
", \'Linked Group: \' + title);\r\n dialog.dialog(\'open\');\r\n }\r\n\r\n " +
" $(document).on(\'click\', \'.Config_LinkedGroup_LinkButton\', function () {\r\n" +
" $this = $(this);\r\n\r\n var configuredGroupId = $this.attr(\'" +
"data-linkedgroupid\');\r\n var configuredFilterBeginDate = $this.attr(\'d" +
"ata-linkedgroupfilterdate\');\r\n var filterDateOption = $this.attr(\'dat" +
"a-linkedgroupfilterdateoption\') === \'True\';\r\n var description = $this" +
".attr(\'data-linkedgroupdescription\');\r\n var updateUrl = $this.attr(\'d" +
"ata-linkedgroupupdateurl\');\r\n var updateDescription = $this.attr(\'dat" +
"a-linkedgroupupdatedescription\') === \'True\';\r\n\r\n showDialog(configure" +
"dGroupId, filterDateOption, configuredFilterBeginDate, updateUrl, description, u" +
"pdateDescription);\r\n\r\n return false;\r\n });\r\n });\r\n</script>" +
"\r\n");
}
@@ -11,45 +11,23 @@
</div>
</div>
<iframe name="callbackFrame" class="hidden">
</iframe>
<form id="callbackSubmit" action="@Model.CallbackUrl" method="post" target="callbackFrame">
<input type="hidden" name="callbackUrl" value="@(new Uri(Request.Url, Url.Action(MVC.API.Activation.TestCallback())))" />
<input type="hidden" name="deploymentId" value="@Model.DeploymentId" />
<form id="callbackSubmit" action="@Model.CallbackUrl" method="post" data-failedurl="@Url.Action(MVC.Config.SystemConfig.Index())">
<input type="hidden" name="correlationId" value="@Model.CorrelationId" />
<input type="hidden" name="deploymentId" value="@Model.DeploymentId" />
<input type="hidden" name="timestamp" value="@Model.Timestamp" />
<input type="hidden" name="proof" value="@Model.Proof" />
<input type="hidden" name="userId" value="@Model.UserId" />
<input type="hidden" name="callbackUrl" value="@(new Uri(Request.Url, Url.Action(MVC.API.Activation.Begin())))" />
</form>
@using (Html.BeginForm(MVC.API.Activation.Begin(), FormMethod.Post, new { id = "activationBegin"}))
{
@Html.AntiForgeryToken()
}
<a id="callbackFailedUrl" href="@Url.Action(MVC.Config.SystemConfig.Index())" class="hidden"></a>
<script>
$(function () {
const callbackForm = $('#callbackSubmit');
const callbackFailedUrl = $('#callbackFailedUrl').attr('href');
const callbackFailedUrl = callbackForm.attr('data-failedurl');
const timeout = window.setTimeout(function () {
alert('A timeout occurred while communicating with Online Services. Please try a different device/browser or try again later.');
window.location.href = callbackFailedUrl;
}, 1000 * 35);
window.activateCallbackResponse = function (deploymentId, correlationId, userId) {
window.clearTimeout(timeout);
const originalDeploymentId = callbackForm.find('input[name="deploymentId"]').val();
const originalCorrelationId = callbackForm.find('input[name="correlationId"]').val();
const originalUserId = callbackForm.find('input[name="userId"]').val();
if (deploymentId !== originalDeploymentId || correlationId !== originalCorrelationId || userId !== originalUserId) {
alert('Invalid response when communicating with Online Services. Please try a different device/browser or try again later.');
window.location.href = callbackFailedUrl;
}
$('#activationBegin').trigger('submit');
};
}, 1000 * 18);
callbackForm.trigger('submit');
});
</script>
@@ -68,44 +68,47 @@ WriteLiteral(">\r\n <h2><i");
WriteLiteral(" class=\"fa fa-lg fa-cog fa-spin\"");
WriteLiteral("></i> Testing Connectivity to Disco ICT Online Services</h2>\r\n </div>\r\n</div>\r" +
"\n\r\n<iframe");
WriteLiteral(" name=\"callbackFrame\"");
WriteLiteral(" class=\"hidden\"");
WriteLiteral(">\r\n</iframe>\r\n\r\n<form");
"\n\r\n<form");
WriteLiteral(" id=\"callbackSubmit\"");
WriteAttribute("action", Tuple.Create(" action=\"", 563), Tuple.Create("\"", 590)
WriteAttribute("action", Tuple.Create(" action=\"", 504), Tuple.Create("\"", 531)
#line 17 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 572), Tuple.Create<System.Object, System.Int32>(Model.CallbackUrl
#line 14 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 513), Tuple.Create<System.Object, System.Int32>(Model.CallbackUrl
#line default
#line hidden
, 572), false)
, 513), false)
);
WriteLiteral(" method=\"post\"");
WriteLiteral(" target=\"callbackFrame\"");
WriteLiteral(" data-failedurl=\"");
#line 14 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
Write(Url.Action(MVC.Config.SystemConfig.Index()));
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(">\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"callbackUrl\"");
WriteLiteral(" name=\"correlationId\"");
WriteAttribute("value", Tuple.Create(" value=\"", 674), Tuple.Create("\"", 752)
WriteAttribute("value", Tuple.Create(" value=\"", 656), Tuple.Create("\"", 684)
#line 18 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 682), Tuple.Create<System.Object, System.Int32>(new Uri(Request.Url, Url.Action(MVC.API.Activation.TestCallback()))
#line 15 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 664), Tuple.Create<System.Object, System.Int32>(Model.CorrelationId
#line default
#line hidden
, 682), false)
, 664), false)
);
WriteLiteral(" />\r\n <input");
@@ -114,30 +117,46 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"deploymentId\"");
WriteAttribute("value", Tuple.Create(" value=\"", 802), Tuple.Create("\"", 829)
WriteAttribute("value", Tuple.Create(" value=\"", 734), Tuple.Create("\"", 761)
#line 19 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 810), Tuple.Create<System.Object, System.Int32>(Model.DeploymentId
#line 16 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 742), Tuple.Create<System.Object, System.Int32>(Model.DeploymentId
#line default
#line hidden
, 810), false)
, 742), false)
);
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"correlationId\"");
WriteLiteral(" name=\"timestamp\"");
WriteAttribute("value", Tuple.Create(" value=\"", 880), Tuple.Create("\"", 908)
WriteAttribute("value", Tuple.Create(" value=\"", 808), Tuple.Create("\"", 832)
#line 20 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 888), Tuple.Create<System.Object, System.Int32>(Model.CorrelationId
#line 17 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 816), Tuple.Create<System.Object, System.Int32>(Model.Timestamp
#line default
#line hidden
, 888), false)
, 816), false)
);
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"proof\"");
WriteAttribute("value", Tuple.Create(" value=\"", 875), Tuple.Create("\"", 895)
#line 18 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 883), Tuple.Create<System.Object, System.Int32>(Model.Proof
#line default
#line hidden
, 883), false)
);
WriteLiteral(" />\r\n <input");
@@ -146,81 +165,43 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"userId\"");
WriteAttribute("value", Tuple.Create(" value=\"", 952), Tuple.Create("\"", 973)
WriteAttribute("value", Tuple.Create(" value=\"", 939), Tuple.Create("\"", 960)
#line 21 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 960), Tuple.Create<System.Object, System.Int32>(Model.UserId
#line 19 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 947), Tuple.Create<System.Object, System.Int32>(Model.UserId
#line default
#line hidden
, 960), false)
, 947), false)
);
WriteLiteral(" />\r\n</form>\r\n\r\n");
WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\"");
#line 24 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
using (Html.BeginForm(MVC.API.Activation.Begin(), FormMethod.Post, new { id = "activationBegin"}))
{
WriteLiteral(" name=\"callbackUrl\"");
WriteAttribute("value", Tuple.Create(" value=\"", 1009), Tuple.Create("\"", 1080)
#line 20 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 1017), Tuple.Create<System.Object, System.Int32>(new Uri(Request.Url, Url.Action(MVC.API.Activation.Begin()))
#line default
#line hidden
#line 26 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
Write(Html.AntiForgeryToken());
#line default
#line hidden
#line 26 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
}
#line default
#line hidden
WriteLiteral("\r\n<a");
WriteLiteral(" id=\"callbackFailedUrl\"");
WriteAttribute("href", Tuple.Create(" href=\"", 1154), Tuple.Create("\"", 1205)
#line 29 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 1161), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.SystemConfig.Index())
#line default
#line hidden
, 1161), false)
, 1017), false)
);
WriteLiteral(" class=\"hidden\"");
WriteLiteral(@"></a>
WriteLiteral(@" />
</form>
<script>
$(function () {
const callbackForm = $('#callbackSubmit');
const callbackFailedUrl = $('#callbackFailedUrl').attr('href');
const callbackFailedUrl = callbackForm.attr('data-failedurl');
const timeout = window.setTimeout(function () {
alert('A timeout occurred while communicating with Online Services. Please try a different device/browser or try again later.');
window.location.href = callbackFailedUrl;
}, 1000 * 35);
window.activateCallbackResponse = function (deploymentId, correlationId, userId) {
window.clearTimeout(timeout);
const originalDeploymentId = callbackForm.find('input[name=""deploymentId""]').val();
const originalCorrelationId = callbackForm.find('input[name=""correlationId""]').val();
const originalUserId = callbackForm.find('input[name=""userId""]').val();
if (deploymentId !== originalDeploymentId || correlationId !== originalCorrelationId || userId !== originalUserId) {
alert('Invalid response when communicating with Online Services. Please try a different device/browser or try again later.');
window.location.href = callbackFailedUrl;
}
$('#activationBegin').trigger('submit');
};
}, 1000 * 18);
callbackForm.trigger('submit');
});
</script>
@@ -1,8 +1,8 @@
using Disco.Models.Repository;
using Disco.Data.Repository;
using Disco.Models.Repository;
using Disco.Services.Jobs.Noticeboards;
using Disco.Services.Web;
using Disco.Web.Areas.Public.Models.UserHeldDevices;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
@@ -10,30 +10,53 @@ namespace Disco.Web.Areas.Public.Controllers
{
public partial class HeldDevicesController : DatabaseController
{
public virtual ActionResult Index(List<int?> DeviceProfileInclude, List<int?> DeviceProfileExclude, List<string> DeviceAddressInclude, List<string> DeviceAddressExclude)
public virtual ActionResult Index(string DeviceProfileInclude, string DeviceProfileExclude, string DeviceAddressInclude, string DeviceAddressExclude, string JobQueueInclude, string JobQueueExclude)
{
IQueryable<Job> query = Database.Jobs;
if (DeviceProfileInclude != null)
query = query.Where(j => DeviceProfileInclude.Contains(j.Device.DeviceProfileId));
if (DeviceProfileExclude != null)
query = query.Where(j => !DeviceProfileExclude.Contains(j.Device.DeviceProfileId));
if (DeviceAddressInclude != null && DeviceAddressInclude.Count > 0)
{
var addressIds = Database.DiscoConfiguration.OrganisationAddresses.Addresses.Where(a => DeviceAddressInclude.Contains(a.ShortName)).Select(a => a.Id).ToList();
query = query.Where(j => addressIds.Contains(j.Device.DeviceProfile.DefaultOrganisationAddress));
}
if (DeviceAddressExclude != null && DeviceAddressExclude.Count > 0)
{
var addressIds = Database.DiscoConfiguration.OrganisationAddresses.Addresses.Where(a => DeviceAddressExclude.Contains(a.ShortName)).Select(a => (int?)a.Id).ToList();
query = query.Where(j => j.Device.DeviceProfile.DefaultOrganisationAddress == null || !addressIds.Contains(j.Device.DeviceProfile.DefaultOrganisationAddress));
}
var query = FilterJobs(Database.Jobs, Database, DeviceProfileInclude, DeviceProfileExclude, DeviceAddressInclude, DeviceAddressExclude, JobQueueInclude, JobQueueExclude);
var m = Disco.Services.Jobs.Noticeboards.HeldDevices.GetHeldDevices(query);
return View(m);
}
internal static IQueryable<Job> FilterJobs(IQueryable<Job> query, DiscoDataContext database, string deviceProfileInclude, string deviceProfileExclude, string deviceAddressInclude, string deviceAddressExclude, string jobQueueInclude, string jobQueueExclude)
{
if (!string.IsNullOrWhiteSpace(deviceProfileInclude))
{
var include = deviceProfileInclude.Split(',').Select(int.Parse).ToList();
query = query.Where(j => include.Contains(j.Device.DeviceProfileId));
}
if (!string.IsNullOrWhiteSpace(deviceProfileExclude))
{
var exclude = deviceProfileExclude.Split(',').Select(int.Parse).ToList();
query = query.Where(j => !exclude.Contains(j.Device.DeviceProfileId));
}
if (!string.IsNullOrWhiteSpace(deviceAddressInclude))
{
var include = deviceAddressInclude.Split(',');
var addressIds = database.DiscoConfiguration.OrganisationAddresses.Addresses.Where(a => include.Contains(a.ShortName)).Select(a => a.Id).ToList();
query = query.Where(j => addressIds.Contains(j.Device.DeviceProfile.DefaultOrganisationAddress));
}
if (!string.IsNullOrWhiteSpace(deviceAddressExclude))
{
var exclude = deviceAddressExclude.Split(',');
var addressIds = database.DiscoConfiguration.OrganisationAddresses.Addresses.Where(a => exclude.Contains(a.ShortName)).Select(a => (int?)a.Id).ToList();
query = query.Where(j => j.Device.DeviceProfile.DefaultOrganisationAddress == null || !addressIds.Contains(j.Device.DeviceProfile.DefaultOrganisationAddress));
}
if (jobQueueInclude != null)
{
var include = jobQueueInclude.Split(',').Select(int.Parse).ToList();
query = query.Where(j => j.JobQueues.Any(q => q.RemovedDate == null && include.Contains(q.JobQueueId)));
}
if (jobQueueExclude != null)
{
var exclude = jobQueueExclude.Split(',').Select(int.Parse).ToList();
query = query.Where(j => !j.JobQueues.Any(q => q.RemovedDate == null && exclude.Contains(q.JobQueueId)));
}
return query;
}
public virtual ActionResult ReadyForReturnXml()
{
var readyForReturn = Disco.Services.Jobs.Noticeboards.HeldDevices.GetHeldDevices(Database)
@@ -1,8 +1,6 @@
using Disco.Models.Repository;
using Disco.Services.Jobs.Noticeboards;
using Disco.Services.Jobs.Noticeboards;
using Disco.Services.Web;
using Disco.Web.Areas.Public.Models.UserHeldDevices;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
@@ -10,24 +8,9 @@ namespace Disco.Web.Areas.Public.Controllers
{
public partial class UserHeldDevicesController : DatabaseController
{
public virtual ActionResult Index(List<int?> DeviceProfileInclude, List<int?> DeviceProfileExclude, List<string> DeviceAddressInclude, List<string> DeviceAddressExclude)
public virtual ActionResult Index(string DeviceProfileInclude, string DeviceProfileExclude, string DeviceAddressInclude, string DeviceAddressExclude, string JobQueueInclude, string JobQueueExclude)
{
IQueryable<Job> query = Database.Jobs;
if (DeviceProfileInclude != null)
query = query.Where(j => DeviceProfileInclude.Contains(j.Device.DeviceProfileId));
if (DeviceProfileExclude != null)
query = query.Where(j => !DeviceProfileExclude.Contains(j.Device.DeviceProfileId));
if (DeviceAddressInclude != null && DeviceAddressInclude.Count > 0)
{
var addressIds = Database.DiscoConfiguration.OrganisationAddresses.Addresses.Where(a => DeviceAddressInclude.Contains(a.ShortName)).Select(a => a.Id).ToList();
query = query.Where(j => addressIds.Contains(j.Device.DeviceProfile.DefaultOrganisationAddress));
}
if (DeviceAddressExclude != null && DeviceAddressExclude.Count > 0)
{
var addressIds = Database.DiscoConfiguration.OrganisationAddresses.Addresses.Where(a => DeviceAddressExclude.Contains(a.ShortName)).Select(a => (int?)a.Id).ToList();
query = query.Where(j => j.Device.DeviceProfile.DefaultOrganisationAddress == null || !addressIds.Contains(j.Device.DeviceProfile.DefaultOrganisationAddress));
}
var query = HeldDevicesController.FilterJobs(Database.Jobs, Database, DeviceProfileInclude, DeviceProfileExclude, DeviceAddressInclude, DeviceAddressExclude, JobQueueInclude, JobQueueExclude);
var m = HeldDevicesForUsers.GetHeldDevicesForUsers(query);
@@ -14,7 +14,7 @@
{
<tr>
<td class="id">
@item.DeviceComputerNameFriendly
@item.DeviceName
</td>
<td class="description">
@if (item.UserId != null)

Some files were not shown because too many files have changed in this diff Show More