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" /> <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" /> <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly> </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> </assemblyBinding>
</runtime> </runtime>
</configuration> </configuration>
+1
View File
@@ -141,6 +141,7 @@
<Compile Include="Extensions\EnrolExtensions.cs" /> <Compile Include="Extensions\EnrolExtensions.cs" />
<Compile Include="Extensions\WhoAmIExtensions.cs" /> <Compile Include="Extensions\WhoAmIExtensions.cs" />
<Compile Include="Interop\Certificates.cs" /> <Compile Include="Interop\Certificates.cs" />
<Compile Include="Interop\EndpointDiscovery.cs" />
<Compile Include="Interop\Hardware.cs" /> <Compile Include="Interop\Hardware.cs" />
<Compile Include="Interop\LocalAuthentication.cs" /> <Compile Include="Interop\LocalAuthentication.cs" />
<Compile Include="Interop\Native\NetworkConnectionStatuses.cs" /> <Compile Include="Interop\Native\NetworkConnectionStatuses.cs" />
+8 -7
View File
@@ -10,19 +10,18 @@ namespace Disco.Client
{ {
public static class ErrorReporting public static class ErrorReporting
{ {
private const string ServicePathTemplate = "http://DISCO:9292/Services/Client/ClientError";
public static string DeviceIdentifier { get; set; } public static string DeviceIdentifier { get; set; }
public static string EnrolmentSessionId { 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() ErrorReport report = new ErrorReport()
{ {
DeviceIdentifier = DeviceIdentifier, DeviceIdentifier = DeviceIdentifier,
SessionId = EnrolmentSessionId, SessionId = EnrolmentSessionId,
JsonException = Ex.IntenseExceptionSerialization() JsonException = exception.IntenseExceptionSerialization()
}; };
try try
@@ -38,7 +37,7 @@ namespace Disco.Client
catch (Exception) { } catch (Exception) { }
// Don't log server errors back to the server // Don't log server errors back to the server
if (!isClientServiceException && ReportToServer) if (!isClientServiceException && reportToServer)
{ {
try try
{ {
@@ -49,7 +48,7 @@ namespace Disco.Client
try try
{ {
Presentation.WriteFatalError(Ex); Presentation.WriteFatalError(exception);
} }
catch (Exception) { } catch (Exception) { }
} }
@@ -85,7 +84,9 @@ namespace Disco.Client
string reportJson = JsonConvert.SerializeObject(report); string reportJson = JsonConvert.SerializeObject(report);
string reportResponse; 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.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}";
request.ContentType = "application/json"; request.ContentType = "application/json";
request.Method = WebRequestMethods.Http.Post; request.Method = WebRequestMethods.Http.Post;
@@ -1,30 +1,23 @@
using Disco.Models.ClientServices; using Disco.Models.ClientServices;
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Reflection; using System.Reflection;
namespace Disco.Client.Extensions namespace Disco.Client.Extensions
{ {
public static class ClientServicesExtensions internal static class ClientServicesExtensions
{ {
//#if DEBUG public static ResponseType Post<ResponseType>(this ServiceBase<ResponseType> service, bool authenticated)
// 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)
{ {
ResponseType serviceResponse; ResponseType serviceResponse;
string serviceUrl; Uri serviceUrl;
if (Authenticated) if (authenticated)
serviceUrl = string.Format(ServicePathAuthenticatedTemplate, Service.Feature); serviceUrl = new Uri(Program.ServerUrl, $"/Services/Client/Authenticated/{service.Feature}");
else else
serviceUrl = string.Format(ServicePathUnauthenticatedTemplate, Service.Feature); serviceUrl = new Uri(Program.ServerUrl, $"/Services/Client/Unauthenticated/{service.Feature}");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceUrl); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serviceUrl);
request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"; request.UserAgent = $"Disco-Client/{Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}";
@@ -39,7 +32,7 @@ namespace Disco.Client.Extensions
{ {
using (var jsonWriter = new JsonTextWriter(requestWriter)) 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 Disco.Models.ClientServices;
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Diagnostics; using System.Runtime.InteropServices;
using System.IO;
namespace Disco.Client.Extensions namespace Disco.Client.Extensions
{ {
public static class EnrolExtensions internal static class EnrolExtensions
{ {
public static void Build(this Enrol enrol) public static void Build(this Enrol enrol)
{ {
enrol.ComputerName = Environment.MachineName; enrol.ComputerName = Environment.MachineName;
@@ -62,6 +60,28 @@ namespace Disco.Client.Extensions
Program.AllowUninstall = enrolResponse.AllowBootstrapperUninstall; 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> /// <summary>
/// Processes a Client Service Enrol Response for Offline Domain Join Actions /// Processes a Client Service Enrol Response for Offline Domain Join Actions
/// </summary> /// </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); Presentation.UpdateStatus("Enrolling Device", $"Performing Offline Domain Join:\r\nRenaming Computer: {Environment.MachineName} -> {enrolResponse.ComputerName}", true, -1, 1500);
string odjFile = Path.GetTempFileName(); var provisionData = Convert.FromBase64String(enrolResponse.OfflineDomainJoinManifest);
File.WriteAllBytes(odjFile, Convert.FromBase64String(enrolResponse.OfflineDomainJoinManifest)); string systemRoot = Environment.GetEnvironmentVariable("SystemRoot");
string odjWindowsPath = Environment.GetEnvironmentVariable("SystemRoot"); var provisionDataPointer = Marshal.AllocCoTaskMem(provisionData.Length);
string odjProcessArguments = $"/REQUESTODJ /LOADFILE \"{odjFile}\" /WINDOWSPATH \"{odjWindowsPath}\" /LOCALOS"; Marshal.Copy(provisionData, 0, provisionDataPointer, provisionData.Length);
var joinResult = default(int);
ProcessStartInfo odjProcessStartInfo = new ProcessStartInfo("DJOIN.EXE", odjProcessArguments) try
{ {
CreateNoWindow = true, joinResult = NetRequestOfflineDomainJoin(provisionDataPointer, provisionData.Length, NETSETUP_PROVISION_FLAGS.NETSETUP_PROVISION_ONLINE_CALLER, systemRoot);
ErrorDialog = false, }
LoadUserProfile = true, finally
RedirectStandardOutput = true, {
UseShellExecute = false Marshal.FreeCoTaskMem(provisionDataPointer);
};
string odjResult;
using (Process odjProcess = System.Diagnostics.Process.Start(odjProcessStartInfo))
{
odjResult = odjProcess.StandardOutput.ReadToEnd();
odjProcess.WaitForExit(20000); // 20 Seconds
} }
Presentation.UpdateStatus("Enrolling Device", $"Offline Domain Join Result:\r\n{odjResult}", true, -1, 3000);
if (File.Exists(odjFile)) if (joinResult != 0)
File.Delete(odjFile); {
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 // 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)) 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.Extensions;
using Disco.Client.Interop; using Disco.Client.Interop;
using System; using System;
using System.Net;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@@ -26,7 +27,7 @@ namespace Disco.Client
} }
public static void UpdateStatus(string SubHeading, string Message, bool ShowProgress, int Progress) 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) public static void TryDelay(int Milliseconds)
{ {
@@ -38,6 +39,11 @@ namespace Disco.Client
{ {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
message.AppendLine($"Version: {Assembly.GetExecutingAssembly().GetName().Version.ToString(3)}"); 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})"); message.AppendLine($"Device: {Hardware.Information.SerialNumber} ({Hardware.Information.Manufacturer} {Hardware.Information.Model})");
Console.ForegroundColor = ConsoleColor.Yellow; Console.ForegroundColor = ConsoleColor.Yellow;
UpdateStatus("Preparation Client Started", message.ToString(), false, 0); UpdateStatus("Preparation Client Started", message.ToString(), false, 0);
@@ -48,12 +54,18 @@ namespace Disco.Client
{ {
Console.ForegroundColor = ConsoleColor.Magenta; Console.ForegroundColor = ConsoleColor.Magenta;
ClientServiceException clientServiceException = ex as ClientServiceException; if (ex is ClientServiceException clientServiceException)
if (clientServiceException != null)
{ {
UpdateStatus($"An error occurred during {clientServiceException.ServiceFeature}", UpdateStatus($"An error occurred during {clientServiceException.ServiceFeature}",
clientServiceException.Message, false, 0); 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 else
{ {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
+59 -6
View File
@@ -1,6 +1,8 @@
using Disco.Client.Extensions; using Disco.Client.Extensions;
using Disco.Client.Interop;
using Disco.Models.ClientServices; using Disco.Models.ClientServices;
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@@ -11,6 +13,9 @@ namespace Disco.Client
public static bool IsAuthenticated { get; set; } public static bool IsAuthenticated { get; set; }
public static bool RebootRequired { get; set; } public static bool RebootRequired { get; set; }
public static bool AllowUninstall { 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] [STAThread]
public static void Main(string[] args) public static void Main(string[] args)
@@ -24,12 +29,15 @@ namespace Disco.Client
{ {
Console.WriteLine("Waiting for Debugger to Attach"); Console.WriteLine("Waiting for Debugger to Attach");
System.Threading.Thread.Sleep(1000); System.Threading.Thread.Sleep(1000);
} while (!System.Diagnostics.Debugger.IsAttached); } while (!Debugger.IsAttached);
} }
#endif #endif
// Initialize Environment Settings // Initialize Environment Settings
SetupEnvironment(); SetupEnvironment(args);
if (ServerUrl == null)
keepProcessing = DiscoverDiscoIct();
// Report to Bootstrapper // Report to Bootstrapper
Presentation.WriteBanner(); Presentation.WriteBanner();
@@ -45,7 +53,7 @@ namespace Disco.Client
Presentation.WriteFooter(RebootRequired, AllowUninstall, !keepProcessing); Presentation.WriteFooter(RebootRequired, AllowUninstall, !keepProcessing);
} }
public static void SetupEnvironment() public static void SetupEnvironment(string[] args)
{ {
// Hookup Unhandled Error Handling // Hookup Unhandled Error Handling
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.CurrentDomain_UnhandledException; AppDomain.CurrentDomain.UnhandledException += ErrorReporting.CurrentDomain_UnhandledException;
@@ -54,21 +62,66 @@ namespace Disco.Client
WebRequest.DefaultWebProxy = new WebProxy(); WebRequest.DefaultWebProxy = new WebProxy();
// Override Http 100 Continue Behaviour // Override Http 100 Continue Behaviour
ServicePointManager.Expect100Continue = false; ServicePointManager.Expect100Continue = false;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
// Assume success unless otherwise notified // Assume success unless otherwise notified
AllowUninstall = true; 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 // Detect Disco.Bootstrapper - Create Enable UI Delay if Running
Presentation.DelayUI = false;
try 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) 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() public static bool WhoAmI()
{ {
try try
@@ -144,7 +197,7 @@ namespace Disco.Client
var secondsConsumed = (DateTimeOffset.Now - startTime).TotalSeconds; var secondsConsumed = (DateTimeOffset.Now - startTime).TotalSeconds;
var progress = (int)((secondsConsumed / totalSeconds) * 100); 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)); System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10));
} }
else else
+2 -2
View File
@@ -1,9 +1,9 @@
@ECHO OFF @ECHO OFF
IF /I "%USERDOMAIN%"=="NT AUTHORITY" GOTO RunAsNetworkService IF /I "%USERDOMAIN%"=="NT AUTHORITY" GOTO RunAsNetworkService
Disco.Client.exe Disco.Client.exe %1 %2 %3
EXIT /B 0 EXIT /B 0
:RunAsNetworkService :RunAsNetworkService
ECHO #Running,Launching Preparation Client, Please wait...{newline}Starting client as 'NT AUTHORITY\Network Service',true,-1 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 EXIT /B 0
+178 -189
View File
@@ -1,4 +1,6 @@
using System; using Disco.Client.Interop;
using Disco.ClientBootstrapper.Interop;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
@@ -6,46 +8,191 @@ using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper namespace Disco.ClientBootstrapper
{ {
class BootstrapperLoop internal class BootstrapperLoop
{ {
private readonly Func<CancellationToken, Task> completeCallback;
public Thread LoopThread; private readonly CancellationToken cancellationToken;
public delegate void LoopCompleteCallback(); private readonly IStatus statusUI;
private LoopCompleteCallback mLoopCompleteCallback; private readonly Uri forcedServerUrl;
private IStatus statusUI;
private string tempWorkingDirectory; private string tempWorkingDirectory;
private StringBuilder errorMessage;
private Process clientProcess; private Process clientProcess;
//#if DEBUG public BootstrapperLoop(IStatus statusUI, Uri forcedServerUrl, Func<CancellationToken, Task> callback, CancellationToken cancellationToken)
// 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)
{ {
statusUI = StatusUI; this.statusUI = statusUI;
mLoopCompleteCallback = Callback; this.forcedServerUrl = forcedServerUrl;
errorMessage = new StringBuilder(); completeCallback = callback;
this.cancellationToken = cancellationToken;
} }
public void Start() public void Start()
{ {
LoopThread = new Thread(new ThreadStart(loopHost)); Task.Factory.StartNew(async () =>
LoopThread.Start(); {
await Loop(forcedServerUrl, cancellationToken);
}, cancellationToken);
} }
private void loopHost() private async Task Loop(Uri forcedServerUrl, CancellationToken cancellationToken)
{ {
try try
{ {
loop(); statusUI.UpdateStatus("System Preparation (Bootstrapper)", "Starting", "Please wait...", true, -1);
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 (!NetworkInterop.HasNetworkConnectivity())
{
statusUI.UpdateStatus(null, "Detecting Network", "No network connectivity detected, Diagnosing...", true, -1);
statusUI_WriteAdapterInfo();
if (!NetworkInterop.HasNetworkConnectivity())
{
// Check for Wireless
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);
await NetworkInterop.ConfigureWireless(cancellationToken);
statusUI.UpdateStatus(null, "Waiting for Wireless Network", null, true, 0);
for (int i = 0; i < 30; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
await Program.SleepThread(2000, false, cancellationToken);
if (NetworkInterop.HasNetworkConnectivity())
break;
}
if (!NetworkInterop.HasNetworkConnectivity())
{
statusUI.UpdateStatus(null, "Wireless Network Failed", "Unable to connect to the wireless network, please connect the network cable...", false);
await Program.SleepThread(3000, false, cancellationToken);
}
}
if (!NetworkInterop.HasNetworkConnectivity())
{
// Instruct user to connect network cable
statusUI.UpdateStatus(null, "Please connect the network cable", null);
for (int i = 0; i < 30; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
await Program.SleepThread(2000, false, cancellationToken);
if (NetworkInterop.HasNetworkConnectivity())
break;
}
}
}
if (!NetworkInterop.HasNetworkConnectivity())
{
// Client Failed
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");
using (var webClient = new WebClient())
{
// Don't use a proxy when downloading the Client
webClient.Proxy = new WebProxy();
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
statusUI.UpdateStatus(null, "Extracting", "Retrieving Preparation Client, Please wait...", true, -1);
string clientLocation = Path.Combine(tempWorkingDirectory, "PreparationClient");
if (Directory.Exists(clientLocation))
Directory.Delete(clientLocation, true);
Directory.CreateDirectory(clientLocation);
using (var clientSource = Ionic.Zip.ZipFile.Read(clientSourceLocation))
{
clientSource.ExtractAll(clientLocation, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently);
}
// Launch Client
statusUI.UpdateStatus("System Preparation (Client)", "Running", "Launching Preparation Client, Please wait...", true, -1);
ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat"), $"2 {Process.GetCurrentProcess().Id} {serverDiscovery.Item1}")
{
WorkingDirectory = clientLocation,
CreateNoWindow = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
using (clientProcess = Process.Start(clientProcessStart))
{
// Read StdOutput until End
try
{
clientProcess.OutputDataReceived += new DataReceivedEventHandler(clientProcess_OutputDataReceived);
clientProcess.BeginOutputReadLine();
clientProcess.WaitForExit();
}
catch (Exception) { throw; }
finally
{
try { clientProcess.CloseMainWindow(); }
catch (Exception) { }
}
}
clientProcess = null;
// Cleanup
if (Directory.Exists(tempWorkingDirectory))
Directory.Delete(tempWorkingDirectory, true);
CertificateInterop.RemoveTempCerts();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -53,167 +200,20 @@ namespace Disco.ClientBootstrapper
return; return;
if (ex.GetType() == typeof(ThreadInterruptedException)) if (ex.GetType() == typeof(ThreadInterruptedException))
return; return;
Program.WriteAppError(ex); if (ex.GetType() == typeof(OperationCanceledException))
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");
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))
{
statusUI.UpdateStatus(null, "Detecting Network", "No network connectivity detected, Diagnosing...", true, -1);
statusUI_WriteAdapterInfo();
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
// Check for Wireless
var hasWireless = (Interop.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();
statusUI.UpdateStatus(null, "Waiting for Wireless Network", null, true, 0);
for (int i = 0; i < 100; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
Program.SleepThread(500, false);
if (Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
break;
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
statusUI.UpdateStatus(null, "Wireless Network Failed", "Unable to connect to the wireless network, please connect the network cable...", false);
Program.SleepThread(3000, false);
}
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
// Instruct user to connect network cable
statusUI.UpdateStatus(null, "Please connect the network cable", null);
for (int i = 0; i < 100; i++)
{
statusUI_WriteAdapterInfo();
statusUI.UpdateStatus(null, null, null, true, i);
Program.SleepThread(500, false);
if (Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
break;
}
}
}
if (!Interop.NetworkInterop.PingDiscoIct(DiscoServerName))
{
// Client Failed
if (mLoopCompleteCallback != null)
{
mLoopCompleteCallback.BeginInvoke(null, null);
}
return; return;
} Program.WriteAppError(ex);
} }
// Download Client
statusUI.UpdateStatus(null, "Downloading", "Retrieving Preparation Client, Please wait...", true, -1);
string clientSourceLocation = Path.Combine(tempWorkingDirectory, "PreparationClient.zip");
using (var webClient = new WebClient())
{
// Don't use a proxy when downloading the Client
webClient.Proxy = new WebProxy();
webClient.DownloadFile($"http://{DiscoServerName}:{DiscoServerPort}/Services/Client/PreparationClient", clientSourceLocation);
}
// Unzip Client
statusUI.UpdateStatus(null, "Extracting", "Retrieving Preparation Client, Please wait...", true, -1);
string clientLocation = Path.Combine(tempWorkingDirectory, "PreparationClient");
if (Directory.Exists(clientLocation))
Directory.Delete(clientLocation, true);
Directory.CreateDirectory(clientLocation);
using (var clientSource = Ionic.Zip.ZipFile.Read(clientSourceLocation))
{
clientSource.ExtractAll(clientLocation, Ionic.Zip.ExtractExistingFileAction.OverwriteSilently);
}
// Launch Client
statusUI.UpdateStatus("System Preparation (Client)", "Running", "Launching Preparation Client, Please wait...", true, -1);
ProcessStartInfo clientProcessStart = new ProcessStartInfo(Path.Combine(clientLocation, "Start.bat"))
{
WorkingDirectory = clientLocation,
CreateNoWindow = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
using (clientProcess = Process.Start(clientProcessStart))
{
// Read StdOutput until End
try
{
clientProcess.OutputDataReceived += new DataReceivedEventHandler(clientProcess_OutputDataReceived);
clientProcess.BeginOutputReadLine();
clientProcess.WaitForExit();
}
catch (Exception) { throw; }
finally
{
try { clientProcess.CloseMainWindow(); }
catch (Exception) { }
}
}
clientProcess = null;
// Cleanup
if (Directory.Exists(tempWorkingDirectory))
Directory.Delete(tempWorkingDirectory, true);
Interop.CertificateInterop.RemoveTempCerts();
// Pause if Error
if (errorMessage.Length > 0)
{
Program.SleepThread(10000, true);
}
// End Of Loop // End Of Loop
if (mLoopCompleteCallback != null) if (completeCallback != null)
{ await completeCallback(cancellationToken);
mLoopCompleteCallback.BeginInvoke(null, null);
}
} }
void statusUI_WriteAdapterInfo() private void statusUI_WriteAdapterInfo()
{ {
var info = new StringBuilder(); var info = new StringBuilder();
foreach (var na in Interop.NetworkInterop.NetworkAdapters) foreach (var na in NetworkInterop.NetworkAdapters)
{ {
if (na.IsWireless) 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)) if (!string.IsNullOrWhiteSpace(e.Data))
{ {
Debug.WriteLine($"OUTPUT: {e.Data}");
var data = e.Data.Substring(1).Split(new char[] { ',' }); var data = e.Data.Substring(1).Split(new char[] { ',' });
switch (e.Data[0]) 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" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Disco.Client\Interop\EndpointDiscovery.cs">
<Link>Interop\EndpointDiscovery.cs</Link>
</Compile>
<Compile Include="..\Resources\Libraries\DotNetZip\Source\BZip2\BitWriter.cs"> <Compile Include="..\Resources\Libraries\DotNetZip\Source\BZip2\BitWriter.cs">
<Link>DotNetZip\BZip2\BitWriter.cs</Link> <Link>DotNetZip\BZip2\BitWriter.cs</Link>
</Compile> </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 delegate void dUpdateStatus(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress);
private dUpdateStatus mUpdateStatus; private readonly dUpdateStatus mUpdateStatus;
public FormStatus() public FormStatus()
{ {
InitializeComponent(); InitializeComponent();
var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; 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); mUpdateStatus = new dUpdateStatus(UpdateStatusDo);
Cursor.Hide(); Cursor.Hide();
} }
void FormStatus_FormClosed(object sender, FormClosedEventArgs e) private void FormStatus_FormClosed(object sender, FormClosedEventArgs e)
{ {
Cursor.Show(); Cursor.Show();
Program.ExitApplication(); Program.ExitApplication();
@@ -32,43 +32,43 @@ namespace Disco.ClientBootstrapper
{ {
try try
{ {
this.Invoke(mUpdateStatus, Heading, SubHeading, Message, ShowProgress, Progress); Invoke(mUpdateStatus, Heading, SubHeading, Message, ShowProgress, Progress);
} }
catch (Exception) { } catch (Exception) { }
} }
private void UpdateStatusDo(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress) private void UpdateStatusDo(string Heading, string SubHeading, string Message, bool? ShowProgress, int? Progress)
{ {
if (Heading != null) if (Heading != null)
if (this.labelHeading.Text != Heading) if (labelHeading.Text != Heading)
this.labelHeading.Text = Heading; labelHeading.Text = Heading;
if (SubHeading != null) if (SubHeading != null)
if (this.labelSubHeading.Text != SubHeading) if (labelSubHeading.Text != SubHeading)
this.labelSubHeading.Text = SubHeading; labelSubHeading.Text = SubHeading;
if (Message != null) if (Message != null)
if (this.labelMessage.Text != Message) if (labelMessage.Text != Message)
this.labelMessage.Text = Message; labelMessage.Text = Message;
if (ShowProgress.HasValue) if (ShowProgress.HasValue)
{ {
if (ShowProgress.Value) if (ShowProgress.Value)
{ {
this.progressBar.Visible = true; progressBar.Visible = true;
if (Progress.HasValue) if (Progress.HasValue)
{ {
if (Progress.Value >= 0) if (Progress.Value >= 0)
{ {
this.progressBar.Value = Math.Min(Progress.Value, 100); progressBar.Value = Math.Min(Progress.Value, 100);
this.progressBar.Style = ProgressBarStyle.Continuous; progressBar.Style = ProgressBarStyle.Continuous;
} }
else else
{ {
this.progressBar.Style = ProgressBarStyle.Marquee; progressBar.Style = ProgressBarStyle.Marquee;
} }
} }
} }
else else
{ {
this.progressBar.Visible = false; progressBar.Visible = false;
} }
} }
} }
+32 -37
View File
@@ -1,54 +1,49 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper 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 InstallLoop(string installLocation, string wimImageId, string tempPath, Action completeCallback, Uri forcedServerUrl)
public delegate void CompleteCallback();
private CompleteCallback mCompleteCallback;
private string InstallLocation;
private string WimImageId;
private string TempPath;
public InstallLoop(string InstallLocation, string WimImageId, string TempPath)
{ {
this.InstallLocation = InstallLocation; this.installLocation = installLocation;
this.WimImageId = WimImageId; this.wimImageId = wimImageId;
this.TempPath = TempPath; this.tempPath = tempPath;
this.completeCallback = completeCallback;
this.forcedServerUrl = forcedServerUrl;
} }
public void Start(CompleteCallback Callback) public void Start()
{ {
mCompleteCallback = Callback; var cancellationToken = cancellationTokenSource.Token;
LoopThread = new Thread(new ThreadStart(loopHost)); Task.Run(async () =>
LoopThread.Start();
}
private void loopHost()
{
try
{ {
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)
catch (Exception ex) {
{ if (ex.GetType() == typeof(ThreadAbortException))
if (ex.GetType() == typeof(ThreadAbortException)) return;
return; if (ex.GetType() == typeof(ThreadInterruptedException))
if (ex.GetType() == typeof(ThreadInterruptedException)) return;
return; if (ex.GetType() == typeof(OperationCanceledException))
Program.WriteAppError(ex); return;
throw; Program.WriteAppError(ex);
} throw;
}
}, cancellationToken);
} }
} }
} }
@@ -4,6 +4,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
{ {
@@ -20,12 +22,12 @@ namespace Disco.ClientBootstrapper.Interop
//Remove(StoreName.Root, StoreLocation.LocalMachine, _tempCerts); //Remove(StoreName.Root, StoreLocation.LocalMachine, _tempCerts);
} }
} }
public static void AddTempCerts() public static async Task AddTempCerts(CancellationToken cancellationToken)
{ {
if (_tempCerts == null) if (_tempCerts == null)
_tempCerts = new List<string>(); _tempCerts = new List<string>();
var inlineCertificateLocation = Program.InlinePath.Value; var inlineCertificateLocation = Path.GetDirectoryName(typeof(Program).Assembly.Location);
// Root Certificates // Root Certificates
try try
@@ -35,6 +37,7 @@ namespace Disco.ClientBootstrapper.Interop
{ {
foreach (var certFile in CertFiles) foreach (var certFile in CertFiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password"); var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password");
var result = Add(StoreName.Root, StoreLocation.LocalMachine, cert); var result = Add(StoreName.Root, StoreLocation.LocalMachine, cert);
if (result) if (result)
@@ -42,7 +45,7 @@ namespace Disco.ClientBootstrapper.Interop
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp")) if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber); _tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, $"Added Root Certificate: {cert.ShortSubjectName()}"); 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) foreach (var certFile in CertFiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password"); var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password");
var result = Add(StoreName.CertificateAuthority, StoreLocation.LocalMachine, cert); var result = Add(StoreName.CertificateAuthority, StoreLocation.LocalMachine, cert);
if (result) if (result)
@@ -67,7 +71,7 @@ namespace Disco.ClientBootstrapper.Interop
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp")) if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber); _tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, $"Added Intermediate Certificate: {cert.ShortSubjectName()}"); 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) foreach (var certFile in CertFiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet); var cert = new X509Certificate2(File.ReadAllBytes(certFile), "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
var result = Add(StoreName.My, StoreLocation.LocalMachine, cert); var result = Add(StoreName.My, StoreLocation.LocalMachine, cert);
if (result) if (result)
@@ -92,7 +97,7 @@ namespace Disco.ClientBootstrapper.Interop
if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp")) if (Path.GetFileNameWithoutExtension(certFile).ToLower().Contains("temp"))
_tempCerts.Add(cert.SerialNumber); _tempCerts.Add(cert.SerialNumber);
Program.Status.UpdateStatus(null, null, $"Added Host Certificate: {cert.ShortSubjectName()}"); 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.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
{ {
public static class InstallInterop public static class InstallInterop
{ {
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [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] [Flags]
enum MoveFileFlags private enum MoveFileFlags
{ {
MOVEFILE_REPLACE_EXISTING = 0x00000001, MOVEFILE_REPLACE_EXISTING = 0x00000001,
MOVEFILE_COPY_ALLOWED = 0x00000002, MOVEFILE_COPY_ALLOWED = 0x00000002,
@@ -22,19 +24,19 @@ namespace Disco.ClientBootstrapper.Interop
MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020 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 SourceLocation = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
var InstallLocation = Path.Combine(RootFilesystemLocation, FilesystemInstallLocation); var InstallLocation = Path.Combine(rootFilesystemLocation, filesystemInstallLocation);
var BootstrapperCmdLinePath = Path.Combine(VirtualRootFilesystemLocation, FilesystemInstallLocation, "Disco.ClientBootstrapper.exe"); var BootstrapperCmdLinePath = Path.Combine(virtualRootFilesystemLocation, filesystemInstallLocation, "Disco.ClientBootstrapper.exe");
var GroupPolicyScriptsIniLocation = Path.Combine(RootFilesystemLocation, "Windows\\System32\\GroupPolicy\\Machine\\Scripts\\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"); var GroupPolicyScriptsIniBackupLocation = Path.Combine(rootFilesystemLocation, @"Windows\System32\GroupPolicy\Machine\Scripts\disco_scripts.ini");
// Create file system Location // Create file system Location
#region "Create File System Location" #region "Create File System Location"
Program.Status.UpdateStatus(null, null, "Creating Installation Location"); Program.Status.UpdateStatus(null, null, "Creating Installation Location");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
if (Directory.Exists(InstallLocation)) if (Directory.Exists(InstallLocation))
{ {
// Try and Delete Directory // Try and Delete Directory
@@ -52,19 +54,23 @@ namespace Disco.ClientBootstrapper.Interop
var installDir = Directory.CreateDirectory(InstallLocation); var installDir = Directory.CreateDirectory(InstallLocation);
installDir.Attributes = installDir.Attributes | FileAttributes.Hidden; installDir.Attributes = installDir.Attributes | FileAttributes.Hidden;
} }
cancellationToken.ThrowIfCancellationRequested();
#endregion #endregion
// Copy files to file system location // Copy files to file system location
#region "Copy to File System" #region "Copy to File System"
Program.Status.UpdateStatus(null, null, "Copying Files"); Program.Status.UpdateStatus(null, null, "Copying Files");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
// Copy Bootstrapper // Copy Bootstrapper
// ie: Executing Assembly // ie: Executing Assembly
File.Copy(System.Reflection.Assembly.GetExecutingAssembly().Location, Path.Combine(InstallLocation, "Disco.ClientBootstrapper.exe")); File.Copy(System.Reflection.Assembly.GetExecutingAssembly().Location, Path.Combine(InstallLocation, "Disco.ClientBootstrapper.exe"));
cancellationToken.ThrowIfCancellationRequested();
foreach (var file in Directory.EnumerateFiles(SourceLocation)) foreach (var file in Directory.EnumerateFiles(SourceLocation))
{ {
cancellationToken.ThrowIfCancellationRequested();
var fileName = Path.GetFileName(file); var fileName = Path.GetFileName(file);
// Only Copy Certain Files // Only Copy Certain Files
@@ -86,7 +92,7 @@ namespace Disco.ClientBootstrapper.Interop
// Backup & Create Group Policy Scripts.ini // Backup & Create Group Policy Scripts.ini
#region "Group Policy Scripts.ini" #region "Group Policy Scripts.ini"
Program.Status.UpdateStatus(null, null, "Creating Group Policy Script Entry"); Program.Status.UpdateStatus(null, null, "Creating Group Policy Script Entry");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
// Backup // Backup
if (!File.Exists(GroupPolicyScriptsIniBackupLocation)) if (!File.Exists(GroupPolicyScriptsIniBackupLocation))
{ {
@@ -95,6 +101,7 @@ namespace Disco.ClientBootstrapper.Interop
File.Move(GroupPolicyScriptsIniLocation, GroupPolicyScriptsIniBackupLocation); File.Move(GroupPolicyScriptsIniLocation, GroupPolicyScriptsIniBackupLocation);
} }
} }
cancellationToken.ThrowIfCancellationRequested();
// Create // Create
if (File.Exists(GroupPolicyScriptsIniLocation)) if (File.Exists(GroupPolicyScriptsIniLocation))
@@ -105,56 +112,67 @@ namespace Disco.ClientBootstrapper.Interop
{ {
using (var scriptsIniStreamWriter = new StreamWriter(scriptsIniStream, Encoding.Unicode)) using (var scriptsIniStreamWriter = new StreamWriter(scriptsIniStream, Encoding.Unicode))
{ {
scriptsIniStreamWriter.Write($"[Startup]{Environment.NewLine}0CmdLine={BootstrapperCmdLinePath}{Environment.NewLine}0Parameters=/AllowUninstall"); scriptsIniStreamWriter.WriteLine("[Startup]");
scriptsIniStreamWriter.Flush(); scriptsIniStreamWriter.WriteLine($"0CmdLine={BootstrapperCmdLinePath}");
if (forcedServerUrl == null)
scriptsIniStreamWriter.WriteLine("0Parameters=/AllowUninstall");
else
scriptsIniStreamWriter.WriteLine($"0Parameters=/AllowUninstall {forcedServerUrl}");
} }
} }
cancellationToken.ThrowIfCancellationRequested();
#endregion #endregion
// Backup & Create Group Policy Registry // Backup & Create Group Policy Registry
#region "Group Policy Registry" #region "Group Policy Registry"
Program.Status.UpdateStatus(null, null, "Creating Group Policy Registry Entries"); Program.Status.UpdateStatus(null, null, "Creating Group Policy Registry Entries");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
// Backup Scripts // 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")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts"); RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts");
} }
} }
cancellationToken.ThrowIfCancellationRequested();
// Create Scripts // Create Scripts
RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Shutdown").Dispose(); rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Shutdown").Dispose();
using (var regScriptsStartup = RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Startup\\0")) using (var regScriptsStartup = rootRegistryLocation.CreateSubKey(@"Microsoft\Windows\CurrentVersion\Group Policy\Scripts\Startup\0"))
{ {
regScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String); regScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String);
regScriptsStartup.SetValue("SOM-ID", "Local", 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("DisplayName", "Local Group Policy", RegistryValueKind.String);
regScriptsStartup.SetValue("GPOName", "Local Group Policy", RegistryValueKind.String); regScriptsStartup.SetValue("GPOName", "Local Group Policy", RegistryValueKind.String);
regScriptsStartup.SetValue("PSScriptOrder", 1, RegistryValueKind.DWord); regScriptsStartup.SetValue("PSScriptOrder", 1, RegistryValueKind.DWord);
using (var regScriptsStartup0 = regScriptsStartup.CreateSubKey("0")) using (var regScriptsStartup0 = regScriptsStartup.CreateSubKey("0"))
{ {
regScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String); regScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String);
regScriptsStartup0.SetValue("Parameters", "/AllowUninstall", 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("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); 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 // 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")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts"); RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts");
} }
} }
cancellationToken.ThrowIfCancellationRequested();
// Create Scripts State // 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("GPO-ID", "LocalGPO", RegistryValueKind.String);
regStateScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String); regStateScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String);
@@ -165,17 +183,21 @@ namespace Disco.ClientBootstrapper.Interop
using (var regStateScriptsStartup0 = regStateScriptsStartup.CreateSubKey("0")) using (var regStateScriptsStartup0 = regStateScriptsStartup.CreateSubKey("0"))
{ {
regStateScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String); regStateScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String);
regStateScriptsStartup0.SetValue("Parameters", "/AllowUninstall", 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); 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 #endregion
// Set Registry Startup Environment Policies // Set Registry Startup Environment Policies
#region "Registry Startup Policies" #region "Registry Startup Policies"
Program.Status.UpdateStatus(null, null, "Creating Startup Policy Registry Entries"); Program.Status.UpdateStatus(null, null, "Creating Startup Policy Registry Entries");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
using (var regWinlogon = RootRegistryLocation.OpenSubKey("Microsoft\\Windows NT\\CurrentVersion\\Winlogon", true)) using (var regWinlogon = rootRegistryLocation.OpenSubKey(@"Microsoft\Windows NT\CurrentVersion\Winlogon", true))
{ {
regWinlogon.SetValue("HideStartupScripts", 0, RegistryValueKind.DWord); regWinlogon.SetValue("HideStartupScripts", 0, RegistryValueKind.DWord);
regWinlogon.SetValue("RunStartupScriptSync", 1, RegistryValueKind.DWord); regWinlogon.SetValue("RunStartupScriptSync", 1, RegistryValueKind.DWord);
@@ -183,94 +205,110 @@ namespace Disco.ClientBootstrapper.Interop
#endregion #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); Program.Status.UpdateStatus("Installing Bootstrapper", "Starting", "Please wait...", false);
if (string.IsNullOrWhiteSpace(InstallLocation)) if (string.IsNullOrWhiteSpace(installLocation))
InstallLocation = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco"); 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) // Offline File System (WIM)
Program.Status.UpdateStatus("Installing Bootstrapper (Offline)", "Installing", $"Install Location: {InstallLocation}"); Program.Status.UpdateStatus("Installing Bootstrapper (Offline)", "Installing", $"Install Location: {installLocation}");
Program.SleepThread(1000, false); await Program.SleepThread(1000, false, cancellationToken);
// Mount WIM // Mount WIM
int wimImageIndex = 0; 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) cancellationToken.ThrowIfCancellationRequested();
WimImageId = "1"; if (wimImageId == null)
if (!int.TryParse(WimImageId, out wimImageIndex)) wimImageId = "1";
if (!int.TryParse(wimImageId, out wimImageIndex))
{ {
Program.Status.UpdateStatus(null, "Analysing WIM", $"Looking for Image Name: {WimImageId}"); Program.Status.UpdateStatus(null, "Analysing WIM", $"Looking for Image Name: {wimImageId}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
for (int i = 0; i < wim.ImageCount; i++) for (int i = 0; i < wim.ImageCount; i++)
{ {
var wimImageInfo = new System.Xml.XmlDocument(); var wimImageInfo = new System.Xml.XmlDocument();
using (var wimImage = wim[i]) using (var wimImage = wim[i])
wimImageInfo.LoadXml(wimImage.ImageInformation); wimImageInfo.LoadXml(wimImage.ImageInformation);
var wimImageInfoName = wimImageInfo.SelectSingleNode("//IMAGE/NAME"); 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; wimImageIndex = i + 1;
Program.Status.UpdateStatus(null, "Analysing WIM", $"Found Image Id '{WimImageId}' at Index {wimImageIndex}"); Program.Status.UpdateStatus(null, "Analysing WIM", $"Found Image Id '{wimImageId}' at Index {wimImageIndex}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
break; break;
} }
} }
} }
} }
cancellationToken.ThrowIfCancellationRequested();
if (wimImageIndex == 0) if (wimImageIndex == 0)
{ {
Program.Status.UpdateStatus(null, "Error", $"Unable to load WIM Image Id: {WimImageId}"); Program.Status.UpdateStatus(null, "Error", $"Unable to load WIM Image Id: {wimImageId}");
Program.SleepThread(5000, false); await Program.SleepThread(5000, false, cancellationToken);
return; return;
} }
// Get Temp Path // Get Temp Path
var wimMountPath = Path.Combine(TempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimMount"); var wimMountPath = Path.Combine(tempPath ?? Path.GetTempPath(), "DiscoClientBootstrapperWimMount");
if (Directory.Exists(wimMountPath)) if (Directory.Exists(wimMountPath))
Directory.Delete(wimMountPath, true); Directory.Delete(wimMountPath, true);
Directory.CreateDirectory(wimMountPath); 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)) if (Directory.Exists(wimTempMountPath))
Directory.Delete(wimTempMountPath, true); Directory.Delete(wimTempMountPath, true);
Directory.CreateDirectory(wimTempMountPath); Directory.CreateDirectory(wimTempMountPath);
cancellationToken.ThrowIfCancellationRequested();
bool wimCommitChanges = true; bool wimCommitChanges = true;
WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback m_MessageCallback = null; WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback m_MessageCallback = null;
try try
{ {
// Mount WIM // Mount WIM
Program.Status.UpdateStatus(null, "Mounting WIM", $"Mounting WIM Image to '{wimMountPath}'"); 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); m_MessageCallback = new WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback(WimImageEventMessagePump);
WIMInterop.WindowsImageContainer.NativeMethods.RegisterCallback(m_MessageCallback); 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 // Load Local Machine Registry
var wimHivePath = Path.Combine(wimMountPath, "Windows\\System32\\config\\SOFTWARE"); var wimHivePath = Path.Combine(wimMountPath, "Windows\\System32\\config\\SOFTWARE");
Program.Status.UpdateStatus(null, "Mounting Offline Registry Hive", $"Mounting Offline Registry Hive at '{wimHivePath}'"); 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)) using (var wimReg = new RegistryInterop(RegistryInterop.RegistryHives.HKEY_LOCAL_MACHINE, "DiscoClientBootstrapperWimHive", wimHivePath))
{ {
using (RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("DiscoClientBootstrapperWimHive", true)) try
{ {
string rootFileSystemLocation = wimMountPath; cancellationToken.ThrowIfCancellationRequested();
string fileSystemInstallLocation = "Disco"; using (RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("DiscoClientBootstrapperWimHive", true))
string virtualRootFileSystemLocation = "C:\\"; {
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}'");
await Program.SleepThread(500, false, cancellationToken);
wimReg.Unload();
} }
// Unload Local Machine Registry
Program.Status.UpdateStatus(null, "Unmounting Offline Registry Hive", $"Unmounting Offline Registry Hive at '{wimHivePath}'");
Program.SleepThread(500, false);
wimReg.Unload();
} }
} }
catch (Exception) catch (Exception)
@@ -282,8 +320,8 @@ namespace Disco.ClientBootstrapper.Interop
{ {
// Unmount WIM // Unmount WIM
Program.Status.UpdateStatus(null, "Unmounting WIM", $"Unmounting WIM Image at '{wimMountPath}'"); Program.Status.UpdateStatus(null, "Unmounting WIM", $"Unmounting WIM Image at '{wimMountPath}'");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
WIMInterop.WindowsImageContainer.NativeMethods.DismountImage(wimMountPath, InstallLocation, wimImageIndex, wimCommitChanges); WIMInterop.WindowsImageContainer.NativeMethods.DismountImage(wimMountPath, installLocation, wimImageIndex, wimCommitChanges);
if (m_MessageCallback != null) if (m_MessageCallback != null)
{ {
@@ -295,23 +333,25 @@ namespace Disco.ClientBootstrapper.Interop
Directory.Delete(wimMountPath, true); Directory.Delete(wimMountPath, true);
if (Directory.Exists(wimTempMountPath)) if (Directory.Exists(wimTempMountPath))
Directory.Delete(wimTempMountPath, true); Directory.Delete(wimTempMountPath, true);
cancellationToken.ThrowIfCancellationRequested();
} }
} }
else else
{ {
// Online File System // Online File System
Program.Status.UpdateStatus("Installing Bootstrapper (Online)", "Installing", $"Install Location: {InstallLocation}", true, -1); Program.Status.UpdateStatus("Installing Bootstrapper (Online)", "Installing", $"Install Location: {installLocation}", true, -1);
Program.SleepThread(1000, false); await Program.SleepThread(1000, false, cancellationToken);
string rootFileSystemLocation = Path.GetPathRoot(InstallLocation); string rootFileSystemLocation = Path.GetPathRoot(installLocation);
RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("SOFTWARE", true); 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.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.Status.UpdateStatus(null, "Complete", "Finished Installing Bootstrapper");
Program.SleepThread(1500, false); await Program.SleepThread(1500, false, cancellationToken);
} }
private static uint WimImageEventMessagePump( private static uint WimImageEventMessagePump(
@@ -349,41 +389,28 @@ namespace Disco.ClientBootstrapper.Interop
return status; return status;
} }
public static void Uninstall() public static async Task Uninstall(CancellationToken cancellationToken)
{ {
// Application Directory // Application Directory
var appDirectory = Program.InlinePath.Value; var appDirectory = Path.GetDirectoryName(typeof(Program).Assembly.Location);
if (Program.AllowUninstall && !appDirectory.StartsWith("\\\\")) if (Program.AllowUninstall && !appDirectory.StartsWith(@"\\"))
{ {
Program.Status.UpdateStatus("System Preparation (Bootstrapper)", "Uninstalling Bootstrapper...", string.Empty, false, 0); Program.Status.UpdateStatus("System Preparation (Bootstrapper)", "Uninstalling Bootstrapper...", string.Empty, false, 0);
Program.SleepThread(1000, true); await Program.SleepThread(1000, true, cancellationToken);
//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);
//}
// Remove Registry Entries // 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("HideStartupScripts", false);
regWinlogon.DeleteValue("RunStartupScriptSync", 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\Shutdown", false);
Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Startup", 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\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\State\Machine\Scripts\Startup", false);
// Restore Registry Backups // 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")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
@@ -391,7 +418,7 @@ namespace Disco.ClientBootstrapper.Interop
RegistryUtilities.RenameSubKey(regGroupPolicy, "Disco_Scripts", "Scripts"); 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")) if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts"))
{ {
@@ -401,10 +428,10 @@ namespace Disco.ClientBootstrapper.Interop
} }
// Delete Group Policy Script File // 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)) if (File.Exists(groupPolicyScriptsPath))
File.Delete(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)) if (File.Exists(groupPolicyScriptsBackupPath))
File.Move(groupPolicyScriptsBackupPath, groupPolicyScriptsPath); File.Move(groupPolicyScriptsBackupPath, groupPolicyScriptsPath);
@@ -1,14 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Management; using System.Management;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Xml; using System.Xml;
namespace Disco.ClientBootstrapper.Interop namespace Disco.ClientBootstrapper.Interop
{ {
static class NetworkInterop internal static class NetworkInterop
{ {
#region PInvoke #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); var ipProps = nic.GetIPProperties();
if (pr.Status == IPStatus.Success) var ipv4Props = ipProps.GetIPv4Properties();
return true; if (ipv4Props.IsAutomaticPrivateAddressingActive)
else continue;
return false;
} return ipProps.UnicastAddresses
catch (Exception) .Where(ua => ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{ .Any();
return false;
} }
} }
return false;
} }
public static void ConfigureWireless() public static async Task ConfigureWireless(CancellationToken cancellationToken)
{ {
// Add Certificates // Add Certificates
Program.Status.UpdateStatus(null, null, "Configuring Wireless Certificates"); Program.Status.UpdateStatus(null, null, "Configuring Wireless Certificates");
CertificateInterop.AddTempCerts(); await CertificateInterop.AddTempCerts(cancellationToken);
// Add Wireless Profiles // Add Wireless Profiles
Program.Status.UpdateStatus(null, null, "Configuring Wireless Profiles"); Program.Status.UpdateStatus(null, null, "Configuring Wireless Profiles");
@@ -208,15 +216,16 @@ namespace Disco.ClientBootstrapper.Interop
{ {
foreach (var inlineWirelessProfile in wirelessInlineProfiles) foreach (var inlineWirelessProfile in wirelessInlineProfiles)
{ {
cancellationToken.ThrowIfCancellationRequested();
if (inlineWirelessProfile.AddProfile(wlanHandle, na.Guid)) if (inlineWirelessProfile.AddProfile(wlanHandle, na.Guid))
{ {
Program.Status.UpdateStatus(null, null, $"Added Wireless Profile: {inlineWirelessProfile.ProfileName}"); Program.Status.UpdateStatus(null, null, $"Added Wireless Profile: {inlineWirelessProfile.ProfileName}");
Program.SleepThread(500, false); await Program.SleepThread(500, false, cancellationToken);
} }
else else
{ {
Program.Status.UpdateStatus(null, null, $"Unable to add Wireless Profile: {inlineWirelessProfile.ProfileName}"); 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() 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); var inlineProfiles = new List<WirelessProfile>(inlineProfileFiles.Count);
foreach (var filename in inlineProfileFiles) foreach (var filename in inlineProfileFiles)
{ {
var profile = new WirelessProfile() var profile = new WirelessProfile()
{ {
Filename = filename, Filename = filename,
ProfileXml = System.IO.File.ReadAllText(filename) ProfileXml = File.ReadAllText(filename)
}; };
var profileXml = new XmlDocument(); var profileXml = new XmlDocument();
profileXml.LoadXml(profile.ProfileXml); profileXml.LoadXml(profile.ProfileXml);
+54 -50
View File
@@ -1,36 +1,58 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace Disco.ClientBootstrapper namespace Disco.ClientBootstrapper
{ {
static class Program internal static class Program
{ {
public static IStatus Status { get; set; } private static readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
public static BootstrapperLoop BootstrapperLoop { get; set; } public static IStatus Status { get; private set; }
public static InstallLoop InstallLoop { get; set; }
public static List<string> PostBootstrapperActions { get; set; } public static List<string> PostBootstrapperActions { get; set; }
public static bool AllowUninstall { get; set; } public static bool AllowUninstall { get; private set; }
public static bool ApplicationExiting { get; set; } public static Uri ForcedServerUrl { get; private set; } = null;
public static Lazy<string> InlinePath = new Lazy<string>(() =>
{
return System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
});
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread] [STAThread]
static void Main(string[] args) private static void Main(string[] args)
{ {
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException); Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
if (args.Length > 0) 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()) switch (args[0].ToLower())
{ {
case "/install": case "/install":
@@ -46,14 +68,17 @@ namespace Disco.ClientBootstrapper
wimImage = args[2]; wimImage = args[2];
if (args.Length > 3) if (args.Length > 3)
tempPath = args[3]; tempPath = args[3];
InstallLoop = new InstallLoop(installLocation, wimImage, tempPath); var installLoop = new InstallLoop(installLocation, wimImage, tempPath, InstallComplete, ForcedServerUrl);
InstallLoop.Start(new InstallLoop.CompleteCallback(InstallComplete)); installLoop.Start();
Application.Run(); Application.Run();
return; return;
case "/uninstall": case "/uninstall":
AllowUninstall = true; AllowUninstall = true;
Status = new NullStatus(); Status = new NullStatus();
Interop.InstallInterop.Uninstall(); Task.Run(async () =>
{
await Interop.InstallInterop.Uninstall(cancellationTokenSource.Token);
}).Wait(cancellationTokenSource.Token);
return; return;
case "/allowuninstall": case "/allowuninstall":
AllowUninstall = true; AllowUninstall = true;
@@ -71,13 +96,13 @@ namespace Disco.ClientBootstrapper
statusForm.Show(); statusForm.Show();
} }
BootstrapperLoop = new BootstrapperLoop(Status, new BootstrapperLoop.LoopCompleteCallback(LoopComplete)); var bootstrapperLoop = new BootstrapperLoop(Status, ForcedServerUrl, LoopComplete, cancellationTokenSource.Token);
BootstrapperLoop.Start(); bootstrapperLoop.Start();
Application.Run(); Application.Run();
} }
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{ {
WriteAppError(e.Exception); WriteAppError(e.Exception);
} }
@@ -100,7 +125,7 @@ namespace Disco.ClientBootstrapper
catch (Exception) { } catch (Exception) { }
} }
public static void LoopComplete() public static async Task LoopComplete(CancellationToken cancellationToken)
{ {
// Run Post Actions // Run Post Actions
if (PostBootstrapperActions != null) if (PostBootstrapperActions != null)
@@ -108,32 +133,32 @@ namespace Disco.ClientBootstrapper
// Check Uninstall // Check Uninstall
if (AllowUninstall && PostBootstrapperActions.Contains("UninstallBootstrapper")) if (AllowUninstall && PostBootstrapperActions.Contains("UninstallBootstrapper"))
{ {
Interop.InstallInterop.Uninstall(); await Interop.InstallInterop.Uninstall(cancellationToken);
} }
// Check ShutdownActions // Check ShutdownActions
if (PostBootstrapperActions.Contains("Shutdown")) if (PostBootstrapperActions.Contains("Shutdown"))
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Shutting Down; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Shutting Down; Finished...", string.Empty, false, 0);
SleepThread(4000, true); await SleepThread(4000, true, cancellationToken);
Interop.ShutdownInterop.Shutdown(); Interop.ShutdownInterop.Shutdown();
} }
else if (PostBootstrapperActions.Contains("Reboot")) else if (PostBootstrapperActions.Contains("Reboot"))
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Rebooting; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Rebooting; Finished...", string.Empty, false, 0);
SleepThread(4000, true); await SleepThread(4000, true, cancellationToken);
Interop.ShutdownInterop.Reboot(); Interop.ShutdownInterop.Reboot();
} }
else else
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0);
SleepThread(2000, true); await SleepThread(2000, true, cancellationToken);
} }
} }
else else
{ {
Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0); Status.UpdateStatus("System Preparation (Bootstrapper)", "Starting System; Finished...", string.Empty, false, 0);
SleepThread(2000, true); await SleepThread(2000, true, cancellationToken);
} }
ExitApplication(); ExitApplication();
@@ -146,33 +171,12 @@ namespace Disco.ClientBootstrapper
public static void ExitApplication() public static void ExitApplication()
{ {
if (!ApplicationExiting) if (!cancellationTokenSource.IsCancellationRequested)
{ cancellationTokenSource.Cancel();
ApplicationExiting = true; Application.Exit();
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();
}
}
}
Application.Exit();
}
} }
public static void Trace(string Format, params string[] args) public static async Task SleepThread(int millisecondsTimeout, bool updateUI, CancellationToken cancellationToken)
{
System.Diagnostics.Debug.WriteLine(Format, args);
}
public static void SleepThread(int millisecondsTimeout, bool updateUI)
{ {
if (updateUI) if (updateUI)
{ {
@@ -180,12 +184,12 @@ namespace Disco.ClientBootstrapper
{ {
int progress = Convert.ToInt32(((Convert.ToDouble(i) / Convert.ToDouble(millisecondsTimeout)) * 100)); int progress = Convert.ToInt32(((Convert.ToDouble(i) / Convert.ToDouble(millisecondsTimeout)) * 100));
Status.UpdateStatus(null, null, null, true, progress); Status.UpdateStatus(null, null, null, true, progress);
Thread.Sleep(500); await Task.Delay(500, cancellationToken);
} }
} }
else else
{ {
Thread.Sleep(millisecondsTimeout); await Task.Delay(millisecondsTimeout, cancellationToken);
} }
} }
} }
@@ -14,5 +14,11 @@ namespace Disco.Data.Configuration.Modules
get => Get(DeviceExportOptions.DefaultOptions()); get => Get(DeviceExportOptions.DefaultOptions());
set => Set(value); 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.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data.Entity" /> <Reference Include="System.Data.Entity" />
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> <Reference Include="System.ValueTuple, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath> <HintPath>..\packages\System.ValueTuple.4.6.2\lib\net462\System.ValueTuple.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
@@ -62,6 +62,7 @@
<Compile Include="Repository\FlagType.cs" /> <Compile Include="Repository\FlagType.cs" />
<Compile Include="Repository\User\UserComment.cs" /> <Compile Include="Repository\User\UserComment.cs" />
<Compile Include="Repository\FlagPermission.cs" /> <Compile Include="Repository\FlagPermission.cs" />
<Compile Include="Services\Devices\DeviceEnrolmentServerDiscoveryMethod.cs" />
<Compile Include="Services\Devices\DeviceFlags\DeviceFlagExportOptions.cs" /> <Compile Include="Services\Devices\DeviceFlags\DeviceFlagExportOptions.cs" />
<Compile Include="Services\Devices\DeviceFlags\DeviceFlagExportRecord.cs" /> <Compile Include="Services\Devices\DeviceFlags\DeviceFlagExportRecord.cs" />
<Compile Include="Services\Documents\DocumentExportOptions.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 string GroupId { get; set; }
public DateTime? FilterBeginDate { 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<StatisticIntPair> Stat_JobIdentifiers { get; set; }
public List<StatisticJob> Stat_Jobs { get; set; } public List<StatisticJob> Stat_Jobs { get; set; }
public List<StatisticInt> Stat_EnrollmentDiscovery { get; set; }
public class StatisticIntPair public class StatisticIntPair
{ {
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
namespace Disco.Models.Services.Jobs.Noticeboards namespace Disco.Models.Services.Jobs.Noticeboards
{ {
@@ -9,6 +10,7 @@ namespace Disco.Models.Services.Jobs.Noticeboards
string DeviceSerialNumber { get; } string DeviceSerialNumber { get; }
string DeviceComputerNameFriendly { get; } string DeviceComputerNameFriendly { get; }
string DeviceComputerName { get; } string DeviceComputerName { get; }
string DeviceName { get; }
string DeviceLocation { get; } string DeviceLocation { get; }
string DeviceDescription { get; } string DeviceDescription { get; }
@@ -16,6 +18,7 @@ namespace Disco.Models.Services.Jobs.Noticeboards
int DeviceProfileId { get; } int DeviceProfileId { get; }
int? DeviceAddressId { get; } int? DeviceAddressId { get; }
string DeviceAddressShortName { get; } string DeviceAddressShortName { get; }
IEnumerable<int> JobQueueIds { get; }
string UserId { get; } string UserId { get; }
string UserIdFriendly { 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 public interface ConfigEnrolmentIndexModel : BaseUIModel
{ {
string MacSshUsername { get; set; } string MacSshUsername { get; set; }
int PendingTimeoutMinutes { 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"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Newtonsoft.Json" version="13.0.2" targetFramework="net45" /> <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> </packages>
@@ -41,6 +41,10 @@
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <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" /> <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly> </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> </assemblyBinding>
</runtime> </runtime>
</configuration> </configuration>
+4
View File
@@ -56,6 +56,10 @@
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <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" /> <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly> </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> </assemblyBinding>
</runtime> </runtime>
</configuration> </configuration>
@@ -1,6 +1,7 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.ClientServices; using Disco.Models.ClientServices;
using Disco.Models.Repository; using Disco.Models.Repository;
using Disco.Models.Services.Devices;
using Disco.Services.Authorization; using Disco.Services.Authorization;
using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Interop.ActiveDirectory;
using Disco.Services.Users; using Disco.Services.Users;
@@ -18,6 +19,18 @@ namespace Disco.Services.Devices.Enrolment
private static readonly string pendingIdentifierAlphabet = "23456789ABCDEFGHJKMNPQRSTWXYZ"; private static readonly string pendingIdentifierAlphabet = "23456789ABCDEFGHJKMNPQRSTWXYZ";
private static readonly Random pendingIdentifierRng = new Random(); private static readonly Random pendingIdentifierRng = new Random();
private static readonly ConcurrentDictionary<string, EnrolResponse> pendingEnrolments = new ConcurrentDictionary<string, EnrolResponse>(); 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() private static void CleanupPendingEnrolments()
{ {
@@ -175,9 +188,11 @@ namespace Disco.Services.Devices.Enrolment
{ {
if (!authenticatedToken.Has(Claims.ComputerAccount)) if (!authenticatedToken.Has(Claims.ComputerAccount))
throw new EnrolmentSafeException($"Connection not correctly authenticated (SN: {Request.SerialNumber}; Auth User: {authenticatedToken.User.UserId})"); 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) if (domain == null && !ActiveDirectory.Context.TryGetDomainByName(Request.DNSDomainName, out domain))
domain = ActiveDirectory.Context.GetDomainByName(Request.DNSDomainName); 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)) 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})"); 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); EnrolmentLog.LogSessionTaskProvisioningADAccount(sessionId, device.SerialNumber, device.DeviceDomainId);
adMachineAccount = domainController.Value.RetrieveADMachineAccount(device.DeviceDomainId); adMachineAccount = domainController.Value.RetrieveADMachineAccount(device.DeviceDomainId);
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount, out var offlineProvisionDiagnosicInfo); response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount);
EnrolmentLog.LogSessionDiagnosticInformation(sessionId, offlineProvisionDiagnosicInfo);
response.RequireReboot = true; response.RequireReboot = true;
} }
@@ -428,10 +441,7 @@ namespace Disco.Services.Devices.Enrolment
response.ComputerName = calculatedAccountUsername; response.ComputerName = calculatedAccountUsername;
// Create New Account // Create New Account
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount);
response.OfflineDomainJoinManifest = domainController.Value.OfflineDomainJoinProvision(device.DeviceDomainId, device.DeviceProfile.OrganisationalUnit, ref adMachineAccount, out var offlineProvisionDiagnosicInfo);
EnrolmentLog.LogSessionDiagnosticInformation(sessionId, offlineProvisionDiagnosicInfo);
response.RequireReboot = true; response.RequireReboot = true;
} }
+14 -4
View File
@@ -53,8 +53,8 @@
<Reference Include="LumenWorks.Framework.IO"> <Reference Include="LumenWorks.Framework.IO">
<HintPath>..\packages\LumenWorks.Framework.IO.3.8.0\lib\net20\LumenWorks.Framework.IO.dll</HintPath> <HintPath>..\packages\LumenWorks.Framework.IO.3.8.0\lib\net20\LumenWorks.Framework.IO.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.AspNet.SignalR.Core, Version=2.1.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="Microsoft.AspNet.SignalR.Core, Version=2.4.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.SignalR.Core.2.1.2\lib\net45\Microsoft.AspNet.SignalR.Core.dll</HintPath> <HintPath>..\packages\Microsoft.AspNet.SignalR.Core.2.4.3\lib\net45\Microsoft.AspNet.SignalR.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL"> <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> <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"> <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> <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> <Reference Include="System.ValueTuple, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath> <HintPath>..\packages\System.ValueTuple.4.6.2\lib\net462\System.ValueTuple.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Web" /> <Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" /> <Reference Include="System.Web.Extensions" />
@@ -464,6 +464,7 @@
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryGroupCache.cs" /> <Compile Include="Interop\ActiveDirectory\ActiveDirectoryGroupCache.cs" />
<Compile Include="Interop\ActiveDirectory\ActiveDirectoryManagedGroups.cs" /> <Compile Include="Interop\ActiveDirectory\ActiveDirectoryManagedGroups.cs" />
<Compile Include="Interop\ActiveDirectory\ADDeviceDescriptionUpdateTask.cs" /> <Compile Include="Interop\ActiveDirectory\ADDeviceDescriptionUpdateTask.cs" />
<Compile Include="Interop\ActiveDirectory\ADDeviceOfflineDomainJoining.cs" />
<Compile Include="Interop\ActiveDirectory\ADDirectoryEntry.cs" /> <Compile Include="Interop\ActiveDirectory\ADDirectoryEntry.cs" />
<Compile Include="Interop\ActiveDirectory\ADDiscoverServers.cs" /> <Compile Include="Interop\ActiveDirectory\ADDiscoverServers.cs" />
<Compile Include="Interop\ActiveDirectory\ADDomain.cs" /> <Compile Include="Interop\ActiveDirectory\ADDomain.cs" />
@@ -484,6 +485,7 @@
<Compile Include="Interop\ActiveDirectory\IADObject.cs" /> <Compile Include="Interop\ActiveDirectory\IADObject.cs" />
<Compile Include="Interop\DiscoServices\ActivationCleanupTask.cs" /> <Compile Include="Interop\DiscoServices\ActivationCleanupTask.cs" />
<Compile Include="Interop\DiscoServices\ActivationService.cs" /> <Compile Include="Interop\DiscoServices\ActivationService.cs" />
<Compile Include="Interop\DiscoServices\AuthenticationSessionScope.cs" />
<Compile Include="Interop\DiscoServices\DiscoServiceHelpers.cs" /> <Compile Include="Interop\DiscoServices\DiscoServiceHelpers.cs" />
<Compile Include="Interop\DiscoServices\Jobs.cs" /> <Compile Include="Interop\DiscoServices\Jobs.cs" />
<Compile Include="Interop\DiscoServices\LicenseValidationTask.cs" /> <Compile Include="Interop\DiscoServices\LicenseValidationTask.cs" />
@@ -496,6 +498,14 @@
<Compile Include="Interop\DiscoServices\Upload\UploadOnlineClient.cs" /> <Compile Include="Interop\DiscoServices\Upload\UploadOnlineClient.cs" />
<Compile Include="Interop\DiscoServices\Upload\UploadOnlineService.cs" /> <Compile Include="Interop\DiscoServices\Upload\UploadOnlineService.cs" />
<Compile Include="Interop\DiscoServices\Upload\UploadOnlineSyncTask.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\IIS\PreserveIisBindingsTask.cs" />
<Compile Include="Interop\MimeTypes.cs" /> <Compile Include="Interop\MimeTypes.cs" />
<Compile Include="Interop\VicEduDept\VicSmart.cs" /> <Compile Include="Interop\VicEduDept\VicSmart.cs" />
@@ -149,27 +149,28 @@ namespace Disco.Services
template = database.DocumentTemplates.Find(templateId); template = database.DocumentTemplates.Find(templateId);
if (template == null) if (template == null)
throw new ArgumentException("Invalid document template id", nameof(templateId)); 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 // validate authorization
switch (template.Scope) switch (scope)
{ {
case DocumentTemplate.DocumentTemplateScopes.Device: case AttachmentTypes.Device:
authorization.Require(Claims.Device.Actions.GenerateDocuments); authorization.Require(Claims.Device.Actions.GenerateDocuments);
break; break;
case DocumentTemplate.DocumentTemplateScopes.Job: case AttachmentTypes.Job:
authorization.Require(Claims.Job.Actions.GenerateDocuments); authorization.Require(Claims.Job.Actions.GenerateDocuments);
break; break;
case DocumentTemplate.DocumentTemplateScopes.User: case AttachmentTypes.User:
authorization.Require(Claims.User.Actions.GenerateDocuments); authorization.Require(Claims.User.Actions.GenerateDocuments);
break; break;
default: default:
throw new InvalidOperationException("Unknown DocumentType Scope"); throw new InvalidOperationException("Unsupported DocumentType Scope");
} }
// resolve target // resolve target
target = template.ResolveScopeTarget(database, targetId, out targetUser); target = template.ResolveScopeTarget(database, targetId, out targetUser)
if (target == null) ?? throw new ArgumentException("Target not found", nameof(targetId));
throw new ArgumentException("Target not found", nameof(targetId));
} }
public static IEnumerable<OnImportUserFlagRule> GetOnImportUserFlagRuleDetails(this DocumentTemplate template, DiscoDataContext database) public static IEnumerable<OnImportUserFlagRule> GetOnImportUserFlagRuleDetails(this DocumentTemplate template, DiscoDataContext database)
@@ -1,6 +1,8 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.Repository; using Disco.Models.Repository;
using Disco.Models.Services.Documents; using Disco.Models.Services.Documents;
using Disco.Services.Authorization;
using Disco.Services.Documents;
using Disco.Services.Expressions; using Disco.Services.Expressions;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -139,12 +141,46 @@ namespace Disco.Services
} }
public static IAttachmentTarget ResolveScopeTarget(this DocumentTemplatePackage templatePackage, DiscoDataContext database, string targetId) 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) if (templatePackage == null)
throw new ArgumentNullException(nameof(templatePackage)); 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));
}
} }
} }
+23 -9
View File
@@ -8,6 +8,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Linq;
using System.Text; using System.Text;
namespace Disco.Services.Expressions namespace Disco.Services.Expressions
@@ -200,18 +201,31 @@ namespace Disco.Services.Expressions
var detailsService = new DetailsProviderService(Database); var detailsService = new DetailsProviderService(Database);
if (target != null) if (target != null)
{ {
var detailsTarget = default(User);
if (target is User targetUser) if (target is User targetUser)
{ detailsTarget = targetUser;
detailsVariables.Add("UserDetails", new LazyDictionary(() => detailsService.GetDetails(targetUser)));
}
else if (target is Job targetJob) else if (target is Job targetJob)
{ detailsTarget = targetJob.User;
detailsVariables.Add("UserDetails", targetJob.User == null ? (IDictionary<string, string>)new Dictionary<string, string>() : new LazyDictionary(() => detailsService.GetDetails(targetJob.User)));
}
else if (target is Device targetDevice) else if (target is Device targetDevice)
{ detailsTarget = targetDevice.AssignedUser;
detailsVariables.Add("UserDetails", targetDevice.AssignedUser == null ? (IDictionary<string, string>)new Dictionary<string, string>() : new LazyDictionary(() => detailsService.GetDetails(targetDevice.AssignedUser)));
} if (detailsTarget != null)
detailsVariables.Add("UserDetails", new LazyDictionary(() =>
{
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) 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices; using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory; using System.DirectoryServices.ActiveDirectory;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Security.Principal; using System.Security.Principal;
using System.Text;
namespace Disco.Services.Interop.ActiveDirectory namespace Disco.Services.Interop.ActiveDirectory
{ {
public class ADDomainController public class ADDomainController
{ {
private const string LdapPathTemplate = @"LDAP://{0}/{1}"; private const string LdapPathTemplate = @"LDAP://{0}/{1}";
private ActiveDirectoryContext context; private readonly ActiveDirectoryContext context;
public DomainController DomainController { get; private set; } public DomainController DomainController { get; private set; }
public ADDomain Domain { get; private set; } public ADDomain Domain { get; private set; }
@@ -100,25 +98,25 @@ namespace Disco.Services.Interop.ActiveDirectory
return results; 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"); throw new ArgumentNullException("SearchRoot");
if (string.IsNullOrEmpty(LdapFilter)) if (string.IsNullOrEmpty(ldapFilter))
throw new ArgumentNullException("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"); 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; searcher.PageSize = 500;
if (ResultLimit.HasValue) if (resultLimit.HasValue)
searcher.SizeLimit = ResultLimit.Value; 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) if (SecurityIdentifier == null)
throw new ArgumentNullException("SecurityIdentifier"); throw new ArgumentNullException("SecurityIdentifier");
if (!SecurityIdentifier.IsEqualDomainSid(Domain.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(); var sidBinaryString = SecurityIdentifier.ToBinaryString();
@@ -295,6 +293,7 @@ namespace Disco.Services.Interop.ActiveDirectory
private const string OrganisationalUnitsLdapFilter = "(objectCategory=organizationalUnit)"; private const string OrganisationalUnitsLdapFilter = "(objectCategory=organizationalUnit)";
private static readonly string[] OrganisationalUnitsLoadProperties = { "name", "distinguishedName" }; private static readonly string[] OrganisationalUnitsLoadProperties = { "name", "distinguishedName" };
[Obsolete("Retrieve as needed using RetrieveADOrganisationUnits(parentDistinguishedName)")]
public List<ADOrganisationalUnit> RetrieveADOrganisationalUnitStructure() public List<ADOrganisationalUnit> RetrieveADOrganisationalUnitStructure()
{ {
Dictionary<string, List<ADOrganisationalUnit>> resultTree = new Dictionary<string, List<ADOrganisationalUnit>>(); Dictionary<string, List<ADOrganisationalUnit>> resultTree = new Dictionary<string, List<ADOrganisationalUnit>>();
@@ -319,6 +318,15 @@ namespace Disco.Services.Interop.ActiveDirectory
return indexedChildren[Domain.DistinguishedName]; 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 #endregion
private ADSearchResult RetrieveBySamAccountName(string Id, string LdapFilterTemplate, string[] LoadProperties) private ADSearchResult RetrieveBySamAccountName(string Id, string LdapFilterTemplate, string[] LoadProperties)
@@ -343,93 +351,6 @@ namespace Disco.Services.Interop.ActiveDirectory
return (pr.Status == IPStatus.Success); 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 #endregion
public override string ToString() public override string ToString()
@@ -204,6 +204,29 @@ namespace Disco.Services.Interop.ActiveDirectory
#region Actions #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) public void DeleteAccount(ADDomainController WritableDomainController)
{ {
if (IsCriticalSystemObject) if (IsCriticalSystemObject)
@@ -80,21 +80,23 @@ namespace Disco.Services.Interop.ActiveDirectory
return JsonConvert.DeserializeObject<ADManagedGroupConfiguration>(ConfigurationJson); return JsonConvert.DeserializeObject<ADManagedGroupConfiguration>(ConfigurationJson);
} }
public static string ValidConfigurationToJson(string GroupKey, string GroupId, DateTime? FilterBeginDate) 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)) if (string.IsNullOrWhiteSpace(groupId))
GroupId = null; groupId = null;
if (GroupId != null) if (groupId != null)
GroupId = ActiveDirectory.Context.ManagedGroups.ValidateGroupId(GroupId, GroupKey); groupId = ActiveDirectory.Context.ManagedGroups.ValidateGroupId(groupId, groupKey);
if (groupId == null)
if (GroupId == null)
return null; return null;
else else
return JsonConvert.SerializeObject(new ADManagedGroupConfiguration() return JsonConvert.SerializeObject(new ADManagedGroupConfiguration()
{ {
GroupId = GroupId, GroupId = groupId,
FilterBeginDate = FilterBeginDate FilterBeginDate = filterBeginDate,
}, new JsonSerializerSettings() { DefaultValueHandling = DefaultValueHandling.Ignore }); UpdateDescription = updateDescription,
}, new JsonSerializerSettings());
} }
public abstract void Dispose(); public abstract void Dispose();
@@ -16,7 +16,7 @@ namespace Disco.Services.Interop.ActiveDirectory
private static ActiveDirectoryContext context; private static ActiveDirectoryContext context;
private static ActiveDirectoryGroupCache groupCache; private static ActiveDirectoryGroupCache groupCache;
private static object contextInitializingLock = new object(); private static readonly object contextInitializingLock = new object();
public static void Initialize(DiscoDataContext Database) public static void Initialize(DiscoDataContext Database)
{ {
@@ -157,6 +157,7 @@ namespace Disco.Services.Interop.ActiveDirectory
#endregion #endregion
#region Organisational Units #region Organisational Units
[Obsolete("Retrieve as needed using RetrieveADOrganisationUnits(parentDistinguishedName)")]
public static IEnumerable<Tuple<ADDomain, List<ADOrganisationalUnit>>> RetrieveADOrganisationalUnitStructure() public static IEnumerable<Tuple<ADDomain, List<ADOrganisationalUnit>>> RetrieveADOrganisationalUnitStructure()
{ {
return Context.Domains return Context.Domains
@@ -178,18 +179,6 @@ namespace Disco.Services.Interop.ActiveDirectory
} }
#endregion #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 #region Helpers
public static string ParseDomainAccountId(string AccountId) public static string ParseDomainAccountId(string AccountId)
@@ -13,9 +13,9 @@ namespace Disco.Services.Interop.ActiveDirectory
public class ActiveDirectoryManagedGroups : IDisposable public class ActiveDirectoryManagedGroups : IDisposable
{ {
private ConcurrentDictionary<string, ADManagedGroup> managedGroups; private readonly ConcurrentDictionary<string, ADManagedGroup> managedGroups;
private Subject<ADManagedGroupScheduledAction> actionBuffer; private readonly Subject<ADManagedGroupScheduledAction> actionBuffer;
private IDisposable actionBufferSubscription; private readonly IDisposable actionBufferSubscription;
internal ActiveDirectoryManagedGroups() internal ActiveDirectoryManagedGroups()
{ {
@@ -379,13 +379,16 @@ 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"); throw new InvalidOperationException($"This group [{adGroup.DistinguishedName}] is a Critical System Active Directory Object and Disco ICT refuses to modify it");
// Update Description // Update Description
var groupDescription = $"Disco ICT: {actionGroup.Item1.GroupDescription}"; if (actionGroup.Item1.Configuration.UpdateDescription)
if (adGroupEntry.Entry.Properties.Value<string>("description") != groupDescription)
{ {
var adGroupEntryDescription = adGroupEntry.Entry.Properties["description"]; var groupDescription = $"Disco ICT: {actionGroup.Item1.GroupDescription}";
if (adGroupEntryDescription.Count > 0) if (adGroupEntry.Entry.Properties.Value<string>("description") != groupDescription)
adGroupEntryDescription.Clear(); {
adGroupEntryDescription.Add(groupDescription); var adGroupEntryDescription = adGroupEntry.Entry.Properties["description"];
if (adGroupEntryDescription.Count > 0)
adGroupEntryDescription.Clear();
adGroupEntryDescription.Add(groupDescription);
}
} }
// Sync Members // Sync Members
@@ -430,7 +433,7 @@ namespace Disco.Services.Interop.ActiveDirectory
internal class ADManagedGroupScheduledAction internal class ADManagedGroupScheduledAction
{ {
private Func<DiscoDataContext, IEnumerable<string>> memberResolver; private readonly Func<DiscoDataContext, IEnumerable<string>> memberResolver;
public ADManagedGroup ManagedGroup { get; private set; } public ADManagedGroup ManagedGroup { get; private set; }
public ADManagedGroupScheduledActionType ActionType { 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 public class ActivationService
{ {
private static readonly HttpClient httpClient;
private static readonly byte[] onlineServicesActivationKey; private static readonly byte[] onlineServicesActivationKey;
private readonly DiscoDataContext database; private readonly DiscoDataContext database;
@@ -29,6 +30,10 @@ namespace Disco.Services.Interop.DiscoServices
resourceStream.Read(key, 0, key.Length); resourceStream.Read(key, 0, key.Length);
onlineServicesActivationKey = key; onlineServicesActivationKey = key;
} }
httpClient = new HttpClient(new OnlineServicesAuthenticatedHandler())
{
BaseAddress = DiscoServiceHelpers.ActivationServiceUrl
};
} }
public ActivationService(DiscoDataContext database) public ActivationService(DiscoDataContext database)
@@ -42,6 +47,24 @@ namespace Disco.Services.Interop.DiscoServices
public Uri GetCallbackUrl() public Uri GetCallbackUrl()
=> new Uri(DiscoServiceHelpers.ActivationServiceUrl, "/api/callback"); => 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> /// <summary>
/// Begin the activation process /// Begin the activation process
/// </summary> /// </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) private static bool ValidateSignature(ChallengeResponse response)
{ {
var stream = new MemoryStream(); var stream = new MemoryStream();
@@ -237,7 +292,7 @@ namespace Disco.Services.Interop.DiscoServices
stream.Write(response.Challenge, 0, response.Challenge.Length); stream.Write(response.Challenge, 0, response.Challenge.Length);
stream.Write(response.ChallengeIv, 0, response.ChallengeIv.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) 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(activationId.ToByteArray(), 0, 16);
stream.Write(challenge, 0, challenge.Length); stream.Write(challenge, 0, challenge.Length);
stream.Write(challengeIv, 0, challengeIv.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; byte[] hash;
using (var hasher = SHA256.Create()) 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) private static byte[] Encrypt(byte[] privateKey, byte[] iv, long timeStamp, byte[] data)
{ {
var key = DeriveEncryptionKey(privateKey, iv, timeStamp); 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) private static byte[] Decrypt(byte[] privateKey, byte[] iv, long timeStamp, byte[] data)
{ {
var key = DeriveEncryptionKey(privateKey, iv, timeStamp); 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) private static byte[] DeriveEncryptionKey(byte[] privateKey, byte[] iv, long timeStamp)
{ {
using (var serverKey = CngKey.Import(onlineServicesActivationKey, CngKeyBlobFormat.EccPublicBlob)) 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() private static (byte[] privateKey, byte[] publicKey) GenerateActivationKey()
{ {
using (var key = CngKey.Create(CngAlgorithm.ECDiffieHellmanP521, null, new CngKeyCreationParameters 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.Data.Repository;
using Disco.Models.Repository;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
@@ -14,7 +16,7 @@ namespace Disco.Services.Interop.DiscoServices
{ {
public static class OnlineServicesAuthentication 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 readonly Guid deploymentId;
private static Guid? activationId; private static Guid? activationId;
private static byte[] key; private static byte[] key;
@@ -31,11 +33,12 @@ namespace Disco.Services.Interop.DiscoServices
} }
public static bool IsActivated => activationId.HasValue; public static bool IsActivated => activationId.HasValue;
internal static byte[] Key => key.ToArray() ?? throw new InvalidOperationException("Not activated");
public static string GetToken() public static string GetToken()
=> GetTokenAsync().Result; => GetTokenAsync().Result;
public async static Task<string> GetTokenAsync() public static async Task<string> GetTokenAsync()
{ {
var localExpires = tokenExpires; var localExpires = tokenExpires;
var localToken = token; var localToken = token;
@@ -56,40 +59,40 @@ namespace Disco.Services.Interop.DiscoServices
if (!IsActivated) if (!IsActivated)
throw new InvalidOperationException("Not activated"); throw new InvalidOperationException("Not activated");
using (var httpClient = new HttpClient()) var timeStamp = DateTime.UtcNow.ToUnixEpoc();
var iv = new byte[32];
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(iv);
var dataStream = new MemoryStream(16 + 16 + 8 + iv.Length);
dataStream.Write(deploymentId.ToByteArray(), 0, 16);
dataStream.Write(activationId.Value.ToByteArray(), 0, 16);
dataStream.Write(BitConverter.GetBytes(timeStamp), 0, 8);
dataStream.Write(iv, 0, iv.Length);
byte[] hash;
using (var hasher = SHA256.Create())
hash = hasher.ComputeHash(dataStream.ToArray());
var signature = ActivationService.SignHash(key, hash);
var body = new AuthenticationRequest()
{ {
httpClient.BaseAddress = DiscoServiceHelpers.ActivationServiceUrl; DeploymentId = deploymentId,
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); ActivationId = activationId.Value,
TimeStamp = timeStamp,
IV = iv,
Signature = signature,
};
var requestJson = JsonConvert.SerializeObject(body);
var timeStamp = DateTime.UtcNow.ToUnixEpoc(); using (var request = new ByteArrayContent(Encoding.UTF8.GetBytes(requestJson)))
var iv = new byte[32]; {
using (var rng = RandomNumberGenerator.Create()) request.Headers.ContentType = new MediaTypeHeaderValue("application/json");
rng.GetBytes(iv);
var dataStream = new MemoryStream(16 + 16 + 8 + iv.Length); using (var httpClient = new HttpClient())
dataStream.Write(deploymentId.ToByteArray(), 0, 16);
dataStream.Write(activationId.Value.ToByteArray(), 0, 16);
dataStream.Write(BitConverter.GetBytes(timeStamp), 0, 8);
dataStream.Write(iv, 0, iv.Length);
byte[] hash;
using (var hasher = SHA256.Create())
hash = hasher.ComputeHash(dataStream.ToArray());
var signature = ActivationService.SignHash(key, hash);
var body = new AuthenticationRequest()
{ {
DeploymentId = deploymentId, httpClient.BaseAddress = DiscoServiceHelpers.ActivationServiceUrl;
ActivationId = activationId.Value, httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
TimeStamp = timeStamp,
IV = iv,
Signature = signature,
};
var requestJson = JsonConvert.SerializeObject(body);
using (var request = new ByteArrayContent(Encoding.UTF8.GetBytes(requestJson)))
{
request.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync("/api/authenticate", request); var response = await httpClient.PostAsync("/api/authenticate", request);
@@ -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) internal static void UpdateActivation(DiscoDataContext database)
{ {
semaphore.Wait(); semaphore.Wait();
@@ -165,5 +194,25 @@ namespace Disco.Services.Interop.DiscoServices
public int? ExpiresInSeconds { get; set; } public int? ExpiresInSeconds { get; set; }
public string ErrorMessage { 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 => connection.Closed += ex =>
{ {
SystemLog.LogException("Online Services: Connection Closed", ex); if (ex != null)
SystemLog.LogException("Online Services: Connection Closed", ex);
else
SystemLog.LogInformation("Online Services: Connection Closed");
return Task.CompletedTask; return Task.CompletedTask;
}; };
connection.Reconnected += connectionId => connection.Reconnected += connectionId =>
@@ -53,7 +56,11 @@ namespace Disco.Services.Interop.DiscoServices
}; };
connection.Reconnecting += ex => connection.Reconnecting += ex =>
{ {
SystemLog.LogInformation("Online Services: Connection Reconnecting"); if (ex != null)
SystemLog.LogException("Online Services: Connection Reconnecting", ex);
else
SystemLog.LogInformation("Online Services: Connection Reconnecting");
return Task.CompletedTask; return Task.CompletedTask;
}; };
} }
@@ -1,6 +1,7 @@
using Disco.Data.Repository; using Disco.Data.Repository;
using Disco.Models.Repository; using Disco.Models.Repository;
using Disco.Models.Services.Interop.DiscoServices; using Disco.Models.Services.Interop.DiscoServices;
using Disco.Services.Devices.Enrolment;
using Disco.Services.Tasks; using Disco.Services.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
@@ -221,6 +222,10 @@ namespace Disco.Services.Interop.DiscoServices
RepairerLogged = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerLoggedDate : j.RepairerLoggedDate, RepairerLogged = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerLoggedDate : j.RepairerLoggedDate,
RepairerCompleted = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerCompletedDate : j.RepairerCompletedDate RepairerCompleted = j.JobType == JobType.JobTypeIds.HWar ? j.WarrantyRepairerCompletedDate : j.RepairerCompletedDate
}).ToList(); }).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(); 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 Disco.Services.Interop.DiscoServices;
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation;
using System.Xml.Linq; using System.Xml.Linq;
namespace Disco.Services.Interop.VicEduDept namespace Disco.Services.Interop.VicEduDept
{ {
public class VicSmart 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> /// <summary>
/// Queries DoE VicSmart Service to detect the current site. /// 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 DeviceSerialNumber { get; set; }
public string DeviceComputerNameFriendly public string DeviceComputerNameFriendly
{ {
get get => DeviceComputerName == null ? null : ActiveDirectory.FriendlyAccountId(DeviceComputerName);
{
return DeviceComputerName == null ? null : ActiveDirectory.FriendlyAccountId(DeviceComputerName);
}
set { } // for XML Serialization set { } // for XML Serialization
} }
public string DeviceComputerName { get; set; } public string DeviceComputerName { get; set; }
public string DeviceName
{
get => DeviceComputerNameFriendly ?? DeviceSerialNumber;
set { }
}
public string DeviceLocation { get; set; } public string DeviceLocation { get; set; }
public string DeviceDescription public string DeviceDescription
{ {
get get
{ {
StringBuilder sb = new StringBuilder(DeviceComputerNameFriendly); StringBuilder sb = new StringBuilder(DeviceName);
if (UserId != null) if (UserId != null)
sb.Append(" - ").Append(UserDisplayName).Append(" (").Append(UserIdFriendly).Append(")"); sb.Append(" - ").Append(UserDisplayName).Append(" (").Append(UserIdFriendly).Append(")");
@@ -60,6 +62,7 @@ namespace Disco.Services.Jobs.Noticeboards
} }
set { } // for XML Serialization set { } // for XML Serialization
} }
public IEnumerable<int> JobQueueIds { get; set; }
public string UserId { get; set; } public string UserId { get; set; }
public string UserIdFriendly public string UserIdFriendly
@@ -130,6 +133,7 @@ namespace Disco.Services.Jobs.Noticeboards
DeviceLocation = j.Device.Location, DeviceLocation = j.Device.Location,
DeviceProfileId = j.Device.DeviceProfileId, DeviceProfileId = j.Device.DeviceProfileId,
DeviceAddressId = j.Device.DeviceProfile.DefaultOrganisationAddress, DeviceAddressId = j.Device.DeviceProfile.DefaultOrganisationAddress,
JobQueueIds = j.JobQueues.Where(q => q.RemovedDate == null).Select(q => q.JobQueueId),
UserId = j.Device.AssignedUserId, UserId = j.Device.AssignedUserId,
UserDisplayName = j.Device.AssignedUser.DisplayName, UserDisplayName = j.Device.AssignedUser.DisplayName,
WaitingForUserAction = j.WaitingForUserAction.HasValue || ((j.JobMetaNonWarranty.AccountingChargeRequiredDate.HasValue || j.JobMetaNonWarranty.AccountingChargeAddedDate.HasValue) && !j.JobMetaNonWarranty.AccountingChargePaidDate.HasValue), 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"; public const string Name = "HeldDevices";
private readonly static List<string> MonitorJobProperties = new List<string>() { private static readonly List<string> MonitorJobProperties = new List<string>() {
"DeviceSerialNumber", "DeviceSerialNumber",
"UserId", "UserId",
"ExpectedClosedDate", "ExpectedClosedDate",
@@ -25,25 +25,25 @@ namespace Disco.Services.Jobs.Noticeboards
"DeviceReadyForReturn", "DeviceReadyForReturn",
"DeviceReturnedDate" "DeviceReturnedDate"
}; };
private readonly static List<string> MonitorJobMetaNonWarrantyProperties = new List<string>(){ private static readonly List<string> MonitorJobMetaNonWarrantyProperties = new List<string>(){
"AccountingChargeRequiredDate", "AccountingChargeRequiredDate",
"AccountingChargeAddedDate", "AccountingChargeAddedDate",
"AccountingChargePaidDate" "AccountingChargePaidDate"
}; };
private readonly static List<string> MonitorDeviceProperties = new List<string>(){ private static readonly List<string> MonitorDeviceProperties = new List<string>(){
"Location", "Location",
"DeviceProfileId", "DeviceProfileId",
"DeviceDomainId", "DeviceDomainId",
"AssignedUserId", "AssignedUserId",
}; };
private readonly static List<string> MonitorDeviceProfileProperties = new List<string>(){ private static readonly List<string> MonitorDeviceProfileProperties = new List<string>(){
"DefaultOrganisationAddress" "DefaultOrganisationAddress"
}; };
private readonly static List<string> MonitorUserProperties = new List<string>(){ private static readonly List<string> MonitorUserProperties = new List<string>(){
"DisplayName" "DisplayName"
}; };
private static Subject<Tuple<List<string>, List<string>>> BufferedUpdateStream; private static readonly Subject<Tuple<List<string>, List<string>>> BufferedUpdateStream;
static HeldDevices() static HeldDevices()
{ {
@@ -74,7 +74,9 @@ namespace Disco.Services.Jobs.Noticeboards
) || ) ||
(e.EntityType == typeof(User) && (e.EntityType == typeof(User) &&
(e.EventType == RepositoryMonitorEventType.Modified && e.ModifiedProperties.Any(p => MonitorUserProperties.Contains(p))) (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); .Subscribe(RepositoryEvent);
} }
@@ -147,6 +149,15 @@ namespace Disco.Services.Jobs.Noticeboards
.Select(j => j.DeviceSerialNumber) .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) if (deviceSerialNumbers.Count > 0 || userIds.Count > 0)
{ {
@@ -53,6 +53,14 @@ namespace Disco.Services.Plugins.Features.DetailsProvider
{ {
using (var originalImage = Image.FromStream(originalStream)) 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 resizedImage = originalImage.ResizeImage(192, Brushes.White))
{ {
using (var savedResizedImage = (MemoryStream)resizedImage.SaveJpg(85)) 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); .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="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.Mvc" version="4.0.30506.0" targetFramework="net45" />
<package id="Microsoft.AspNet.Razor" version="2.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.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.Connections.Abstractions" version="9.0.0" targetFramework="net462" />
<package id="Microsoft.AspNetCore.Http.Connections.Client" 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.Text.Json" version="9.0.0" targetFramework="net462" />
<package id="System.Threading.Channels" 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.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" /> <package id="WebActivatorEx" version="2.0.5" targetFramework="net45" />
</packages> </packages>
+4
View File
@@ -56,6 +56,10 @@
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <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" /> <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly> </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> </assemblyBinding>
</runtime> </runtime>
</configuration> </configuration>
@@ -12,10 +12,22 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.DiscoAdminAccount)] [DiscoAuthorize(Claims.DiscoAdminAccount)]
public partial class ActivationController : AuthorizedDatabaseController public partial class ActivationController : AuthorizedDatabaseController
{ {
[HttpPost] [HttpGet]
public virtual ActionResult TestCallback(CallbackModel model) 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] [HttpPost, ValidateAntiForgeryToken]
@@ -33,7 +45,7 @@ namespace Disco.Web.Areas.API.Controllers
RedirectUrl = challengeModel.RedirectUrl RedirectUrl = challengeModel.RedirectUrl
}; };
return View(model); return View(MVC.API.Activation.Views.Begin, model);
} }
[HttpGet] [HttpGet]
@@ -18,22 +18,20 @@ namespace Disco.Web.Areas.API.Controllers
{ {
public partial class DeviceBatchController : AuthorizedDatabaseController public partial class DeviceBatchController : AuthorizedDatabaseController
{ {
const string pName = "name"; private const string pName = "name";
const string pPurchaseDate = "purchasedate"; private const string pPurchaseDate = "purchasedate";
const string pSupplier = "supplier"; private const string pSupplier = "supplier";
const string pPurchaseDetails = "purchasedetails"; private const string pPurchaseDetails = "purchasedetails";
const string pUnitCost = "unitcost"; private const string pUnitCost = "unitcost";
const string pUnitQuantity = "unitquantity"; private const string pUnitQuantity = "unitquantity";
const string pDefaultDeviceModelId = "defaultdevicemodelid"; private const string pDefaultDeviceModelId = "defaultdevicemodelid";
const string pWarrantyValidUntil = "warrantyvaliduntil"; private const string pWarrantyValidUntil = "warrantyvaliduntil";
const string pWarrantyDetails = "warrantydetails"; private const string pWarrantyDetails = "warrantydetails";
const string pInsuredDate = "insureddate"; private const string pInsuredDate = "insureddate";
const string pInsuranceSupplier = "insurancesupplier"; private const string pInsuranceSupplier = "insurancesupplier";
const string pInsuredUntil = "insureduntil"; private const string pInsuredUntil = "insureduntil";
const string pInsuranceDetails = "insurancedetails"; private const string pInsuranceDetails = "insurancedetails";
const string pComments = "comments"; private const string pComments = "comments";
const string pDevicesLinkedGroup = "deviceslinkedgroup";
const string pAssignedUsersLinkedGroup = "assigneduserslinkedgroup";
[DiscoAuthorize(Claims.Config.DeviceBatch.Configure)] [DiscoAuthorize(Claims.Config.DeviceBatch.Configure)]
[HttpPost, ValidateAntiForgeryToken] [HttpPost, ValidateAntiForgeryToken]
@@ -94,12 +92,6 @@ namespace Disco.Web.Areas.API.Controllers
case pComments: case pComments:
UpdateComments(deviceBatch, value); UpdateComments(deviceBatch, value);
break; break;
case pDevicesLinkedGroup:
UpdateDevicesLinkedGroup(deviceBatch, value);
break;
case pAssignedUsersLinkedGroup:
UpdateAssignedUsersLinkedGroup(deviceBatch, value);
break;
default: default:
throw new Exception("Invalid Update Key"); throw new Exception("Invalid Update Key");
} }
@@ -224,18 +216,17 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.Config.DeviceBatch.Configure)] [DiscoAuthorize(Claims.Config.DeviceBatch.Configure)]
[HttpPost, ValidateAntiForgeryToken] [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 try
{ {
if (id < 0) if (id < 0)
throw new ArgumentOutOfRangeException("id"); throw new ArgumentOutOfRangeException(nameof(id));
var deviceBatch = Database.DeviceBatches.Find(id); var deviceBatch = Database.DeviceBatches.Find(id)
if (deviceBatch == null) ?? throw new ArgumentException("Invalid Device Batch Id", nameof(id));
throw new ArgumentException("Invalid Device Batch Id", "id");
var syncTaskStatus = UpdateDevicesLinkedGroup(deviceBatch, GroupId); var syncTaskStatus = UpdateDevicesLinkedGroup(deviceBatch, groupId, updateDescription ?? true);
if (redirect) if (redirect)
if (syncTaskStatus == null) if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceBatch.Index(deviceBatch.Id)); return RedirectToAction(MVC.Config.DeviceBatch.Index(deviceBatch.Id));
@@ -257,18 +248,17 @@ namespace Disco.Web.Areas.API.Controllers
} }
[DiscoAuthorize(Claims.Config.DeviceBatch.Configure)] [DiscoAuthorize(Claims.Config.DeviceBatch.Configure)]
[HttpPost, ValidateAntiForgeryToken] [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 try
{ {
if (id < 0) if (id < 0)
throw new ArgumentOutOfRangeException("id"); throw new ArgumentOutOfRangeException(nameof(id));
var deviceBatch = Database.DeviceBatches.Find(id); var deviceBatch = Database.DeviceBatches.Find(id)
if (deviceBatch == null) ?? throw new ArgumentException("Invalid Device Batch Id", nameof(id));
throw new ArgumentException("Invalid Device Batch Id", "id");
var syncTaskStatus = UpdateAssignedUsersLinkedGroup(deviceBatch, GroupId); var syncTaskStatus = UpdateAssignedUsersLinkedGroup(deviceBatch, groupId, updateDescription ?? true);
if (redirect) if (redirect)
if (syncTaskStatus == null) if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceBatch.Index(deviceBatch.Id)); return RedirectToAction(MVC.Config.DeviceBatch.Index(deviceBatch.Id));
@@ -486,9 +476,9 @@ namespace Disco.Web.Areas.API.Controllers
Database.SaveChanges(); 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) if (DeviceBatch.DevicesLinkedGroup != configJson)
{ {
@@ -503,9 +493,9 @@ namespace Disco.Web.Areas.API.Controllers
return null; 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) if (DeviceBatch.AssignedUsersLinkedGroup != configJson)
{ {
@@ -582,6 +572,7 @@ namespace Disco.Web.Areas.API.Controllers
var batchesInformation = Database.DeviceBatches.Select(db => new var batchesInformation = Database.DeviceBatches.Select(db => new
{ {
Id = db.Id,
Name = db.Name, Name = db.Name,
Comments = db.Comments, Comments = db.Comments,
PurchaseDate = db.PurchaseDate, PurchaseDate = db.PurchaseDate,
@@ -614,7 +605,7 @@ namespace Disco.Web.Areas.API.Controllers
description = bi.Comments ?? string.Empty, description = bi.Comments ?? string.Empty,
color = ColorTranslator.ToHtml(color), color = ColorTranslator.ToHtml(color),
image = Url.Action(MVC.API.DeviceModel.Image(bi.DefaultModelId)), 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.Entity; using System.Data.Entity;
using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
@@ -26,15 +28,15 @@ namespace Disco.Web.Areas.API.Controllers
{ {
public partial class DeviceController : AuthorizedDatabaseController public partial class DeviceController : AuthorizedDatabaseController
{ {
const string pDeviceProfileId = "deviceprofileid"; private const string pDeviceProfileId = "deviceprofileid";
const string pDeviceBatchId = "devicebatchid"; private const string pDeviceBatchId = "devicebatchid";
const string pAssetNumber = "assetnumber"; private const string pAssetNumber = "assetnumber";
const string pAssignedUserId = "assigneduserid"; private const string pAssignedUserId = "assigneduserid";
const string pLocation = "location"; private const string pLocation = "location";
const string pAllowUnauthenticatedEnrol = "allowunauthenticatedenrol"; private const string pAllowUnauthenticatedEnrol = "allowunauthenticatedenrol";
const string pDetailACAdapter = "detailacadapter"; private const string pDetailACAdapter = "detailacadapter";
const string pDetailBattery = "detailbattery"; private const string pDetailBattery = "detailbattery";
const string pDetailKeyboard = "detailkeyboard"; private const string pDetailKeyboard = "detailkeyboard";
[HttpPost, ValidateAntiForgeryToken] [HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult Update(string id, string key, string value = null, bool redirect = false) 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 #region Device Attachments
[DiscoAuthorize(Claims.Device.ShowAttachments), OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)] [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); var da = Database.DeviceAttachments.Find(id);
if (da != null) if (da != null)
@@ -553,7 +555,7 @@ namespace Disco.Web.Areas.API.Controllers
var filePath = da.RepositoryFilename(Database); var filePath = da.RepositoryFilename(Database);
if (System.IO.File.Exists(filePath)) if (System.IO.File.Exists(filePath))
{ {
return File(filePath, da.MimeType, da.Filename); return File(filePath, da.MimeType, (inline ?? false) ? null : da.Filename);
} }
else else
{ {
@@ -563,6 +565,44 @@ namespace Disco.Web.Areas.API.Controllers
return HttpNotFound("Invalid Attachment Number"); 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)] [DiscoAuthorize(Claims.Device.ShowAttachments), OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
public virtual ActionResult AttachmentThumbnail(int id) public virtual ActionResult AttachmentThumbnail(int id)
{ {
@@ -166,19 +166,17 @@ namespace Disco.Web.Areas.API.Controllers
} }
[DiscoAuthorize(Claims.Config.DeviceFlag.Configure)] [DiscoAuthorize(Claims.Config.DeviceFlag.Configure)]
[HttpPost, ValidateAntiForgeryToken] [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 try
{ {
if (id < 0) if (id < 0)
throw new ArgumentOutOfRangeException("id"); throw new ArgumentOutOfRangeException(nameof(id));
var deviceFlag = Database.DeviceFlags.Find(id); var deviceFlag = Database.DeviceFlags.Find(id)
if (deviceFlag == null) ?? throw new ArgumentException("Invalid Device Flag Id", nameof(id));
throw new ArgumentException("Invalid Device Flag Id", "id");
var syncTaskStatus = UpdateDevicesLinkedGroup(deviceFlag, groupId, filterBeginDate, updateDescription ?? true);
var syncTaskStatus = UpdateDevicesLinkedGroup(deviceFlag, GroupId, FilterBeginDate);
if (redirect) if (redirect)
if (syncTaskStatus == null) if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceFlag.Index(deviceFlag.Id)); return RedirectToAction(MVC.Config.DeviceFlag.Index(deviceFlag.Id));
@@ -200,25 +198,23 @@ namespace Disco.Web.Areas.API.Controllers
} }
[DiscoAuthorize(Claims.Config.DeviceFlag.Configure)] [DiscoAuthorize(Claims.Config.DeviceFlag.Configure)]
[HttpPost, ValidateAntiForgeryToken] [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 try
{ {
if (id < 0) if (id < 0)
throw new ArgumentOutOfRangeException("id"); throw new ArgumentOutOfRangeException(nameof(id));
var DeviceFlag = Database.DeviceFlags.Find(id); var deviceFlag = Database.DeviceFlags.Find(id)
if (DeviceFlag == null) ?? throw new ArgumentException("Invalid Device Flag Id", nameof(id));
throw new ArgumentException("Invalid Device Flag Id", "id");
var syncTaskStatus = UpdateAssignedUserLinkedGroup(deviceFlag, groupId, filterBeginDate, updateDescription ?? true);
var syncTaskStatus = UpdateAssignedUserLinkedGroup(DeviceFlag, GroupId, FilterBeginDate);
if (redirect) if (redirect)
if (syncTaskStatus == null) if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceFlag.Index(DeviceFlag.Id)); return RedirectToAction(MVC.Config.DeviceFlag.Index(deviceFlag.Id));
else 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)); return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId));
} }
else else
@@ -340,9 +336,9 @@ namespace Disco.Web.Areas.API.Controllers
DeviceFlagService.Update(Database, deviceFlag); 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) if (deviceFlag.DevicesLinkedGroup != configJson)
{ {
@@ -358,9 +354,9 @@ namespace Disco.Web.Areas.API.Controllers
return null; 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) if (deviceFlag.DeviceUsersLinkedGroup != configJson)
{ {
@@ -38,8 +38,6 @@ namespace Disco.Web.Areas.API.Controllers
private const string pAssignedUserLocalAdmin = "assigneduserlocaladmin"; private const string pAssignedUserLocalAdmin = "assigneduserlocaladmin";
private const string pSetAssignedUserForLogon = "setassigneduserforlogon"; private const string pSetAssignedUserForLogon = "setassigneduserforlogon";
private const string pAllowUntrustedReimageJobEnrolment = "allowuntrustedreimagejobrnrolment"; private const string pAllowUntrustedReimageJobEnrolment = "allowuntrustedreimagejobrnrolment";
private const string pDevicesLinkedGroup = "deviceslinkedgroup";
private const string pAssignedUsersLinkedGroup = "assigneduserslinkedgroup";
[DiscoAuthorize(Claims.Config.DeviceProfile.Configure)] [DiscoAuthorize(Claims.Config.DeviceProfile.Configure)]
[HttpPost, ValidateAntiForgeryToken] [HttpPost, ValidateAntiForgeryToken]
@@ -106,12 +104,6 @@ namespace Disco.Web.Areas.API.Controllers
case pAllowUntrustedReimageJobEnrolment: case pAllowUntrustedReimageJobEnrolment:
UpdateAllowUntrustedReimageJobEnrolment(deviceProfile, value); UpdateAllowUntrustedReimageJobEnrolment(deviceProfile, value);
break; break;
case pDevicesLinkedGroup:
UpdateDevicesLinkedGroup(deviceProfile, value);
break;
case pAssignedUsersLinkedGroup:
UpdateAssignedUsersLinkedGroup(deviceProfile, value);
break;
default: default:
throw new Exception("Invalid Update Key"); throw new Exception("Invalid Update Key");
} }
@@ -385,18 +377,17 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.Config.DeviceProfile.Configure)] [DiscoAuthorize(Claims.Config.DeviceProfile.Configure)]
[HttpPost, ValidateAntiForgeryToken] [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 try
{ {
if (id < 0) if (id < 0)
throw new ArgumentOutOfRangeException("id"); throw new ArgumentOutOfRangeException(nameof(id));
var deviceProfile = Database.DeviceProfiles.Find(id); var deviceProfile = Database.DeviceProfiles.Find(id)
if (deviceProfile == null) ?? throw new ArgumentException("Invalid Device Profile Id", nameof(id));
throw new ArgumentException("Invalid Device Profile Id", "id");
var syncTaskStatus = UpdateDevicesLinkedGroup(deviceProfile, GroupId); var syncTaskStatus = UpdateDevicesLinkedGroup(deviceProfile, groupId, updateDescription ?? true);
if (redirect) if (redirect)
if (syncTaskStatus == null) if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceProfile.Index(deviceProfile.Id)); return RedirectToAction(MVC.Config.DeviceProfile.Index(deviceProfile.Id));
@@ -418,18 +409,17 @@ namespace Disco.Web.Areas.API.Controllers
} }
[DiscoAuthorize(Claims.Config.DeviceProfile.Configure)] [DiscoAuthorize(Claims.Config.DeviceProfile.Configure)]
[HttpPost, ValidateAntiForgeryToken] [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 try
{ {
if (id < 0) if (id < 0)
throw new ArgumentOutOfRangeException("id"); throw new ArgumentOutOfRangeException(nameof(id));
var deviceProfile = Database.DeviceProfiles.Find(id); var deviceProfile = Database.DeviceProfiles.Find(id)
if (deviceProfile == null) ?? throw new ArgumentException("Invalid Device Profile Id", nameof(id));
throw new ArgumentException("Invalid Device Profile Id", "id");
var syncTaskStatus = UpdateAssignedUsersLinkedGroup(deviceProfile, GroupId); var syncTaskStatus = UpdateAssignedUsersLinkedGroup(deviceProfile, groupId, updateDescription ?? true);
if (redirect) if (redirect)
if (syncTaskStatus == null) if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DeviceProfile.Index(deviceProfile.Id)); return RedirectToAction(MVC.Config.DeviceProfile.Index(deviceProfile.Id));
@@ -725,9 +715,9 @@ namespace Disco.Web.Areas.API.Controllers
throw new Exception("Invalid Boolean Value"); 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) if (deviceProfile.DevicesLinkedGroup != configJson)
{ {
@@ -742,9 +732,9 @@ namespace Disco.Web.Areas.API.Controllers
return null; 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) if (deviceProfile.AssignedUsersLinkedGroup != configJson)
{ {
@@ -20,6 +20,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Data.Entity; using System.Data.Entity;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Web; using System.Web;
@@ -254,18 +255,17 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)]
[HttpPost, ValidateAntiForgeryToken] [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 try
{ {
if (string.IsNullOrWhiteSpace(id)) if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException("id"); throw new ArgumentNullException(nameof(id));
var documentTemplate = Database.DocumentTemplates.Find(id); var documentTemplate = Database.DocumentTemplates.Find(id)
if (documentTemplate == null) ?? throw new ArgumentException("Invalid Document Template Id", nameof(id));
throw new ArgumentException("Invalid Document Template Id", "id");
var syncTaskStatus = UpdateDevicesLinkedGroup(documentTemplate, GroupId, FilterBeginDate); var syncTaskStatus = UpdateDevicesLinkedGroup(documentTemplate, groupId, filterBeginDate, updateDescription ?? true);
if (redirect) if (redirect)
if (syncTaskStatus == null) if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id));
@@ -288,18 +288,17 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)]
[HttpPost, ValidateAntiForgeryToken] [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 try
{ {
if (string.IsNullOrWhiteSpace(id)) if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException("id"); throw new ArgumentNullException(nameof(id));
var documentTemplate = Database.DocumentTemplates.Find(id); var documentTemplate = Database.DocumentTemplates.Find(id)
if (documentTemplate == null) ?? throw new ArgumentException("Invalid Document Template Id", nameof(id));
throw new ArgumentException("Invalid Document Template Id", "id");
var syncTaskStatus = UpdateUsersLinkedGroup(documentTemplate, GroupId, FilterBeginDate); var syncTaskStatus = UpdateUsersLinkedGroup(documentTemplate, groupId, filterBeginDate, updateDescription ?? true);
if (redirect) if (redirect)
if (syncTaskStatus == null) if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id));
@@ -469,16 +468,16 @@ namespace Disco.Web.Areas.API.Controllers
Database.SaveChanges(); 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(); Database.SaveChanges();
var managedGroup = DocumentTemplateDevicesManagedGroup.Initialize(DocumentTemplate); var managedGroup = DocumentTemplateDevicesManagedGroup.Initialize(documentTemplate);
if (managedGroup != null) // Sync Group if (managedGroup != null) // Sync Group
return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); return ADManagedGroupsSyncTask.ScheduleSync(managedGroup);
} }
@@ -486,16 +485,16 @@ namespace Disco.Web.Areas.API.Controllers
return null; 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(); Database.SaveChanges();
var managedGroup = DocumentTemplateUsersManagedGroup.Initialize(DocumentTemplate); var managedGroup = DocumentTemplateUsersManagedGroup.Initialize(documentTemplate);
if (managedGroup != null) // Sync Group if (managedGroup != null) // Sync Group
return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); return ADManagedGroupsSyncTask.ScheduleSync(managedGroup);
} }
@@ -1775,7 +1774,7 @@ namespace Disco.Web.Areas.API.Controllers
} }
[HttpPost, ValidateAntiForgeryToken] [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 _); 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(); 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)] [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 #endregion
#region Handlers #region Handlers
[HttpPost, ValidateAntiForgeryToken] [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)); var handlerManifest = Plugins.GetPluginFeature(handlerId, typeof(DocumentHandlerProviderFeature));
@@ -1907,9 +1982,9 @@ namespace Disco.Web.Areas.API.Controllers
} }
[HttpPost, ValidateAntiForgeryToken] [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)) var handlers = Plugins.GetPluginFeatures(typeof(DocumentHandlerProviderFeature))
.SelectMany(f => .SelectMany(f =>
@@ -4,8 +4,11 @@ using Disco.Models.Services.Documents;
using Disco.Services; using Disco.Services;
using Disco.Services.Authorization; using Disco.Services.Authorization;
using Disco.Services.Documents; using Disco.Services.Documents;
using Disco.Services.Plugins;
using Disco.Services.Plugins.Features.DocumentHandlerProvider;
using Disco.Services.Users; using Disco.Services.Users;
using Disco.Services.Web; using Disco.Services.Web;
using Disco.Web.Areas.API.Models.DocumentTemplate;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -16,12 +19,12 @@ namespace Disco.Web.Areas.API.Controllers
{ {
public partial class DocumentTemplatePackageController : AuthorizedDatabaseController public partial class DocumentTemplatePackageController : AuthorizedDatabaseController
{ {
const string pDescription = "description"; private const string pDescription = "description";
const string pScope = "scope"; private const string pScope = "scope";
const string pFilterExpression = "filterexpression"; private const string pFilterExpression = "filterexpression";
const string pOnGenerateExpression = "ongenerateexpression"; private const string pOnGenerateExpression = "ongenerateexpression";
const string pIsHidden = "ishidden"; private const string pIsHidden = "ishidden";
const string pInsertBlankPages = "insertblankpages"; private const string pInsertBlankPages = "insertblankpages";
[DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)]
[HttpPost, ValidateAntiForgeryToken] [HttpPost, ValidateAntiForgeryToken]
@@ -393,7 +396,7 @@ namespace Disco.Web.Areas.API.Controllers
} }
[HttpPost, ValidateAntiForgeryToken] [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)) if (string.IsNullOrWhiteSpace(id))
throw new ArgumentNullException(nameof(id)); throw new ArgumentNullException(nameof(id));
@@ -432,7 +435,7 @@ namespace Disco.Web.Areas.API.Controllers
} }
Database.SaveChanges(); 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)] [DiscoAuthorize(Claims.Config.DocumentTemplate.Delete)]
@@ -470,5 +473,65 @@ namespace Disco.Web.Areas.API.Controllers
} }
#endregion #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); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data.Entity; using System.Data.Entity;
using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web.Caching; using System.Web.Caching;
@@ -1925,7 +1927,7 @@ namespace Disco.Web.Areas.API.Controllers
#region Job Attachments #region Job Attachments
[DiscoAuthorize(Claims.Job.ShowAttachments), OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)] [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); var ja = Database.JobAttachments.Find(id);
if (ja != null) if (ja != null)
@@ -1933,7 +1935,7 @@ namespace Disco.Web.Areas.API.Controllers
var filePath = ja.RepositoryFilename(Database); var filePath = ja.RepositoryFilename(Database);
if (System.IO.File.Exists(filePath)) if (System.IO.File.Exists(filePath))
{ {
return File(filePath, ja.MimeType, ja.Filename); return File(filePath, ja.MimeType, (inline ?? false) ? null : ja.Filename);
} }
else else
{ {
@@ -1943,6 +1945,44 @@ namespace Disco.Web.Areas.API.Controllers
return HttpNotFound("Invalid Attachment Number"); 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)] [DiscoAuthorize(Claims.Job.ShowAttachments), OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
public virtual ActionResult AttachmentThumbnail(int id) public virtual ActionResult AttachmentThumbnail(int id)
{ {
@@ -7,6 +7,7 @@ using Disco.Services.Messaging;
using Disco.Services.Users; using Disco.Services.Users;
using Disco.Services.Web; using Disco.Services.Web;
using Disco.Web.Areas.API.Models.Shared; using Disco.Web.Areas.API.Models.Shared;
using Disco.Web.Models.Shared;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
@@ -309,16 +310,99 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorizeAny(Claims.Config.System.ConfigureActiveDirectory, Claims.Config.DeviceProfile.Configure)] [DiscoAuthorizeAny(Claims.Config.System.ConfigureActiveDirectory, Claims.Config.DeviceProfile.Configure)]
[HttpPost, ValidateAntiForgeryToken] [HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult DomainOrganisationalUnits() public virtual ActionResult DomainOrganisationalUnitTree(string expandNode = null)
{ {
var domainOUs = ActiveDirectory.RetrieveADOrganisationalUnitStructure() List<FancyTreeNode> nodes;
.Select(d => new Models.System.DomainOrganisationalUnitsModel() { Domain = d.Item1, OrganisationalUnits = d.Item2 })
.Select(ous => ous.ToFancyTreeNode()).ToList(); 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() return new JsonResult()
{ {
Data = domainOUs, Data = nodes,
JsonRequestBehavior = JsonRequestBehavior.AllowGet, 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 MaxJsonLength = int.MaxValue
}; };
} }
@@ -9,6 +9,8 @@ using Disco.Services.Users;
using Disco.Services.Web; using Disco.Services.Web;
using System; using System;
using System.Data.Entity; using System.Data.Entity;
using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web.Mvc; using System.Web.Mvc;
@@ -106,7 +108,7 @@ namespace Disco.Web.Areas.API.Controllers
[DiscoAuthorize(Claims.User.ShowAttachments)] [DiscoAuthorize(Claims.User.ShowAttachments)]
[OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)] [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); var ua = Database.UserAttachments.Find(id);
if (ua != null) if (ua != null)
@@ -114,7 +116,7 @@ namespace Disco.Web.Areas.API.Controllers
var filePath = ua.RepositoryFilename(Database); var filePath = ua.RepositoryFilename(Database);
if (System.IO.File.Exists(filePath)) if (System.IO.File.Exists(filePath))
{ {
return File(filePath, ua.MimeType, ua.Filename); return File(filePath, ua.MimeType, (inline ?? false) ? null : ua.Filename);
} }
else else
{ {
@@ -124,6 +126,46 @@ namespace Disco.Web.Areas.API.Controllers
return HttpNotFound("Invalid Attachment Number"); 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)] [DiscoAuthorize(Claims.User.ShowAttachments)]
[OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)] [OutputCache(Location = System.Web.UI.OutputCacheLocation.Client, Duration = 172800)]
public virtual ActionResult AttachmentThumbnail(int id) public virtual ActionResult AttachmentThumbnail(int id)
@@ -166,25 +166,23 @@ namespace Disco.Web.Areas.API.Controllers
} }
[DiscoAuthorize(Claims.Config.UserFlag.Configure)] [DiscoAuthorize(Claims.Config.UserFlag.Configure)]
[HttpPost, ValidateAntiForgeryToken] [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 try
{ {
if (id < 0) if (id < 0)
throw new ArgumentOutOfRangeException(nameof(id)); throw new ArgumentOutOfRangeException(nameof(id));
var UserFlag = Database.UserFlags.Find(id); var userFlag = Database.UserFlags.Find(id)
if (UserFlag == null) ?? throw new ArgumentException("Invalid User Flag Id", nameof(id));
throw new ArgumentException("Invalid User Flag Id", nameof(id));
var syncTaskStatus = UpdateAssignedUsersLinkedGroup(userFlag, groupId, filterBeginDate, updateDescription ?? true);
var syncTaskStatus = UpdateAssignedUsersLinkedGroup(UserFlag, GroupId, FilterBeginDate);
if (redirect) if (redirect)
if (syncTaskStatus == null) if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.UserFlag.Index(UserFlag.Id)); return RedirectToAction(MVC.Config.UserFlag.Index(userFlag.Id));
else 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)); return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId));
} }
else else
@@ -200,25 +198,23 @@ namespace Disco.Web.Areas.API.Controllers
} }
[DiscoAuthorize(Claims.Config.UserFlag.Configure)] [DiscoAuthorize(Claims.Config.UserFlag.Configure)]
[HttpPost, ValidateAntiForgeryToken] [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 try
{ {
if (id < 0) if (id < 0)
throw new ArgumentOutOfRangeException(nameof(id)); throw new ArgumentOutOfRangeException(nameof(id));
var UserFlag = Database.UserFlags.Find(id); var userFlag = Database.UserFlags.Find(id)
if (UserFlag == null) ?? throw new ArgumentException("Invalid User Flag Id", nameof(id));
throw new ArgumentException("Invalid User Flag Id", nameof(id));
var syncTaskStatus = UpdateAssignedUserDevicesLinkedGroup(userFlag, groupId, filterBeginDate, updateDescription ?? true);
var syncTaskStatus = UpdateAssignedUserDevicesLinkedGroup(UserFlag, GroupId, FilterBeginDate);
if (redirect) if (redirect)
if (syncTaskStatus == null) if (syncTaskStatus == null)
return RedirectToAction(MVC.Config.UserFlag.Index(UserFlag.Id)); return RedirectToAction(MVC.Config.UserFlag.Index(userFlag.Id));
else 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)); return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId));
} }
else else
@@ -340,19 +336,19 @@ namespace Disco.Web.Areas.API.Controllers
UserFlagService.Update(Database, UserFlag); 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; userFlag.UsersLinkedGroup = configJson;
UserFlagService.Update(Database, UserFlag); UserFlagService.Update(Database, userFlag);
if (UserFlag.UsersLinkedGroup != null) if (userFlag.UsersLinkedGroup != null)
{ {
// Sync Group // Sync Group
if (UserFlagUsersManagedGroup.TryGetManagedGroup(UserFlag, out var managedGroup)) if (UserFlagUsersManagedGroup.TryGetManagedGroup(userFlag, out var managedGroup))
{ {
return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); return ADManagedGroupsSyncTask.ScheduleSync(managedGroup);
} }
@@ -361,19 +357,19 @@ namespace Disco.Web.Areas.API.Controllers
return null; 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; userFlag.UserDevicesLinkedGroup = configJson;
UserFlagService.Update(Database, UserFlag); UserFlagService.Update(Database, userFlag);
if (UserFlag.UserDevicesLinkedGroup != null) if (userFlag.UserDevicesLinkedGroup != null)
{ {
// Sync Group // Sync Group
if (UserFlagUserDevicesManagedGroup.TryGetManagedGroup(UserFlag, out var managedGroup)) if (UserFlagUserDevicesManagedGroup.TryGetManagedGroup(userFlag, out var managedGroup))
{ {
return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); return ADManagedGroupsSyncTask.ScheduleSync(managedGroup);
} }
@@ -5,9 +5,12 @@ namespace Disco.Web.Areas.API.Models.Activation
{ {
public class CallbackModel public class CallbackModel
{ {
public string Origin { get; set; }
public Guid DeploymentId { get; set; } public Guid DeploymentId { get; set; }
public Guid CorrelationId { get; set; } public Guid CorrelationId { get; set; }
[StringLength(50)] [StringLength(50)]
public string UserId { get; set; } 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.Models.UI.Config.Enrolment;
using Disco.Services.Authorization; 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.Plugins.Features.UIExtension;
using Disco.Services.Web; using Disco.Services.Web;
using System;
using System.Linq; using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
@@ -12,10 +18,30 @@ namespace Disco.Web.Areas.Config.Controllers
[DiscoAuthorize(Claims.Config.Enrolment.Show)] [DiscoAuthorize(Claims.Config.Enrolment.Show)]
public virtual ActionResult Index() 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() var m = new Models.Enrolment.IndexModel()
{ {
MacSshUsername = Database.DiscoConfiguration.Bootstrapper.MacSshUsername, MacSshUsername = Database.DiscoConfiguration.Bootstrapper.MacSshUsername,
PendingTimeoutMinutes = (int)Database.DiscoConfiguration.Bootstrapper.PendingTimeout.TotalMinutes, 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 // UI Extensions
@@ -1,7 +1,9 @@
using Disco.Models.UI.Config.JobPreferences; using Disco.Models.UI.Config.JobPreferences;
using Disco.Services.Authorization; using Disco.Services.Authorization;
using Disco.Services.Jobs.JobQueues;
using Disco.Services.Plugins.Features.UIExtension; using Disco.Services.Plugins.Features.UIExtension;
using Disco.Services.Web; using Disco.Services.Web;
using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
namespace Disco.Web.Areas.Config.Controllers namespace Disco.Web.Areas.Config.Controllers
@@ -23,6 +25,9 @@ namespace Disco.Web.Areas.Config.Controllers
OnCreateExpression = Database.DiscoConfiguration.JobPreferences.OnCreateExpression, OnCreateExpression = Database.DiscoConfiguration.JobPreferences.OnCreateExpression,
OnDeviceReadyForReturnExpression = Database.DiscoConfiguration.JobPreferences.OnDeviceReadyForReturnExpression, OnDeviceReadyForReturnExpression = Database.DiscoConfiguration.JobPreferences.OnDeviceReadyForReturnExpression,
OnCloseExpression = Database.DiscoConfiguration.JobPreferences.OnCloseExpression, 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 // UI Extensions
@@ -30,7 +30,9 @@ namespace Disco.Web.Areas.Config.Controllers
DeploymentId = Guid.Parse(Database.DiscoConfiguration.DeploymentId), DeploymentId = Guid.Parse(Database.DiscoConfiguration.DeploymentId),
CorrelationId = Guid.NewGuid(), CorrelationId = Guid.NewGuid(),
UserId = CurrentUser.UserId, UserId = CurrentUser.UserId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
}; };
model.Proof = service.CalculateCallbackProof(model.CorrelationId, model.UserId, model.Timestamp);
return View(model); return View(model);
} }
@@ -1,4 +1,5 @@
using Disco.Models.UI.Config.Enrolment; using Disco.Models.UI.Config.Enrolment;
using System;
namespace Disco.Web.Areas.Config.Models.Enrolment namespace Disco.Web.Areas.Config.Models.Enrolment
{ {
@@ -6,5 +7,12 @@ namespace Disco.Web.Areas.Config.Models.Enrolment
{ {
public string MacSshUsername { get; set; } public string MacSshUsername { get; set; }
public int PendingTimeoutMinutes { 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.Models.UI.Config.JobPreferences;
using Disco.Services.Extensions; using Disco.Services.Extensions;
using System; using System;
@@ -32,21 +31,9 @@ namespace Disco.Web.Areas.Config.Models.JobPreferences
return UIHelpers.NoticeboardThemes.ToList(); return UIHelpers.NoticeboardThemes.ToList();
} }
public Lazy<List<Disco.Models.Repository.DeviceProfile>> DeviceProfiles = new Lazy<List<Disco.Models.Repository.DeviceProfile>>(() => public List<Disco.Models.Repository.DeviceProfile> DeviceProfiles { get; set; }
{ public List<Disco.Models.BI.Config.OrganisationAddress> OrganisationAddresses { get; set; }
using (var database = new DiscoDataContext()) public List<Disco.Models.Repository.JobQueue> JobQueues { get; set; }
{
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<KeyValuePair<int, string>> LongRunningJobDaysThresholdOptions() public List<KeyValuePair<int, string>> LongRunningJobDaysThresholdOptions()
{ {
@@ -4,9 +4,11 @@ namespace Disco.Web.Areas.Config.Models.SystemConfig
{ {
public class ActivateModel public class ActivateModel
{ {
public Uri CallbackUrl { get; set; }
public Guid DeploymentId { get; set; } public Guid DeploymentId { get; set; }
public Guid CorrelationId { get; set; } public Guid CorrelationId { get; set; }
public string UserId { 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> </span>
</div> </div>
<button id="changeOrganisationalUnit" type="button" class="button small">Change</button>@AjaxHelpers.AjaxLoader() <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"> <div id="dialogOrganisationalUnit_Loading">
@AjaxHelpers.AjaxLoader() Loading Organisational Units @AjaxHelpers.AjaxLoader() Loading Organisational Units
</div> </div>
@@ -690,6 +690,7 @@
$enforceCheckbox = $('#enforceOrganisationalUnit'); $enforceCheckbox = $('#enforceOrganisationalUnit');
const body = new FormData(); const body = new FormData();
body.append('expandNode', ouValue);
body.append('__RequestVerificationToken', document.body.dataset.antiforgery); body.append('__RequestVerificationToken', document.body.dataset.antiforgery);
const response = await fetch($dialog.attr('data-url'), { const response = await fetch($dialog.attr('data-url'), {
method: 'POST', method: 'POST',
@@ -698,17 +699,23 @@
const data = await response.json(); const data = await response.json();
$loading.hide(); $loading.hide();
// Make 'Domains' unselectable
$.each(data, function (i, node) {
node.unselectable = true;
});
ouTree = $ouTree.fancytree({ ouTree = $ouTree.fancytree({
source: data, source: data,
checkbox: false, checkbox: false,
selectMode: 1, selectMode: 1,
keyboard: false, 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'); }).fancytree('getTree');
ouTree.$container.css('position', 'relative'); ouTree.$container.css('position', 'relative');
@@ -2041,7 +2041,18 @@ WriteLiteral(" data-url=\"");
#line 623 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 623 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Url.Action(MVC.API.System.DomainOrganisationalUnits())); 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()));
#line default #line default
@@ -2181,52 +2192,59 @@ WriteLiteral(">\r\n $(function () {\r\n
" = $(\'#treeOrganisationalUnit\');\r\n $dialog.cs" + " = $(\'#treeOrganisationalUnit\');\r\n $dialog.cs" +
"s(\'overflow\', \'visible\');\r\n\r\n $enforceCheckbo" + "s(\'overflow\', \'visible\');\r\n\r\n $enforceCheckbo" +
"x = $(\'#enforceOrganisationalUnit\');\r\n\r\n cons" + "x = $(\'#enforceOrganisationalUnit\');\r\n\r\n cons" +
"t body = new FormData();\r\n body.append(\'__Req" + "t body = new FormData();\r\n body.append(\'expan" +
"uestVerificationToken\', document.body.dataset.antiforgery);\r\n " + "dNode\', ouValue);\r\n body.append(\'__RequestVer" +
" const response = await fetch($dialog.attr(\'data-url\'), {\r\n " + "ificationToken\', document.body.dataset.antiforgery);\r\n " +
" method: \'POST\',\r\n " + " const response = await fetch($dialog.attr(\'data-url\'), {\r\n " +
" body: body\r\n });\r\n " + " method: \'POST\',\r\n " +
" const data = await response.json();\r\n " + " body: body\r\n });\r\n " +
" $loading.hide();\r\n\r\n // Make \'D" + " const data = await response.json();\r\n " +
"omains\' unselectable\r\n $.each(data, function " + " $loading.hide();\r\n\r\n ouTree = $ouTree." +
"(i, node) {\r\n node.unselectable = true;\r\n" + "fancytree({\r\n source: data,\r\n " +
" });\r\n\r\n o" + " checkbox: false,\r\n " +
"uTree = $ouTree.fancytree({\r\n source: dat" + " selectMode: 1,\r\n keyboard: false,\r\n" +
"a,\r\n checkbox: false,\r\n " + " fx: null,\r\n " +
" selectMode: 1,\r\n ke" + " lazyload: function (event, data) {\r\n " +
"yboard: false,\r\n fx: null\r\n " + " data.result = {\r\n url:" +
" }).fancytree(\'getTree\');\r\n\r\n " + " $dialog.attr(\'data-urllazy\'),\r\n " +
" ouTree.$container.css(\'position\', \'relative\');\r\n\r\n " + "method: \'POST\',\r\n cache: false,\r\n" +
" // Set Buttons\r\n $dialog.dialog(\'" + " data: {\r\n " +
"option\', \'buttons\', {\r\n \'Use Default Comp" + " node: data.node.key,\r\n " +
"uters Container\': function () {\r\n var" + " \'__RequestVerificationToken\': document.body.dataset.antif" +
" $this = $(this);\r\n $this.css(\'overfl" + "orgery\r\n }\r\n " +
"ow\', \'hidden\');\r\n $this.dialog(\"disab" + " };\r\n }\r\n " +
"le\");\r\n $this.dialog(\"option\", \"butto" + " }).fancytree(\'getTree\');\r\n\r\n " +
"ns\", null);\r\n ouSet(\'\');\r\n " + " ouTree.$container.css(\'position\', \'relative\');\r\n\r\n " +
" },\r\n \'Save\':" + " // Set Buttons\r\n $dialog.dial" +
" function () {\r\n var node = ouTree.ge" + "og(\'option\', \'buttons\', {\r\n \'Use Default " +
"tActiveNode();\r\n if (node && node.key" + "Computers Container\': function () {\r\n " +
".substr(0, 3).toLowerCase() == \'ou=\') {\r\n " + " var $this = $(this);\r\n $this.css(\'ov" +
" var $this = $(this);\r\n $" + "erflow\', \'hidden\');\r\n $this.dialog(\"d" +
"this.css(\'overflow\', \'hidden\');\r\n " + "isable\");\r\n $this.dialog(\"option\", \"b" +
" $this.dialog(\"disable\");\r\n $this" + "uttons\", null);\r\n ouSet(\'\');\r\n " +
".dialog(\"option\", \"buttons\", null);\r\n " + " },\r\n \'Sa" +
" ouSet(node.key);\r\n } else {\r\n " + "ve\': function () {\r\n var node = ouTre" +
" alert(\'Select an Organisational Uni" + "e.getActiveNode();\r\n if (node && node" +
"t to Save\')\r\n }\r\n " + ".key.substr(0, 3).toLowerCase() == \'ou=\') {\r\n " +
" }\r\n });\r\n\r\n " + " var $this = $(this);\r\n " +
" // Expand\r\n expandAndFo" + " $this.css(\'overflow\', \'hidden\');\r\n " +
"cusNode(ouValue);\r\n\r\n ouTree.options.fx = { h" + " $this.dialog(\"disable\");\r\n $" +
"eight: \"toggle\", duration: 200 };\r\n }\r\n " + "this.dialog(\"option\", \"buttons\", null);\r\n " +
" $dialog.dialog(\'open\');\r\n\r\n " + " ouSet(node.key);\r\n } else {\r" +
" $enforceCheckbox.prop(\'checked\', $(\'#DeviceProfile_EnforceOrganisationalUnit\')." + "\n alert(\'Select an Organisational" +
"prop(\'checked\'));\r\n };\r\n\r\n " + " Unit to Save\')\r\n }\r\n " +
" $(\'#changeOrganisationalUnit\').click(ouChange);\r\n });\r\n " + " }\r\n });\r\n\r\n " +
" </script>\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_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 else
{ {
@@ -2245,7 +2263,7 @@ WriteLiteral(">\r\n <span>\r\n");
WriteLiteral(" "); WriteLiteral(" ");
#line 757 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 764 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Model.FriendlyOrganisationalUnitName); Write(Model.FriendlyOrganisationalUnitName);
@@ -2254,7 +2272,7 @@ WriteLiteral(" ");
WriteLiteral("\r\n </span>\r\n </div>\r\n"); 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(" "); WriteLiteral(" ");
#line 761 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 768 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (!Model.OrganisationalUnitExists) if (!Model.OrganisationalUnitExists)
{ {
@@ -2286,7 +2304,7 @@ WriteLiteral("></i>The Organisational Unit could not be found.\r\n
" </div>\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"); WriteLiteral(">\r\n");
#line 770 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 777 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default #line default
#line hidden #line hidden
#line 770 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 777 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig) if (canConfig)
{ {
@@ -2321,7 +2339,7 @@ WriteLiteral(" type=\"checkbox\"");
WriteLiteral(" "); 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)); 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))); Write(Url.Action(MVC.API.DeviceProfile.UpdateEnforceOrganisationalUnit(Model.DeviceProfile.Id)));
@@ -2352,7 +2370,7 @@ WriteLiteral("\',\r\n \'EnforceOrganisational
" </script>\r\n"); " </script>\r\n");
#line 783 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 790 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
} }
else else
{ {
@@ -2369,7 +2387,7 @@ WriteLiteral(" type=\"checkbox\"");
WriteLiteral(" "); 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)); Write(Model.DeviceProfile.EnforceOrganisationalUnit ? new MvcHtmlString("checked=\"checked\" ") : new MvcHtmlString(string.Empty));
@@ -2378,7 +2396,7 @@ WriteLiteral(" ");
WriteLiteral(" disabled=\"disabled\" />\r\n"); 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(" "); WriteLiteral(" ");
#line 791 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 798 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(AjaxHelpers.AjaxLoader()); 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 default
#line hidden #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)) 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"); WriteLiteral(" <br />\r\n");
#line 808 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 815 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default #line default
#line hidden #line hidden
#line 808 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 815 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(AjaxHelpers.AjaxLoader("DeviceProfile_CertificateProviders")); Write(AjaxHelpers.AjaxLoader("DeviceProfile_CertificateProviders"));
#line default #line default
#line hidden #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(" "); WriteLiteral(" ");
#line 810 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 817 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.CertificateProviders.Count > 0) if (canConfig && Model.CertificateProviders.Count > 0)
{ {
@@ -2489,7 +2507,7 @@ WriteLiteral(@">
fetch('"); 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))); 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(" "); WriteLiteral(" ");
#line 841 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 848 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.CertificateProviders.Count > 0) if (canConfig && Model.CertificateProviders.Count > 0)
{ {
@@ -2549,7 +2567,7 @@ WriteLiteral(@">
fetch('"); 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))); 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"); "tes</h4>\r\n");
#line 875 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 882 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default #line default
#line hidden #line hidden
#line 875 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 882 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.CertificateProviders.Count > 0) if (canConfig && Model.CertificateProviders.Count > 0)
{ {
@@ -2599,14 +2617,14 @@ WriteLiteral(" </th>\r\n <td>\r\n <h4>Devic
#line default #line default
#line hidden #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()))); Write(CommonHelpers.CheckBoxList("DeviceProfile_CertificateProviders", "DeviceProfile_CertificateProviders", Model.CertificateProviders.ToSelectListItems(Model.DeviceProfile.GetCertificateProviders())));
#line default #line default
#line hidden #line hidden
#line 877 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 884 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
} }
else else
@@ -2626,7 +2644,7 @@ WriteLiteral(" class=\"smallMessage\"");
WriteLiteral(">&lt;None Allocated&gt;</span>\r\n"); 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 else
{ {
@@ -2637,13 +2655,13 @@ WriteLiteral(">&lt;None Allocated&gt;</span>\r\n");
WriteLiteral(" <ul>\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 default
#line hidden #line hidden
#line 890 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 897 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
foreach (var certificateProvider in certificateProviders) foreach (var certificateProvider in certificateProviders)
{ {
@@ -2653,7 +2671,7 @@ WriteLiteral(" <ul>\r\n");
WriteLiteral(" <li>"); WriteLiteral(" <li>");
#line 892 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 899 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(certificateProvider.Name); Write(certificateProvider.Name);
@@ -2662,7 +2680,7 @@ WriteLiteral(" <li>");
WriteLiteral("</li>\r\n"); 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"); 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"); 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 default
#line hidden #line hidden
#line 898 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 905 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.CertificateAuthorityProviders.Count > 0) if (canConfig && Model.CertificateAuthorityProviders.Count > 0)
{ {
@@ -2699,14 +2717,14 @@ WriteLiteral(">Authority Certificates</h4>\r\n");
#line default #line default
#line hidden #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()))); Write(CommonHelpers.CheckBoxList("DeviceProfile_CertificateAuthorityProviders", "DeviceProfile_CertificateAuthorityProviders", Model.CertificateAuthorityProviders.ToSelectListItems(Model.DeviceProfile.GetCertificateAuthorityProviders())));
#line default #line default
#line hidden #line hidden
#line 900 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 907 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
} }
else else
@@ -2726,7 +2744,7 @@ WriteLiteral(" class=\"smallMessage\"");
WriteLiteral(">&lt;None Allocated&gt;</span>\r\n"); 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 else
{ {
@@ -2737,13 +2755,13 @@ WriteLiteral(">&lt;None Allocated&gt;</span>\r\n");
WriteLiteral(" <ul>\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 default
#line hidden #line hidden
#line 913 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 920 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
foreach (var certificateProvider in certificateProviders) foreach (var certificateProvider in certificateProviders)
{ {
@@ -2753,7 +2771,7 @@ WriteLiteral(" <ul>\r\n");
WriteLiteral(" <li>"); WriteLiteral(" <li>");
#line 915 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 922 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(certificateProvider.Name); Write(certificateProvider.Name);
@@ -2762,7 +2780,7 @@ WriteLiteral(" <li>");
WriteLiteral("</li>\r\n"); 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"); 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(" "); WriteLiteral(" ");
#line 920 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 927 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canViewPlugins) if (canViewPlugins)
{ {
@@ -2802,21 +2820,21 @@ WriteLiteral(" class=\"fa fa-info-circle\"");
WriteLiteral("></i>View the <a"); 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" #line 931 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 53303), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.Plugins.Install()) , Tuple.Create(Tuple.Create("", 53943), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.Plugins.Install())
#line default #line default
#line hidden #line hidden
, 53303), false) , 53943), false)
); );
WriteLiteral(">Plugin Catalogue</a> to discover and install certificate provider plugins.\r\n " + WriteLiteral(">Plugin Catalogue</a> to discover and install certificate provider plugins.\r\n " +
" </p>\r\n </div>\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"); " Provision Wireless Profiles:\r\n");
#line 933 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 940 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default #line default
#line hidden #line hidden
#line 933 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 940 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.WirelessProfileProviders.Count > 0) if (canConfig && Model.WirelessProfileProviders.Count > 0)
{ {
@@ -2842,20 +2860,20 @@ WriteLiteral(" </td>\r\n </tr>\r\n <tr>\r\n
WriteLiteral(" <br />\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 default
#line hidden #line hidden
#line 936 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 943 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(AjaxHelpers.AjaxLoader("DeviceProfile_WirelessProfileProviders")); Write(AjaxHelpers.AjaxLoader("DeviceProfile_WirelessProfileProviders"));
#line default #line default
#line hidden #line hidden
#line 936 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 943 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
@@ -2879,7 +2897,7 @@ WriteLiteral(@">
fetch('"); 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))); 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"); 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 default
#line hidden #line hidden
#line 968 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 975 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig && Model.WirelessProfileProviders.Count > 0) if (canConfig && Model.WirelessProfileProviders.Count > 0)
{ {
@@ -2928,14 +2946,14 @@ WriteLiteral(" </th>\r\n <td>\r\n");
#line default #line default
#line hidden #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()))); Write(CommonHelpers.CheckBoxList("DeviceProfile_WirelessProfileProviders", "DeviceProfile_WirelessProfileProviders", Model.WirelessProfileProviders.ToSelectListItems(Model.DeviceProfile.GetWirelessProfileProviders())));
#line default #line default
#line hidden #line hidden
#line 970 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 977 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
} }
else else
@@ -2955,7 +2973,7 @@ WriteLiteral(" class=\"smallMessage\"");
WriteLiteral(">&lt;None Allocated&gt;</span>\r\n"); 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 else
{ {
@@ -2966,13 +2984,13 @@ WriteLiteral(">&lt;None Allocated&gt;</span>\r\n");
WriteLiteral(" <ul>\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 default
#line hidden #line hidden
#line 983 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 990 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
foreach (var wirelessProfileProvider in wirelessProfileProviders) foreach (var wirelessProfileProvider in wirelessProfileProviders)
{ {
@@ -2982,7 +3000,7 @@ WriteLiteral(" <ul>\r\n");
WriteLiteral(" <li>"); WriteLiteral(" <li>");
#line 985 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 992 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(wirelessProfileProvider.Name); Write(wirelessProfileProvider.Name);
@@ -2991,7 +3009,7 @@ WriteLiteral(" <li>");
WriteLiteral("</li>\r\n"); 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"); 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(" "); WriteLiteral(" ");
#line 990 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 997 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canViewPlugins) if (canViewPlugins)
{ {
@@ -3031,21 +3049,21 @@ WriteLiteral(" class=\"fa fa-info-circle\"");
WriteLiteral("></i>View the <a"); 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" #line 1001 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 56961), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.Plugins.Install()) , Tuple.Create(Tuple.Create("", 57601), Tuple.Create<System.Object, System.Int32>(Url.Action(MVC.Config.Plugins.Install())
#line default #line default
#line hidden #line hidden
, 56961), false) , 57601), false)
); );
WriteLiteral(">Plugin Catalogue</a> to discover and install wireless profile provider plugins.\r" + WriteLiteral(">Plugin Catalogue</a> to discover and install wireless profile provider plugins.\r" +
"\n </p>\r\n </div>\r\n"); "\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"); 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 default
#line hidden #line hidden
#line 1000 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1007 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (hideAdvanced) 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(" "); 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() Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel()
{ {
CanConfigure = canConfig, CanConfigure = canConfig,
@@ -3128,7 +3146,7 @@ WriteLiteral("\r\n");
WriteLiteral(" "); 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() Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupInstance, new LinkedGroupModel()
{ {
CanConfigure = canConfig, CanConfigure = canConfig,
@@ -3144,13 +3162,13 @@ WriteLiteral(" ");
WriteLiteral("\r\n"); WriteLiteral("\r\n");
#line 1039 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1046 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default #line default
#line hidden #line hidden
#line 1039 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1046 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canConfig) if (canConfig)
{ {
@@ -3158,14 +3176,14 @@ WriteLiteral("\r\n");
#line default #line default
#line hidden #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)); Write(Html.Partial(MVC.Config.Shared.Views.LinkedGroupShared));
#line default #line default
#line hidden #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"); 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)); Write(Html.Partial(MVC.Config.Shared.Views._DeviceGroupDocumentBulkGenerate, Model));
@@ -3188,13 +3206,13 @@ WriteLiteral(" class=\"actionBar\"");
WriteLiteral(">\r\n"); WriteLiteral(">\r\n");
#line 1050 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1057 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default #line default
#line hidden #line hidden
#line 1050 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1057 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (Model.CanDecommission) if (Model.CanDecommission)
{ {
@@ -3220,13 +3238,13 @@ WriteLiteral(" title=\"Profile Device Decommission\"");
WriteLiteral(">\r\n"); WriteLiteral(">\r\n");
#line 1054 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1061 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default #line default
#line hidden #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))) using (Html.BeginForm(MVC.API.Device.DeviceProfileDecommission(Model.DeviceProfile.Id)))
{ {
@@ -3234,14 +3252,14 @@ WriteLiteral(">\r\n");
#line default #line default
#line hidden #line hidden
#line 1056 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1063 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Html.AntiForgeryToken()); Write(Html.AntiForgeryToken());
#line default #line default
#line hidden #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"); WriteLiteral(">\r\n");
#line 1062 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1069 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default #line default
#line hidden #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())) 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\""); WriteLiteral(" type=\"radio\"");
WriteAttribute("id", Tuple.Create(" id=\"", 60656), Tuple.Create("\"", 60728) WriteAttribute("id", Tuple.Create(" id=\"", 61296), Tuple.Create("\"", 61368)
, Tuple.Create(Tuple.Create("", 60661), Tuple.Create("DeviceProfile_Decommission_Dialog_Reason_", 60661), true) , Tuple.Create(Tuple.Create("", 61301), Tuple.Create("DeviceProfile_Decommission_Dialog_Reason_", 61301), true)
#line 1065 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1072 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 60702), Tuple.Create<System.Object, System.Int32>((int)decommissionReason , Tuple.Create(Tuple.Create("", 61342), Tuple.Create<System.Object, System.Int32>((int)decommissionReason
#line default #line default
#line hidden #line hidden
, 60702), false) , 61342), false)
); );
WriteLiteral("\r\n name=\"decommissionReason\""); 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" #line 1073 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 60803), Tuple.Create<System.Object, System.Int32>((int)decommissionReason , Tuple.Create(Tuple.Create("", 61443), Tuple.Create<System.Object, System.Int32>((int)decommissionReason
#line default #line default
#line hidden #line hidden
, 60803), false) , 61443), false)
); );
WriteLiteral(" "); 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); Write((decommissionReason == DecommissionReasons.EndOfLife) ? "checked=\"checked\"" : string.Empty);
@@ -3317,21 +3335,21 @@ WriteLiteral(" ");
#line hidden #line hidden
WriteLiteral(" />\r\n <label"); WriteLiteral(" />\r\n <label");
WriteAttribute("for", Tuple.Create(" for=\"", 60969), Tuple.Create("\"", 61042) WriteAttribute("for", Tuple.Create(" for=\"", 61609), Tuple.Create("\"", 61682)
, Tuple.Create(Tuple.Create("", 60975), Tuple.Create("DeviceProfile_Decommission_Dialog_Reason_", 60975), true) , Tuple.Create(Tuple.Create("", 61615), Tuple.Create("DeviceProfile_Decommission_Dialog_Reason_", 61615), true)
#line 1067 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1074 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
, Tuple.Create(Tuple.Create("", 61016), Tuple.Create<System.Object, System.Int32>((int)decommissionReason , Tuple.Create(Tuple.Create("", 61656), Tuple.Create<System.Object, System.Int32>((int)decommissionReason
#line default #line default
#line hidden #line hidden
, 61016), false) , 61656), false)
); );
WriteLiteral(">"); WriteLiteral(">");
#line 1067 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1074 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(decommissionReason.ReasonMessage()); Write(decommissionReason.ReasonMessage());
@@ -3340,7 +3358,7 @@ WriteLiteral(">");
WriteLiteral("</label>\r\n </li>\r\n"); 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"); "\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(" "); WriteLiteral(" ");
#line 1109 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1116 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (canDelete) if (canDelete)
{ {
@@ -3440,13 +3458,13 @@ WriteLiteral(" title=\"Delete this Device Profile?\"");
WriteLiteral(">\r\n"); WriteLiteral(">\r\n");
#line 1113 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1120 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
#line default #line default
#line hidden #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))) using (Html.BeginForm(MVC.API.DeviceProfile.Delete(Model.DeviceProfile.Id, true)))
{ {
@@ -3454,14 +3472,14 @@ WriteLiteral(">\r\n");
#line default #line default
#line hidden #line hidden
#line 1115 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1122 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
Write(Html.AntiForgeryToken()); Write(Html.AntiForgeryToken());
#line default #line default
#line hidden #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(" "); WriteLiteral(" ");
#line 1150 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1157 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
if (Authorization.Has(Claims.Device.Actions.Export)) if (Authorization.Has(Claims.Device.Actions.Export))
{ {
@@ -3526,14 +3544,14 @@ WriteLiteral(" ");
#line default #line default
#line hidden #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))); Write(Html.ActionLinkButton("Export Devices", MVC.Device.Export(null, Disco.Models.Services.Devices.DeviceExportTypes.Profile, Model.DeviceProfile.Id)));
#line default #line default
#line hidden #line hidden
#line 1152 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml" #line 1159 "..\..\Areas\Config\Views\DeviceProfile\Show.cshtml"
} }
@@ -3543,7 +3561,7 @@ WriteLiteral(" ");
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) if (Authorization.Has(Claims.Device.Search) && Model.DeviceCount > 0)
{ {
@@ -3551,14 +3569,14 @@ WriteLiteral(" ");
#line default #line default
#line hidden #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"))); 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 default
#line hidden #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.TemplateExpressions.All(e => e.All(p => !p.ParseError) &&
!Model.OnImportUserFlagRules.Any()); !Model.OnImportUserFlagRules.Any());
#region Can Bulk Generate var canBulkGenerate = false;
var canBulkGenerate = Authorization.Has(Claims.Config.DocumentTemplate.BulkGenerate); var canBulkDownload = false;
if (canBulkGenerate) switch (Model.DocumentTemplate.Scope)
{ {
switch (Model.DocumentTemplate.Scope) case DocumentTemplate.DocumentTemplateScopes.Device:
{ canBulkGenerate = Authorization.Has(Claims.Config.DocumentTemplate.BulkGenerate) && Authorization.Has(Claims.Device.Actions.GenerateDocuments);
case DocumentTemplate.DocumentTemplateScopes.Device: canBulkDownload = Authorization.Has(Claims.Device.ShowAttachments) && Model.StoredInstanceCount > 0;
canBulkGenerate = Authorization.Has(Claims.Device.Actions.GenerateDocuments); break;
break; case DocumentTemplate.DocumentTemplateScopes.Job:
case DocumentTemplate.DocumentTemplateScopes.Job: canBulkGenerate = Authorization.Has(Claims.Config.DocumentTemplate.BulkGenerate) && Authorization.Has(Claims.Job.Actions.GenerateDocuments);
canBulkGenerate = Authorization.Has(Claims.Job.Actions.GenerateDocuments); canBulkDownload = Authorization.Has(Claims.Job.ShowAttachments) && Model.StoredInstanceCount > 0;
break; break;
case DocumentTemplate.DocumentTemplateScopes.User: case DocumentTemplate.DocumentTemplateScopes.User:
canBulkGenerate = Authorization.Has(Claims.User.Actions.GenerateDocuments); canBulkGenerate = Authorization.Has(Claims.Config.DocumentTemplate.BulkGenerate) && Authorization.Has(Claims.User.Actions.GenerateDocuments);
break; canBulkDownload = Authorization.Has(Claims.User.ShowAttachments) && Model.StoredInstanceCount > 0;
default: break;
throw new InvalidOperationException("Invalid DocumentType Scope"); 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); 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)) @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 (canBulkGenerate)
{ {
if (Model.DocumentTemplate.Scope == DocumentTemplate.DocumentTemplateScopes.User || Model.DocumentTemplate.Scope == DocumentTemplate.DocumentTemplateScopes.Device) if (Model.DocumentTemplate.Scope == DocumentTemplate.DocumentTemplateScopes.User || Model.DocumentTemplate.Scope == DocumentTemplate.DocumentTemplateScopes.Device)
@@ -1045,7 +1104,7 @@
} }
else 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 id="dialogBulkGenerate" class="dialog dialog-bulk-generate" title="Bulk Generate: @(Model.DocumentTemplate.Id)">
<div class="brief"> <div class="brief">
@switch (Model.DocumentTemplate.Scope) @switch (Model.DocumentTemplate.Scope)
File diff suppressed because it is too large Load Diff
@@ -224,7 +224,7 @@
<div> <div>
Add all devices in the selected batch Add all devices in the selected batch
</div> </div>
}) }
</div> </div>
@using (Html.BeginForm(MVC.API.DocumentTemplate.BulkGenerateAddDeviceBatch())) @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 227 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
} }
#line default #line default
#line hidden #line hidden
WriteLiteral(")\r\n </div>\r\n"); WriteLiteral(" </div>\r\n");
#line 229 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml" #line 229 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml"
@@ -1336,15 +1337,15 @@ WriteLiteral(">\r\n");
#line hidden #line hidden
WriteLiteral(" <div"); WriteLiteral(" <div");
WriteAttribute("class", Tuple.Create(" class=\"", 9870), Tuple.Create("\"", 9922) WriteAttribute("class", Tuple.Create(" class=\"", 9869), Tuple.Create("\"", 9921)
, Tuple.Create(Tuple.Create("", 9878), Tuple.Create("item", 9878), true) , Tuple.Create(Tuple.Create("", 9877), Tuple.Create("item", 9877), true)
#line 235 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml" #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 default
#line hidden #line hidden
, 9883), false) , 9882), false)
); );
WriteLiteral(" data-id=\""); WriteLiteral(" data-id=\"");
@@ -1413,14 +1414,14 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"scope\""); 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" #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 default
#line hidden #line hidden
, 10234), false) , 10233), false)
); );
WriteLiteral(" />\r\n"); WriteLiteral(" />\r\n");
@@ -1471,19 +1472,19 @@ WriteLiteral(" id=\"DocumentTemplate_BulkGenerate_Dialog_AddDocumentAttachment\"
WriteLiteral(" class=\"dialog dialog-bulk-generate\""); 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" #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 default
#line hidden #line hidden
, 10484), false) , 10483), false)
, Tuple.Create(Tuple.Create("", 10521), Tuple.Create(":", 10521), true) , Tuple.Create(Tuple.Create("", 10520), Tuple.Create(":", 10520), true)
, Tuple.Create(Tuple.Create(" ", 10522), Tuple.Create("Add", 10523), true) , Tuple.Create(Tuple.Create(" ", 10521), Tuple.Create("Add", 10522), true)
, Tuple.Create(Tuple.Create(" ", 10526), Tuple.Create("by", 10527), true) , Tuple.Create(Tuple.Create(" ", 10525), Tuple.Create("by", 10526), true)
, Tuple.Create(Tuple.Create(" ", 10529), Tuple.Create("Document", 10530), true) , Tuple.Create(Tuple.Create(" ", 10528), Tuple.Create("Document", 10529), true)
, Tuple.Create(Tuple.Create(" ", 10538), Tuple.Create("Attachment", 10539), true) , Tuple.Create(Tuple.Create(" ", 10537), Tuple.Create("Attachment", 10538), true)
); );
WriteLiteral(">\r\n <div"); WriteLiteral(">\r\n <div");
@@ -1581,15 +1582,15 @@ WriteLiteral(">\r\n");
#line hidden #line hidden
WriteLiteral(" <div"); WriteLiteral(" <div");
WriteAttribute("class", Tuple.Create(" class=\"", 11364), Tuple.Create("\"", 11419) WriteAttribute("class", Tuple.Create(" class=\"", 11363), Tuple.Create("\"", 11418)
, Tuple.Create(Tuple.Create("", 11372), Tuple.Create("item", 11372), true) , Tuple.Create(Tuple.Create("", 11371), Tuple.Create("item", 11371), true)
#line 269 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml" #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 default
#line hidden #line hidden
, 11377), false) , 11376), false)
); );
WriteLiteral(" data-id=\""); WriteLiteral(" data-id=\"");
@@ -1686,14 +1687,14 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"scope\""); 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" #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 default
#line hidden #line hidden
, 12114), false) , 12113), false)
); );
WriteLiteral(" />\r\n"); WriteLiteral(" />\r\n");
@@ -1744,19 +1745,19 @@ WriteLiteral(" id=\"DocumentTemplate_BulkGenerate_Dialog_AddUserDetail\"");
WriteLiteral(" class=\"dialog dialog-bulk-generate\""); 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" #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 default
#line hidden #line hidden
, 12350), false) , 12349), false)
, Tuple.Create(Tuple.Create("", 12387), Tuple.Create(":", 12387), true) , Tuple.Create(Tuple.Create("", 12386), Tuple.Create(":", 12386), true)
, Tuple.Create(Tuple.Create(" ", 12388), Tuple.Create("Add", 12389), true) , Tuple.Create(Tuple.Create(" ", 12387), Tuple.Create("Add", 12388), true)
, Tuple.Create(Tuple.Create(" ", 12392), Tuple.Create("by", 12393), true) , Tuple.Create(Tuple.Create(" ", 12391), Tuple.Create("by", 12392), true)
, Tuple.Create(Tuple.Create(" ", 12395), Tuple.Create("User", 12396), true) , Tuple.Create(Tuple.Create(" ", 12394), Tuple.Create("User", 12395), true)
, Tuple.Create(Tuple.Create(" ", 12400), Tuple.Create("Detail", 12401), true) , Tuple.Create(Tuple.Create(" ", 12399), Tuple.Create("Detail", 12400), true)
); );
WriteLiteral(">\r\n <div"); WriteLiteral(">\r\n <div");
@@ -1874,15 +1875,15 @@ WriteLiteral(">\r\n");
#line hidden #line hidden
WriteLiteral(" <div"); WriteLiteral(" <div");
WriteAttribute("class", Tuple.Create(" class=\"", 13278), Tuple.Create("\"", 13328) WriteAttribute("class", Tuple.Create(" class=\"", 13277), Tuple.Create("\"", 13327)
, Tuple.Create(Tuple.Create("", 13286), Tuple.Create("item", 13286), true) , Tuple.Create(Tuple.Create("", 13285), Tuple.Create("item", 13285), true)
#line 309 "..\..\Areas\Config\Views\DocumentTemplate\_BulkGenerateShared.cshtml" #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 default
#line hidden #line hidden
, 13291), false) , 13290), false)
); );
WriteLiteral(" data-id=\""); WriteLiteral(" data-id=\"");
@@ -1951,14 +1952,14 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"scope\""); 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" #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 default
#line hidden #line hidden
, 13643), false) , 13642), false)
); );
WriteLiteral(" />\r\n"); 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: 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> </span>
<div class="code"> <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> </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="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"> <span class="smallMessage">
@@ -133,6 +133,167 @@
</tr> </tr>
</table> </table>
</div> </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)) @if (canShowStatus && Authorization.Has(Claims.Config.Logging.Show))
{ {
<h2>Live Enrolment Logging</h2> <h2>Live Enrolment Logging</h2>
@@ -451,10 +451,26 @@ WriteLiteral(">\r\n curl&nbsp;<a");
WriteLiteral(" target=\"_blank\""); 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 " + #line 124 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
" </div>\r\n <span"); , 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\""); WriteLiteral(" class=\"smallText\"");
@@ -486,10 +502,521 @@ WriteLiteral(" class=\"code\"");
WriteLiteral(">&lt;script&gt;</span>\r\n tag embedded on the organisation\'s in" + 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>" + "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)) 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"); 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 default
#line hidden #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() Write(Html.Partial(MVC.Config.Shared.Views.LogEvents, new Disco.Web.Areas.Config.Models.Shared.LogEventsModel()
{ {
IsLive = true, IsLive = true,
@@ -519,7 +1046,7 @@ Write(Html.Partial(MVC.Config.Shared.Views.LogEvents, new Disco.Web.Areas.Config
#line default #line default
#line hidden #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"); WriteLiteral(">\r\n");
#line 149 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 310 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
#line default #line default
#line hidden #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)) if (Authorization.Has(Claims.Config.Enrolment.DownloadBootstrapper))
{ {
@@ -547,14 +1074,14 @@ WriteLiteral(">\r\n");
#line default #line default
#line hidden #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())); Write(Html.ActionLinkButton("Download Bootstrapper", MVC.Services.Client.Bootstrapper()));
#line default #line default
#line hidden #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(" "); WriteLiteral(" ");
#line 153 "..\..\Areas\Config\Views\Enrolment\Index.cshtml" #line 314 "..\..\Areas\Config\Views\Enrolment\Index.cshtml"
if (canShowStatus) if (canShowStatus)
{ {
@@ -572,14 +1099,14 @@ WriteLiteral(" ");
#line default #line default
#line hidden #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())); Write(Html.ActionLinkButton("Enrolment Status", MVC.Config.Enrolment.Status()));
#line default #line default
#line hidden #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;"> <div class="form" style="width: 450px; padding: 100px 0;">
<h2>No saved exports are configured</h2> <h2>No saved exports are configured</h2>
<div> <div class="info-box">
@if (Authorization.Has(Claims.Device.Actions.Export)) <p class="fa-p">
{ <i class="fa fa-fw fa-info-circle"></i>
<a href="@Url.Action(MVC.Device.Export())" class="button small">Device Export</a> 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.
@if (Authorization.Has(Claims.Job.Actions.Export)) </p>
{
<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> </div>
</div> </div>
} }
@@ -420,148 +420,36 @@ WriteLiteral(" class=\"form\"");
WriteLiteral(" style=\"width: 450px; padding: 100px 0;\""); 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 WriteLiteral(">\r\n <i");
#line hidden
#line 81 "..\..\Areas\Config\Views\Export\Index.cshtml" WriteLiteral(" class=\"fa fa-fw fa-info-circle\"");
if (Authorization.Has(Claims.Device.Actions.Export))
{
WriteLiteral("></i>\r\n Visit any export page (eg. <a");
#line default WriteAttribute("href", Tuple.Create(" href=\"", 3675), Tuple.Create("\"", 3714)
#line hidden
WriteLiteral(" <a");
WriteAttribute("href", Tuple.Create(" href=\"", 3626), Tuple.Create("\"", 3665)
#line 83 "..\..\Areas\Config\Views\Export\Index.cshtml" #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 default
#line hidden #line hidden
, 3633), false) , 3682), false)
); );
WriteLiteral(" class=\"button small\""); 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" +
WriteLiteral(">Device Export</a>\r\n"); " </div>\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");
#line 88 "..\..\Areas\Config\Views\Export\Index.cshtml" #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"
} }
@@ -93,8 +93,18 @@
<h3>Filter</h3> <h3>Filter</h3>
<select id="Config_ReportPrefs_Builder_Filter"> <select id="Config_ReportPrefs_Builder_Filter">
<option value="">&lt;None&gt;</option> <option value="">&lt;None&gt;</option>
<option value="DeviceProfile">Device Profile</option> @if (Model.DeviceProfiles.Any())
<option value="DeviceAddress">Device Profile Address</option> {
<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> </select>
<div class="options"> <div class="options">
<div class="method"> <div class="method">
@@ -103,17 +113,17 @@
</div> </div>
<div class="filter-option filter-DeviceProfile"> <div class="filter-option filter-DeviceProfile">
<ul class="none"> <ul class="none">
@foreach (var deviceProfile in Model.DeviceProfiles.Value) @foreach (var deviceProfile in Model.DeviceProfiles)
{ {
<li> <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> </li>
} }
</ul> </ul>
</div> </div>
<div class="filter-option filter-DeviceAddress"> <div class="filter-option filter-DeviceAddress">
<ul class="none"> <ul class="none">
@foreach (var address in Model.OrganisationAddresses.Value) @foreach (var address in Model.OrganisationAddresses)
{ {
<li> <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> <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> </ul>
</div> </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>
</div> </div>
</form> </form>
@@ -436,16 +436,82 @@ WriteLiteral(">\r\n <option");
WriteLiteral(" value=\"\""); 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(" 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(" value=\"DeviceAddress\"");
WriteLiteral(">Device Profile Address</option>\r\n </select>\r\n " + WriteLiteral(">Device Profile Address</option>\r\n");
" <div");
#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\""); WriteLiteral(" class=\"options\"");
@@ -495,14 +561,14 @@ WriteLiteral(" class=\"none\"");
WriteLiteral(">\r\n"); WriteLiteral(">\r\n");
#line 106 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 116 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line default #line default
#line hidden #line hidden
#line 106 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 116 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
foreach (var deviceProfile in Model.DeviceProfiles.Value) foreach (var deviceProfile in Model.DeviceProfiles)
{ {
@@ -511,47 +577,47 @@ WriteLiteral(">\r\n");
WriteLiteral(" <li>\r\n " + WriteLiteral(" <li>\r\n " +
" <input"); " <input");
WriteAttribute("id", Tuple.Create(" id=\"", 7313), Tuple.Create("\"", 7367) WriteAttribute("id", Tuple.Create(" id=\"", 7806), Tuple.Create("\"", 7860)
, Tuple.Create(Tuple.Create("", 7318), Tuple.Create("Config_ReportPrefs_Builder_DP_", 7318), true) , Tuple.Create(Tuple.Create("", 7811), Tuple.Create("Config_ReportPrefs_Builder_DP_", 7811), true)
#line 109 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 7348), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id , Tuple.Create(Tuple.Create("", 7841), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id
#line default #line default
#line hidden #line hidden
, 7348), false) , 7841), false)
); );
WriteLiteral(" type=\"checkbox\""); 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" #line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 7392), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id , Tuple.Create(Tuple.Create("", 7885), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id
#line default #line default
#line hidden #line hidden
, 7392), false) , 7885), false)
); );
WriteLiteral(" /><label"); WriteLiteral(" /><label");
WriteAttribute("for", Tuple.Create(" for=\"", 7419), Tuple.Create("\"", 7474) WriteAttribute("for", Tuple.Create(" for=\"", 7912), Tuple.Create("\"", 7967)
, Tuple.Create(Tuple.Create("", 7425), Tuple.Create("Config_ReportPrefs_Builder_DP_", 7425), true) , Tuple.Create(Tuple.Create("", 7918), Tuple.Create("Config_ReportPrefs_Builder_DP_", 7918), true)
#line 109 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 7455), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id , Tuple.Create(Tuple.Create("", 7948), Tuple.Create<System.Object, System.Int32>(deviceProfile.Id
#line default #line default
#line hidden #line hidden
, 7455), false) , 7948), false)
); );
WriteLiteral(">"); WriteLiteral(">");
#line 109 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
Write(deviceProfile.Description); Write(deviceProfile.Name);
#line default #line default
@@ -559,7 +625,7 @@ WriteLiteral(">");
WriteLiteral("</label>\r\n </li>\r\n"); 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"); WriteLiteral(">\r\n");
#line 116 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 126 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
#line default #line default
#line hidden #line hidden
#line 116 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 126 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
foreach (var address in Model.OrganisationAddresses.Value) foreach (var address in Model.OrganisationAddresses)
{ {
@@ -593,46 +659,46 @@ WriteLiteral(">\r\n");
WriteLiteral(" <li>\r\n " + WriteLiteral(" <li>\r\n " +
" <input"); " <input");
WriteAttribute("id", Tuple.Create(" id=\"", 8074), Tuple.Create("\"", 8122) WriteAttribute("id", Tuple.Create(" id=\"", 8554), Tuple.Create("\"", 8602)
, Tuple.Create(Tuple.Create("", 8079), Tuple.Create("Config_ReportPrefs_Builder_OA_", 8079), true) , Tuple.Create(Tuple.Create("", 8559), Tuple.Create("Config_ReportPrefs_Builder_OA_", 8559), true)
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 129 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 8109), Tuple.Create<System.Object, System.Int32>(address.Id , Tuple.Create(Tuple.Create("", 8589), Tuple.Create<System.Object, System.Int32>(address.Id
#line default #line default
#line hidden #line hidden
, 8109), false) , 8589), false)
); );
WriteLiteral(" type=\"checkbox\""); 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" #line 129 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 8147), Tuple.Create<System.Object, System.Int32>(address.ShortName , Tuple.Create(Tuple.Create("", 8627), Tuple.Create<System.Object, System.Int32>(address.ShortName
#line default #line default
#line hidden #line hidden
, 8147), false) , 8627), false)
); );
WriteLiteral(" /><label"); WriteLiteral(" /><label");
WriteAttribute("for", Tuple.Create(" for=\"", 8175), Tuple.Create("\"", 8224) WriteAttribute("for", Tuple.Create(" for=\"", 8655), Tuple.Create("\"", 8704)
, Tuple.Create(Tuple.Create("", 8181), Tuple.Create("Config_ReportPrefs_Builder_OA_", 8181), true) , Tuple.Create(Tuple.Create("", 8661), Tuple.Create("Config_ReportPrefs_Builder_OA_", 8661), true)
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 129 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
, Tuple.Create(Tuple.Create("", 8211), Tuple.Create<System.Object, System.Int32>(address.Id , Tuple.Create(Tuple.Create("", 8691), Tuple.Create<System.Object, System.Int32>(address.Id
#line default #line default
#line hidden #line hidden
, 8211), false) , 8691), false)
); );
WriteLiteral(">"); WriteLiteral(">");
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 129 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
Write(address.Name); Write(address.Name);
@@ -641,7 +707,7 @@ WriteLiteral(">");
WriteLiteral(" ("); WriteLiteral(" (");
#line 119 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml" #line 129 "..\..\Areas\Config\Views\JobPreferences\Parts\Reports.cshtml"
Write(address.ShortName); Write(address.ShortName);
@@ -650,7 +716,111 @@ WriteLiteral(" (");
WriteLiteral(")</label>\r\n </li>\r\n"); 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"> <div class="code" title="@group.Id">
<i class="fa fa-fw fa-lg fa-link success"></i>@group.Domain.FriendlyDistinguishedNamePath(group.DistinguishedName) <i class="fa fa-fw fa-lg fa-link success"></i>@group.Domain.FriendlyDistinguishedNamePath(group.DistinguishedName)
</div> </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))) using (Html.BeginForm(MVC.API.System.SyncActiveDirectoryManagedGroup(Model.ManagedGroup.Key, Context.Request.Path)))
{ {
@Html.AntiForgeryToken() @Html.AntiForgeryToken()
@@ -34,12 +34,12 @@
<div class="code error"> <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> <i class="fa fa-fw fa-lg fa-unlink error"></i>Group Not Found: <strong class="code">@Model.ManagedGroup.Configuration.GroupId</strong>
</div> </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 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 else
@@ -177,22 +177,33 @@ WriteLiteral(" data-linkedgroupfilterdate=\"");
#line hidden #line hidden
WriteLiteral("\""); WriteLiteral("\"");
WriteLiteral(" data-linkedroupdescription=\""); WriteLiteral(" data-linkedgroupdescription=\"");
#line 25 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml" #line 25 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
Write(Model.CategoryDescription); Write(Model.CategoryDescription);
#line default #line default
#line hidden #line hidden
WriteLiteral("\""); WriteLiteral("\"");
WriteLiteral(" data-linkedroupupdateurl=\""); WriteLiteral(" data-linkedgroupupdateurl=\"");
#line 25 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml" #line 25 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
Write(Model.UpdateUrl); Write(Model.UpdateUrl);
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedgroupupdatedescription=\"");
#line 25 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
Write(Model.ManagedGroup.Configuration.UpdateDescription);
#line default #line default
@@ -303,22 +314,33 @@ WriteLiteral(" data-linkedgroupfilterdate=\"");
#line hidden #line hidden
WriteLiteral("\""); WriteLiteral("\"");
WriteLiteral(" data-linkedroupdescription=\""); WriteLiteral(" data-linkedgroupdescription=\"");
#line 37 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml" #line 37 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
Write(Model.CategoryDescription); Write(Model.CategoryDescription);
#line default #line default
#line hidden #line hidden
WriteLiteral("\""); WriteLiteral("\"");
WriteLiteral(" data-linkedroupupdateurl=\""); WriteLiteral(" data-linkedgroupupdateurl=\"");
#line 37 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml" #line 37 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
Write(Model.UpdateUrl); Write(Model.UpdateUrl);
#line default
#line hidden
WriteLiteral("\"");
WriteLiteral(" data-linkedgroupupdatedescription=\"");
#line 37 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
Write(Model.ManagedGroup.Configuration.UpdateDescription);
#line default #line default
@@ -356,28 +378,30 @@ WriteLiteral(" data-linkedgroupfilterdateoption=\"");
#line hidden #line hidden
WriteLiteral("\""); WriteLiteral("\"");
WriteLiteral(" data-linkedroupdescription=\""); WriteLiteral(" data-linkedgroupdescription=\"");
#line 42 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml" #line 42 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
Write(Model.CategoryDescription); Write(Model.CategoryDescription);
#line default #line default
#line hidden #line hidden
WriteLiteral("\""); WriteLiteral("\"");
WriteLiteral(" data-linkedroupupdateurl=\""); WriteLiteral(" data-linkedgroupupdateurl=\"");
#line 42 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml" #line 42 "..\..\Areas\Config\Views\Shared\LinkedGroupInstance.cshtml"
Write(Model.UpdateUrl); Write(Model.UpdateUrl);
#line default #line default
#line hidden #line hidden
WriteLiteral("\""); WriteLiteral("\"");
WriteLiteral(" data-linkedgroupupdatedescription=\"True\"");
WriteLiteral(">Link Group</button>\r\n"); WriteLiteral(">Link Group</button>\r\n");
@@ -398,14 +422,14 @@ WriteLiteral(" <div");
WriteLiteral(" class=\"code\""); 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" #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 default
#line hidden #line hidden
, 2974), false) , 3201), false)
); );
WriteLiteral(">\r\n <i"); WriteLiteral(">\r\n <i");
@@ -12,7 +12,7 @@
<label for="Config_LinkedGroup_Id">Linked Group:</label> <label for="Config_LinkedGroup_Id">Linked Group:</label>
</th> </th>
<td> <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> </td>
</tr> </tr>
<tr> <tr>
@@ -20,7 +20,15 @@
<label for="Config_LinkedGroup_FilterDate">Filter Date: </label> <label for="Config_LinkedGroup_FilterDate">Filter Date: </label>
</th> </th>
<td> <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> </td>
</tr> </tr>
</tbody> </tbody>
@@ -30,6 +38,7 @@
<p class="fa-p"> <p class="fa-p">
<i class="fa fa-exclamation-circle"></i><strong>Warning:</strong> This group will be managed by Disco ICT.<br /> <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. 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> </p>
</div> </div>
</div> </div>
@@ -38,9 +47,10 @@
let dialog; let dialog;
let dialogGroupId; let dialogGroupId;
let dialogFilterDate; let dialogFilterDate;
let dialogUpdateDescription;
let dialogTitle; let dialogTitle;
function showDialog(groupId, filterDateOption, filterDateValue, updateUrl, title) { function showDialog(groupId, filterDateOption, filterDateValue, updateUrl, title, updateDescription) {
if (dialog == null) { if (dialog == null) {
dialog = $('#Config_LinkedGroup_Dialog').dialog({ dialog = $('#Config_LinkedGroup_Dialog').dialog({
width: 450, width: 450,
@@ -57,6 +67,10 @@
dateFormat: 'yy/mm/dd' dateFormat: 'yy/mm/dd'
}); });
dialogUpdateDescription = $('#Config_LinkedGroup_UpdateDescription').on('change', function () {
$('#Config_LinkedGroup_UpdateDescriptionOff').prop('disabled', $(this).prop('checked'));
});
dialogGroupId = $('#Config_LinkedGroup_Id'); dialogGroupId = $('#Config_LinkedGroup_Id');
dialogGroupId.focus(function () { $(this).select(); }); dialogGroupId.focus(function () { $(this).select(); });
dialogGroupId.autocomplete({ dialogGroupId.autocomplete({
@@ -109,6 +123,8 @@
dialogFilterDate.closest('tr').hide(); dialogFilterDate.closest('tr').hide();
} }
dialogUpdateDescription.prop('checked', updateDescription);
dialogTitle.text(title); dialogTitle.text(title);
dialog.dialog('option', 'buttons', dialogButtons); dialog.dialog('option', 'buttons', dialogButtons);
dialog.dialog('option', 'title', 'Linked Group: ' + title); dialog.dialog('option', 'title', 'Linked Group: ' + title);
@@ -120,11 +136,12 @@
var configuredGroupId = $this.attr('data-linkedgroupid'); var configuredGroupId = $this.attr('data-linkedgroupid');
var configuredFilterBeginDate = $this.attr('data-linkedgroupfilterdate'); var configuredFilterBeginDate = $this.attr('data-linkedgroupfilterdate');
var filterDateOption = $this.attr('data-linkedgroupfilterdateoption') == 'True'; var filterDateOption = $this.attr('data-linkedgroupfilterdateoption') === 'True';
var description = $this.attr('data-linkedroupdescription'); var description = $this.attr('data-linkedgroupdescription');
var updateUrl = $this.attr('data-linkedroupupdateurl'); 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; return false;
}); });
@@ -96,7 +96,7 @@ WriteLiteral(" id=\"Config_LinkedGroup_Id\"");
WriteLiteral(" type=\"text\""); WriteLiteral(" type=\"text\"");
WriteLiteral(" name=\"GroupId\""); WriteLiteral(" name=\"groupId\"");
WriteLiteral(" data-sourceurl=\""); WriteLiteral(" data-sourceurl=\"");
@@ -121,14 +121,40 @@ WriteLiteral(" id=\"Config_LinkedGroup_FilterDate\"");
WriteLiteral(" type=\"text\""); WriteLiteral(" type=\"text\"");
WriteLiteral(" name=\"FilterBeginDate\""); WriteLiteral(" name=\"filterBeginDate\"");
WriteLiteral(" placeholder=\"No Filter\""); WriteLiteral(" placeholder=\"No Filter\"");
WriteLiteral(" autocomplete=\"off\""); WriteLiteral(" autocomplete=\"off\"");
WriteLiteral(" />\r\n </td>\r\n </tr>\r\n </tbody>\r\n " + WriteLiteral(" />\r\n </td>\r\n </tr>\r\n <tr>\r\n " +
" </table>\r\n </form>\r\n <div"); " <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\""); 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 " + 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>, " + " 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 " + "and it will be automatically synchronized with related members.\r\n If " +
" </div>\r\n</div>\r\n<script>\r\n $(function () {\r\n let dialog;\r\n l" + "the \'Update Group Description\' option is selected, the group\'s description will " +
"et dialogGroupId;\r\n let dialogFilterDate;\r\n let dialogTitle;\r\n\r\n " + "also be updated to reflect its managed status.\r\n </p>\r\n </div>\r\n</div>" +
" function showDialog(groupId, filterDateOption, filterDateValue, updateUrl," + "\r\n<script>\r\n $(function () {\r\n let dialog;\r\n let dialogGroupId;" +
" title) {\r\n if (dialog == null) {\r\n dialog = $(\'#Confi" + "\r\n let dialogFilterDate;\r\n let dialogUpdateDescription;\r\n l" +
"g_LinkedGroup_Dialog\').dialog({\r\n width: 450,\r\n " + "et dialogTitle;\r\n\r\n function showDialog(groupId, filterDateOption, filter" +
" resizable: false,\r\n modal: true,\r\n a" + "DateValue, updateUrl, title, updateDescription) {\r\n if (dialog == nul" +
"utoOpen: false\r\n });\r\n\r\n dialogFilterDate = $(\'#Co" + "l) {\r\n dialog = $(\'#Config_LinkedGroup_Dialog\').dialog({\r\n " +
"nfig_LinkedGroup_FilterDate\');\r\n dialogFilterDate.datetimepicker(" + " width: 450,\r\n resizable: false,\r\n " +
"{\r\n ampm: true,\r\n changeYear: true,\r\n " + " modal: true,\r\n autoOpen: false\r\n });\r\n\r\n " +
" changeMonth: true,\r\n dateFormat: \'yy/mm/dd\'\r\n " + " dialogFilterDate = $(\'#Config_LinkedGroup_FilterDate\');\r\n " +
" });\r\n\r\n dialogGroupId = $(\'#Config_LinkedGroup_Id\'" + " dialogFilterDate.datetimepicker({\r\n ampm: true,\r\n " +
");\r\n dialogGroupId.focus(function () { $(this).select(); });\r\n " + " changeYear: true,\r\n changeMonth: true,\r\n " +
" dialogGroupId.autocomplete({\r\n source: dialogGro" + " dateFormat: \'yy/mm/dd\'\r\n });\r\n\r\n di" +
"upId.attr(\'data-sourceurl\'),\r\n minLength: 2,\r\n " + "alogUpdateDescription = $(\'#Config_LinkedGroup_UpdateDescription\').on(\'change\', " +
" select: function (e, ui) {\r\n dialogGroupId.val(ui.it" + "function () {\r\n $(\'#Config_LinkedGroup_UpdateDescriptionOff\')" +
"em.Id);\r\n return false;\r\n }\r\n " + ".prop(\'disabled\', $(this).prop(\'checked\'));\r\n });\r\n\r\n " +
" }).data(\'ui-autocomplete\')._renderItem = function (ul, item) {\r\n " + " dialogGroupId = $(\'#Config_LinkedGroup_Id\');\r\n dialogGroupId." +
" return $(\"<li>\")\r\n .data(\"item.autocomplete\", " + "focus(function () { $(this).select(); });\r\n dialogGroupId.autocom" +
"item)\r\n .append(\"<a><strong>\" + item.Name + \"</strong><br" + "plete({\r\n source: dialogGroupId.attr(\'data-sourceurl\'),\r\n " +
">\" + item.Id + \" (\" + item.Type + \")</a>\")\r\n .appendTo(ul" + " minLength: 2,\r\n select: function (e, ui) {\r\n " +
");\r\n };\r\n\r\n dialogTitle = $(\'#Config_LinkedGroup_T" + " dialogGroupId.val(ui.item.Id);\r\n r" +
"itle\');\r\n }\r\n\r\n var dialogButtons = {};\r\n if (!" + "eturn false;\r\n }\r\n }).data(\'ui-autocomplete\')." +
"!groupId) {\r\n dialogButtons[\'Remove Link\'] = function () {\r\n " + "_renderItem = function (ul, item) {\r\n return $(\"<li>\")\r\n " +
" $(this).dialog(\'disable\');\r\n dialogGroupId.val" + " .data(\"item.autocomplete\", item)\r\n .ap" +
"(\'\');\r\n dialogGroupId.closest(\'form\').attr(\'action\', updateUr" + "pend(\"<a><strong>\" + item.Name + \"</strong><br>\" + item.Id + \" (\" + item.Type + " +
"l).submit();\r\n }\r\n }\r\n dialogButtons[(!!gro" + "\")</a>\")\r\n .appendTo(ul);\r\n };\r\n\r\n " +
"upId ? \'Save Changes\' : \'Link Group\')] = function () {\r\n if (!dia" + " dialogTitle = $(\'#Config_LinkedGroup_Title\');\r\n }\r\n\r\n " +
"logGroupId.val()) {\r\n alert(\'A Linked Group must be specified" + " var dialogButtons = {};\r\n if (!!groupId) {\r\n dialo" +
"\');\r\n return;\r\n }\r\n $(this).dia" + "gButtons[\'Remove Link\'] = function () {\r\n $(this).dialog(\'dis" +
"log(\'disable\');\r\n dialogGroupId.closest(\'form\').attr(\'action\', up" + "able\');\r\n dialogGroupId.val(\'\');\r\n dialogG" +
"dateUrl).submit();\r\n }\r\n dialogButtons[\'Cancel\'] = functio" + "roupId.closest(\'form\').attr(\'action\', updateUrl).submit();\r\n }\r\n " +
"n () {\r\n $(this).dialog(\'close\');\r\n };\r\n\r\n " + " }\r\n dialogButtons[(!!groupId ? \'Save Changes\' : \'Link Grou" +
"dialogGroupId.val(groupId);\r\n\r\n if (!!filterDateOption) {\r\n " + "p\')] = function () {\r\n if (!dialogGroupId.val()) {\r\n " +
" if (!!filterDateValue) {\r\n dialogFilterDate.datetimepic" + " alert(\'A Linked Group must be specified\');\r\n return;\r\n" +
"ker(\'setDate\', moment(filterDateValue).toDate());\r\n } else {\r\n " + " }\r\n $(this).dialog(\'disable\');\r\n d" +
" dialogFilterDate.val(\'\');\r\n }\r\n d" + "ialogGroupId.closest(\'form\').attr(\'action\', updateUrl).submit();\r\n }\r" +
"ialogFilterDate.closest(\'tr\').show();\r\n } else {\r\n dia" + "\n dialogButtons[\'Cancel\'] = function () {\r\n $(this).di" +
"logFilterDate.closest(\'tr\').hide();\r\n }\r\n\r\n dialogTitle.te" + "alog(\'close\');\r\n };\r\n\r\n dialogGroupId.val(groupId);\r\n\r\n " +
"xt(title);\r\n dialog.dialog(\'option\', \'buttons\', dialogButtons);\r\n " + " if (!!filterDateOption) {\r\n if (!!filterDateValue) {\r\n " +
" dialog.dialog(\'option\', \'title\', \'Linked Group: \' + title);\r\n " + " dialogFilterDate.datetimepicker(\'setDate\', moment(filterDateVa" +
" dialog.dialog(\'open\');\r\n }\r\n\r\n $(document).on(\'click\', \'.Config_L" + "lue).toDate());\r\n } else {\r\n dialogFilterDate." +
"inkedGroup_LinkButton\', function () {\r\n $this = $(this);\r\n\r\n " + "val(\'\');\r\n }\r\n dialogFilterDate.closest(\'tr\').show" +
" var configuredGroupId = $this.attr(\'data-linkedgroupid\');\r\n var co" + "();\r\n } else {\r\n dialogFilterDate.closest(\'tr\').hide()" +
"nfiguredFilterBeginDate = $this.attr(\'data-linkedgroupfilterdate\');\r\n " + ";\r\n }\r\n\r\n dialogUpdateDescription.prop(\'checked\', updateDe" +
" var filterDateOption = $this.attr(\'data-linkedgroupfilterdateoption\') == \'True\'" + "scription);\r\n\r\n dialogTitle.text(title);\r\n dialog.dialog(\'" +
";\r\n var description = $this.attr(\'data-linkedroupdescription\');\r\n " + "option\', \'buttons\', dialogButtons);\r\n dialog.dialog(\'option\', \'title\'" +
" var updateUrl = $this.attr(\'data-linkedroupupdateurl\');\r\n\r\n s" + ", \'Linked Group: \' + title);\r\n dialog.dialog(\'open\');\r\n }\r\n\r\n " +
"howDialog(configuredGroupId, filterDateOption, configuredFilterBeginDate, update" + " $(document).on(\'click\', \'.Config_LinkedGroup_LinkButton\', function () {\r\n" +
"Url, description);\r\n\r\n return false;\r\n });\r\n });\r\n</script>" + " $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"); "\r\n");
} }
@@ -11,45 +11,23 @@
</div> </div>
</div> </div>
<iframe name="callbackFrame" class="hidden"> <form id="callbackSubmit" action="@Model.CallbackUrl" method="post" data-failedurl="@Url.Action(MVC.Config.SystemConfig.Index())">
</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" />
<input type="hidden" name="correlationId" value="@Model.CorrelationId" /> <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="userId" value="@Model.UserId" />
<input type="hidden" name="callbackUrl" value="@(new Uri(Request.Url, Url.Action(MVC.API.Activation.Begin())))" />
</form> </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> <script>
$(function () { $(function () {
const callbackForm = $('#callbackSubmit'); const callbackForm = $('#callbackSubmit');
const callbackFailedUrl = $('#callbackFailedUrl').attr('href'); const callbackFailedUrl = callbackForm.attr('data-failedurl');
const timeout = window.setTimeout(function () { const timeout = window.setTimeout(function () {
alert('A timeout occurred while communicating with Online Services. Please try a different device/browser or try again later.'); alert('A timeout occurred while communicating with Online Services. Please try a different device/browser or try again later.');
window.location.href = callbackFailedUrl; window.location.href = callbackFailedUrl;
}, 1000 * 35); }, 1000 * 18);
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');
};
callbackForm.trigger('submit'); callbackForm.trigger('submit');
}); });
</script> </script>
@@ -68,44 +68,47 @@ WriteLiteral(">\r\n <h2><i");
WriteLiteral(" class=\"fa fa-lg fa-cog fa-spin\""); 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" + WriteLiteral("></i> Testing Connectivity to Disco ICT Online Services</h2>\r\n </div>\r\n</div>\r" +
"\n\r\n<iframe"); "\n\r\n<form");
WriteLiteral(" name=\"callbackFrame\"");
WriteLiteral(" class=\"hidden\"");
WriteLiteral(">\r\n</iframe>\r\n\r\n<form");
WriteLiteral(" id=\"callbackSubmit\""); 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" #line 14 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 572), Tuple.Create<System.Object, System.Int32>(Model.CallbackUrl , Tuple.Create(Tuple.Create("", 513), Tuple.Create<System.Object, System.Int32>(Model.CallbackUrl
#line default #line default
#line hidden #line hidden
, 572), false) , 513), false)
); );
WriteLiteral(" method=\"post\""); 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(">\r\n <input");
WriteLiteral(" type=\"hidden\""); 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" #line 15 "..\..\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())) , Tuple.Create(Tuple.Create("", 664), Tuple.Create<System.Object, System.Int32>(Model.CorrelationId
#line default #line default
#line hidden #line hidden
, 682), false) , 664), false)
); );
WriteLiteral(" />\r\n <input"); WriteLiteral(" />\r\n <input");
@@ -114,30 +117,46 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"deploymentId\""); 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" #line 16 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 810), Tuple.Create<System.Object, System.Int32>(Model.DeploymentId , Tuple.Create(Tuple.Create("", 742), Tuple.Create<System.Object, System.Int32>(Model.DeploymentId
#line default #line default
#line hidden #line hidden
, 810), false) , 742), false)
); );
WriteLiteral(" />\r\n <input"); WriteLiteral(" />\r\n <input");
WriteLiteral(" type=\"hidden\""); 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" #line 17 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 888), Tuple.Create<System.Object, System.Int32>(Model.CorrelationId , Tuple.Create(Tuple.Create("", 816), Tuple.Create<System.Object, System.Int32>(Model.Timestamp
#line default #line default
#line hidden #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"); WriteLiteral(" />\r\n <input");
@@ -146,81 +165,43 @@ WriteLiteral(" type=\"hidden\"");
WriteLiteral(" name=\"userId\""); 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" #line 19 "..\..\Areas\Config\Views\SystemConfig\Activate.cshtml"
, Tuple.Create(Tuple.Create("", 960), Tuple.Create<System.Object, System.Int32>(Model.UserId , Tuple.Create(Tuple.Create("", 947), Tuple.Create<System.Object, System.Int32>(Model.UserId
#line default #line default
#line hidden #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" WriteLiteral(" name=\"callbackUrl\"");
using (Html.BeginForm(MVC.API.Activation.Begin(), FormMethod.Post, new { id = "activationBegin"}))
{
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 default
#line hidden #line hidden
, 1017), false)
#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)
); );
WriteLiteral(" class=\"hidden\""); WriteLiteral(@" />
</form>
WriteLiteral(@"></a>
<script> <script>
$(function () { $(function () {
const callbackForm = $('#callbackSubmit'); const callbackForm = $('#callbackSubmit');
const callbackFailedUrl = $('#callbackFailedUrl').attr('href'); const callbackFailedUrl = callbackForm.attr('data-failedurl');
const timeout = window.setTimeout(function () { const timeout = window.setTimeout(function () {
alert('A timeout occurred while communicating with Online Services. Please try a different device/browser or try again later.'); alert('A timeout occurred while communicating with Online Services. Please try a different device/browser or try again later.');
window.location.href = callbackFailedUrl; window.location.href = callbackFailedUrl;
}, 1000 * 35); }, 1000 * 18);
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');
};
callbackForm.trigger('submit'); callbackForm.trigger('submit');
}); });
</script> </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.Jobs.Noticeboards;
using Disco.Services.Web; using Disco.Services.Web;
using Disco.Web.Areas.Public.Models.UserHeldDevices; using Disco.Web.Areas.Public.Models.UserHeldDevices;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
@@ -10,30 +10,53 @@ namespace Disco.Web.Areas.Public.Controllers
{ {
public partial class HeldDevicesController : DatabaseController 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; var query = FilterJobs(Database.Jobs, Database, DeviceProfileInclude, DeviceProfileExclude, DeviceAddressInclude, DeviceAddressExclude, JobQueueInclude, JobQueueExclude);
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 m = Disco.Services.Jobs.Noticeboards.HeldDevices.GetHeldDevices(query); var m = Disco.Services.Jobs.Noticeboards.HeldDevices.GetHeldDevices(query);
return View(m); 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() public virtual ActionResult ReadyForReturnXml()
{ {
var readyForReturn = Disco.Services.Jobs.Noticeboards.HeldDevices.GetHeldDevices(Database) 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.Services.Web;
using Disco.Web.Areas.Public.Models.UserHeldDevices; using Disco.Web.Areas.Public.Models.UserHeldDevices;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
@@ -10,24 +8,9 @@ namespace Disco.Web.Areas.Public.Controllers
{ {
public partial class UserHeldDevicesController : DatabaseController 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; var query = HeldDevicesController.FilterJobs(Database.Jobs, Database, DeviceProfileInclude, DeviceProfileExclude, DeviceAddressInclude, DeviceAddressExclude, JobQueueInclude, JobQueueExclude);
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 m = HeldDevicesForUsers.GetHeldDevicesForUsers(query); var m = HeldDevicesForUsers.GetHeldDevicesForUsers(query);
@@ -14,7 +14,7 @@
{ {
<tr> <tr>
<td class="id"> <td class="id">
@item.DeviceComputerNameFriendly @item.DeviceName
</td> </td>
<td class="description"> <td class="description">
@if (item.UserId != null) @if (item.UserId != null)

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