Skip to content

Commit

Permalink
- Fix #25
Browse files Browse the repository at this point in the history
- Update Nuget Packages
- Refactor code a bit
  • Loading branch information
Seji64 committed Aug 26, 2024
1 parent 640e306 commit 5ebdc4b
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 60 deletions.
4 changes: 2 additions & 2 deletions src/LAPS-WebUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
<ItemGroup>
<PackageReference Include="Blazored.SessionStorage" Version="2.4.0" />
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="7.0.0" />
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="7.0.1" />
<PackageReference Include="CurrieTechnologies.Razor.Clipboard" Version="1.6.0" />
<PackageReference Include="LdapForNet" Version="2.7.15" />
<PackageReference Include="Macross.Json.Extensions" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.7" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.8" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
<PackageReference Include="MudBlazor" Version="7.6.0" />
Expand Down
82 changes: 42 additions & 40 deletions src/Pages/LAPS.razor
Original file line number Diff line number Diff line change
Expand Up @@ -54,55 +54,57 @@
</MudTooltip>
<MudIconButton Icon="@Icons.Material.Filled.Close" Color="Color.Default" OnClick="@(() => RemoveComputerCard(computer.Name))" aria-label="close" Disabled=@(computer.Loading) />
</MudStack>
</CardHeaderActions>
</CardHeaderActions>
</MudCardHeader>
<MudCardContent>
<MudLoading Class="d-flex justify-center" Loading="@computer.Loading" LoaderType="LoaderType.Circular">
<div style="position: relative" Class="mb-4">
<MudLoading Loading="@computer.Loading" LoaderType="LoaderType.Circular">
@if (!computer.FailedToRetrieveLapsDetails)
{
<MudTabs Position="Position.Top" Rounded="true" Border="false" ApplyEffectsToContainer="true" PanelClass="pa-4" @key="computer.Name" @ref="_mudTabsDict[computer.Name]">
<MudTabPanel Icon="@Icons.Material.Outlined.Filter1" ID="@("v1")" Text="v1" Disabled=@(!computer.LapsInformations!.Any(x => x is { Version: Enums.LAPSVersion.v1, IsCurrent: true }))>
<LapsInformationDetail LapsInfo="computer.LapsInformations!.SingleOrDefault(x => x.Version == Enums.LAPSVersion.v1)"/>
</MudTabPanel>
<MudTabPanel Icon="@Icons.Material.Outlined.Filter2" ID="@("v2")" Text="v2" Disabled=@(!computer.LapsInformations!.Any(x => x is { Version: Enums.LAPSVersion.v2, IsCurrent: true }))>
<LapsInformationDetail LapsInfo="computer.LapsInformations!.SingleOrDefault(x => x is { Version: Enums.LAPSVersion.v2, IsCurrent: true })"/>
</MudTabPanel>
<MudTabPanel Icon="@Icons.Material.Outlined.History" ID="@("history")" Text="History" Disabled=@(!computer.LapsInformations!.Any(x => x is { Version: Enums.LAPSVersion.v2, IsCurrent: false }))>
<MudSimpleTable Style="overflow-x: auto;" Dense="true" Hover="true" Striped="true">
<thead>
<tr>
<th>Account</th>
<th>Password</th>
<th>Date set</th>
</tr>
</thead>
<tbody>
@foreach (LapsInformation entry in computer.LapsInformations!.Where(x => x is { IsCurrent: false, Version: Enums.LAPSVersion.v2 }))
{
<tr>
<td>
@entry.Account
</td>
<td>
@entry.Password
</td>
<td>
@entry.PasswordSetDate
</td>
</tr>
}
</tbody>
</MudSimpleTable>
</MudTabPanel>
</MudTabs>
<MudTabs Position="Position.Top" Rounded="true" Border="false" ApplyEffectsToContainer="true" PanelClass="pa-4" @key="computer.Name" @ref="_mudTabsDict[computer.Name]">
<MudTabPanel Icon="@Icons.Material.Outlined.Filter1" ID="@("v1")" Text="v1" Disabled=@(!computer.LapsInformations!.Any(x => x is { Version: Enums.LAPSVersion.v1, IsCurrent: true }))>
<LapsInformationDetail LapsInfo="computer.LapsInformations!.SingleOrDefault(x => x.Version == Enums.LAPSVersion.v1)"/>
</MudTabPanel>
<MudTabPanel Icon="@Icons.Material.Outlined.Filter2" ID="@("v2")" Text="v2" Disabled=@(!computer.LapsInformations!.Any(x => x is { Version: Enums.LAPSVersion.v2, IsCurrent: true }))>
<LapsInformationDetail LapsInfo="computer.LapsInformations!.SingleOrDefault(x => x is { Version: Enums.LAPSVersion.v2, IsCurrent: true })"/>
</MudTabPanel>
<MudTabPanel Icon="@Icons.Material.Outlined.History" ID="@("history")" Text="History" Disabled=@(!computer.LapsInformations!.Any(x => x is { Version: Enums.LAPSVersion.v2, IsCurrent: false }))>
<MudSimpleTable Style="overflow-x: auto;" Dense="true" Hover="true" Striped="true">
<thead>
<tr>
<th>Account</th>
<th>Password</th>
<th>Date set</th>
</tr>
</thead>
<tbody>
@foreach (LapsInformation entry in computer.LapsInformations!.Where(x => x is { IsCurrent: false, Version: Enums.LAPSVersion.v2 }))
{
<tr>
<td>
@entry.Account
</td>
<td>
@entry.Password
</td>
<td>
@entry.PasswordSetDate
</td>
</tr>
}
</tbody>
</MudSimpleTable>
</MudTabPanel>
</MudTabs>
}
else
{
<MudAlert Icon="@Icons.Material.Outlined.Warning" ShowCloseIcon="false" Variant="Variant.Outlined" Severity="Severity.Warning">
<MudText Typo="Typo.inherit">No permission to retrieve LAPS Password or no LAPS Password set!</MudText>
</MudAlert>
<MudAlert Icon="@Icons.Material.Outlined.Warning" ShowCloseIcon="false" Variant="Variant.Outlined" Severity="Severity.Warning">
<MudText Typo="Typo.inherit">No permission to retrieve LAPS Password or no LAPS Password set!</MudText>
</MudAlert>
}
</MudLoading>
</div>
</MudCardContent>
</MudCard>
</MudItem>
Expand Down
67 changes: 49 additions & 18 deletions src/Services/LDAPService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public async Task<bool> ClearLapsPassword(string domainName, LdapCredential ldap
{
throw new Exception("Failed to get LDAP Credentials");
}
using LdapConnection? ldapConnection = await CreateBindAsync(domainName, ldapCredential.UserName, ldapCredential.Password) ?? throw new Exception("LDAP bind failed!");
using LdapConnection ldapConnection = await CreateBindAsync(domainName, ldapCredential.UserName, ldapCredential.Password) ?? throw new Exception("LDAP bind failed!");

string attribute = version switch
{
Expand All @@ -99,11 +99,45 @@ public async Task<bool> ClearLapsPassword(string domainName, LdapCredential ldap

return response.ResultCode == ResultCode.Success;
}

private static string EscapeLdapSearchFilter(string searchFilter)
{
StringBuilder escape = new StringBuilder();
foreach (char current in searchFilter)
{
switch (current)
{
case '\\':
escape.Append(@"\5c");
break;
case '*':
escape.Append(@"\2a");
break;
case '(':
escape.Append(@"\28");
break;
case ')':
escape.Append(@"\29");
break;
case '\u0000':
escape.Append(@"\00");
break;
case '/':
escape.Append(@"\2f");
break;
default:
escape.Append(current);
break;
}
}

return escape.ToString();
}

public async Task<AdComputer?> GetAdComputerAsync(string domainName, LdapCredential ldapCredential, string distinguishedName)
{
AdComputer? adComputer = null;
Domain? domain = _domains.Value.SingleOrDefault(x => x.Name == domainName) ?? throw new Exception($"No configured domain found with name {domainName}");
AdComputer? adComputer;
Domain domain = _domains.Value.SingleOrDefault(x => x.Name == domainName) ?? throw new Exception($"No configured domain found with name {domainName}");

if (ldapCredential is null)
{
Expand All @@ -117,11 +151,10 @@ public async Task<bool> ClearLapsPassword(string domainName, LdapCredential ldap
}

string? defaultNamingContext = domain.Ldap.SearchBase;

string sanitizedDistinguishedName =
distinguishedName.Replace("(", "0x28").Replace(")", "0x29").Replace(@"\", "0x5c");

LdapEntry? ldapSearchResult = (await ldapConnection.SearchAsync(defaultNamingContext, $"(&(objectCategory=computer)(distinguishedName={sanitizedDistinguishedName}))",null, LdapSearchScope.LDAP_SCOPE_SUB)).SingleOrDefault();
string ldapFilter = $"(&(objectCategory=computer)(distinguishedName={distinguishedName}))";

LdapEntry? ldapSearchResult = (await ldapConnection.SearchAsync(defaultNamingContext, ldapFilter)).SingleOrDefault();

if (ldapSearchResult != null)
{
Expand All @@ -139,8 +172,8 @@ public async Task<bool> ClearLapsPassword(string domainName, LdapCredential ldap
ComputerName = adComputer.Name,
Version = LAPSVersion.v1,
Account = null,
Password = ldapSearchResult.DirectoryAttributes["ms-Mcs-AdmPwd"].GetValues<string>().First().ToString(),
PasswordExpireDate = DateTime.FromFileTimeUtc(Convert.ToInt64(ldapSearchResult.DirectoryAttributes["ms-Mcs-AdmPwdExpirationTime"].GetValues<string>().First().ToString())).ToLocalTime(),
Password = ldapSearchResult.DirectoryAttributes["ms-Mcs-AdmPwd"].GetValues<string>().First(),
PasswordExpireDate = DateTime.FromFileTimeUtc(Convert.ToInt64(ldapSearchResult.DirectoryAttributes["ms-Mcs-AdmPwdExpirationTime"].GetValues<string>().First())).ToLocalTime(),
IsCurrent = true,
PasswordSetDate = null
};
Expand All @@ -156,12 +189,12 @@ public async Task<bool> ClearLapsPassword(string domainName, LdapCredential ldap

if (ldapSearchResult.DirectoryAttributes.Any(x => x.Name == fieldName) && (domain.Laps.ForceVersion == LAPSVersion.All || domain.Laps.ForceVersion == LAPSVersion.v2))
{
MsLapsPayload? msLapsPayload = null;
MsLapsPayload? msLapsPayload;
string ldapValue;

if (domain.Laps.EncryptionDisabled)
{
ldapValue = ldapSearchResult.DirectoryAttributes["msLAPS-Password"].GetValues<string>().First().ToString();
ldapValue = ldapSearchResult.DirectoryAttributes["msLAPS-Password"].GetValues<string>().First();
}
else
{
Expand All @@ -178,7 +211,7 @@ public async Task<bool> ClearLapsPassword(string domainName, LdapCredential ldap
Account = msLapsPayload.ManagedAccountName,
Password = msLapsPayload.Password,
WasEncrypted = !domain.Laps.EncryptionDisabled,
PasswordExpireDate = DateTime.FromFileTimeUtc(Convert.ToInt64(ldapSearchResult.DirectoryAttributes["msLAPS-PasswordExpirationTime"].GetValues<string>().First().ToString())).ToLocalTime(),
PasswordExpireDate = DateTime.FromFileTimeUtc(Convert.ToInt64(ldapSearchResult.DirectoryAttributes["msLAPS-PasswordExpirationTime"].GetValues<string>().First())).ToLocalTime(),
IsCurrent = true,
PasswordSetDate = DateTime.FromFileTimeUtc(Int64.Parse(msLapsPayload.PasswordUpdateTime!, System.Globalization.NumberStyles.HexNumber)).ToLocalTime()

Expand Down Expand Up @@ -275,17 +308,15 @@ private static async Task<string> DecryptLapsPayload(byte[] value, LdapCredentia
public async Task<List<AdComputer>> SearchAdComputersAsync(string domainName, LdapCredential ldapCredential, string query)
{
List<AdComputer> result = [];
Domain? domain = _domains.Value.SingleOrDefault(x => x.Name == domainName) ?? throw new Exception($"No configured domain found with name {domainName}");
Domain domain = _domains.Value.SingleOrDefault(x => x.Name == domainName) ?? throw new Exception($"No configured domain found with name {domainName}");

if (ldapCredential is null)
{
throw new Exception("Failed to get LDAP Credentials");
}

string sanitizedQuery = query.Replace("(", "0x28").Replace(")", "0x29").Replace(@"\", "0x5c");

using LdapConnection? ldapConnection = await CreateBindAsync(domainName, ldapCredential.UserName, ldapCredential.Password);
string filter = $"(&(objectCategory=computer)(name={sanitizedQuery}{(sanitizedQuery.EndsWith('*') ? string.Empty : '*')}))";
string filter = $"(&(objectCategory=computer)(name={query}{(query.EndsWith('*') ? string.Empty : '*')}))";
string[] propertiesToLoad = new string[] { "cn", "distinguishedName" };
string? defaultNamingContext = domain.Ldap.SearchBase;

Expand All @@ -296,9 +327,9 @@ public async Task<List<AdComputer>> SearchAdComputersAsync(string domainName, Ld
throw new Exception("LDAP Bind failed!");
}

IList<LdapEntry>? ldapSearchResults = await ldapConnection.SearchAsync(defaultNamingContext, filter, propertiesToLoad, LdapSearchScope.LDAP_SCOPE_SUB);
IList<LdapEntry>? ldapSearchResults = await ldapConnection.SearchAsync(defaultNamingContext, filter, propertiesToLoad);

result.AddRange(ldapSearchResults.Select(o => new AdComputer(o.Dn, o.DirectoryAttributes["cn"].GetValues<string>().First())).ToList());
result.AddRange(ldapSearchResults.Select(o => new AdComputer(EscapeLdapSearchFilter(o.Dn), o.DirectoryAttributes["cn"].GetValues<string>().First())).ToList());
}
catch (Exception ex)
{
Expand Down

0 comments on commit 5ebdc4b

Please sign in to comment.