Skip to content

Commit

Permalink
Fix Copy Password Bug, Improve UI, Update NuGet Packages
Browse files Browse the repository at this point in the history
  • Loading branch information
Seji64 committed Sep 19, 2023
1 parent 6404cbd commit 5a43e33
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 87 deletions.
3 changes: 3 additions & 0 deletions src/Components/AutoCompleteMoreItemsFound.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<MudText Typo="Typo.body2" Color="Color.Warning" Align="Align.Center" Class="px-4 py-1">
Too much Items found - please refine your search
</MudText>
3 changes: 3 additions & 0 deletions src/Components/AutoCompleteNoItemsFound.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<MudText Typo="Typo.body1" Align="Align.Center" Class="px-4">
No items found
</MudText>
23 changes: 19 additions & 4 deletions src/Components/LapsInformationDetail.razor
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
@inherits MudComponentBase
@inject ISnackbar Snackbar
@inject ClipboardService clipboard
@using CurrieTechnologies.Razor.Clipboard

@if (LapsInfo != null)
{
<MudStack Spacing="2">
<MudField Label="Password" Variant="Variant.Text" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.Password" AdornmentColor="Color.Default" aria-label="LAPS Password">@LapsInfo.Password</MudField>

<MudTextFieldExtended T="string" Label="Password" Variant="Variant.Text" InputMode="InputMode.none" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.Password" AdornmentColor="Color.Default" aria-label="LAPS Password" Text="@LapsInfo.Password" ReadOnly="true">
<AdornmentStart>
<MudIcon Icon="@Icons.Material.Outlined.Password" />
</AdornmentStart>
<AdornmentEnd>
<MudTooltip Text="@(IsCopyToClipboardSupported ? "Copy Password to clipboard" : "Your browser does not support Clipboard API - please use STRG+C")">
<MudIconButton Class="mb-1" Disabled="@(IsCopyButtonDisabled())" Icon="@Icons.Material.Outlined.ContentCopy" Color="Color.Primary" OnClick="@(async (_) => await CopyLAPSPasswordToClipboardAsync())" aria-label="Copy Password to clipbard" />
</MudTooltip>

</AdornmentEnd>
</MudTextFieldExtended>
@if (LapsInfo.Account != null)
{
<MudField Label="Account" Variant="Variant.Text" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.AccountCircle" AdornmentColor="Color.Default" aria-label="LAPS Managed Account">@LapsInfo.Account</MudField>
<MudTextField T="string" ReadOnly="true" Label="Account" Variant="Variant.Text" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.AccountCircle" AdornmentColor="Color.Default" aria-label="LAPS Managed Account" Text="@LapsInfo.Account"/>
}
@if (LapsInfo.PasswordSetDate != null)
{
<MudField Label="Set Date" Variant="Variant.Text" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.MoreTime" AdornmentColor="Color.Default" aria-label="LAPS Password Set Date">@LapsInfo.PasswordSetDate</MudField>
<MudTextField T="string" ReadOnly="true" Label="Set Date" Variant="Variant.Text" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.MoreTime" AdornmentColor="Color.Default" aria-label="LAPS Password Set Date" Text="@LapsInfo.PasswordSetDate.ToString()"/>
}
<MudField Label="Expire Date" Variant="Variant.Text" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.HourglassBottom" AdornmentColor="Color.Default" aria-label="LAPS Password Expire Date">@LapsInfo.PasswordExpireDate</MudField>
<MudTextField T="string" ReadOnly="true" Label="Expire Date" Variant="Variant.Text" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.HourglassBottom" AdornmentColor="Color.Default" aria-label="LAPS Password Expire Date" Text="@LapsInfo.PasswordExpireDate.ToString()"/>
</MudStack>
}
27 changes: 27 additions & 0 deletions src/Components/LapsInformationDetail.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,32 @@ namespace LAPS_WebUI.Components
public partial class LapsInformationDetail : MudComponentBase
{
[Parameter] public LapsInformation? LapsInfo { get; set; }
[Parameter] public MudTabs? MudTab { get; set; }
private bool IsCopyToClipboardSupported { get; set; }

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
IsCopyToClipboardSupported = await clipboard.IsSupportedAsync();
}
}

private bool IsCopyButtonDisabled()
{
return !IsCopyToClipboardSupported || LapsInfo is null || string.IsNullOrEmpty(LapsInfo.Password);
}
private async Task CopyLAPSPasswordToClipboardAsync()
{
if (LapsInfo != null && !string.IsNullOrEmpty(LapsInfo.Password))
{
await clipboard.WriteTextAsync(LapsInfo.Password);
Snackbar.Add("Copied password to clipboard!", Severity.Success);
}
else
{
Snackbar.Add("Failed to copy password to clipboard!", Severity.Error);
}
}
}
}
3 changes: 2 additions & 1 deletion src/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ FROM base AS final
WORKDIR /app
RUN apt update && \
apt upgrade -y && \
apt install --no-install-recommends -y ca-certificates libldap-2.4-2 gcc python3 python3-dev python3-pip libkrb5-dev && \
apt install --no-install-recommends -y ca-certificates libldap-2.4-2 gcc python3 python3-dev python3-pip libkrb5-dev curl && \
apt clean && \
rm -rf /var/lib/apt/lists/* && \
ln -s /usr/lib/x86_64-linux-gnu/libldap-2.4.so.2 /usr/lib/libldap.so.2 && \
ln -s /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2 /usr/lib/liblber.so.2 && \
pip3 install dpapi-ng[kerberos]
COPY --from=publish /app/publish .
HEALTHCHECK CMD curl --fail http://localhost/healthz || exit
ENTRYPOINT ["dotnet", "LAPS-WebUI.dll"]
9 changes: 5 additions & 4 deletions src/LAPS-WebUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Blazored.SessionStorage" Version="2.3.0" />
<PackageReference Include="Blazored.SessionStorage" Version="2.4.0" />
<PackageReference Include="CliWrap" Version="3.6.4" />
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="6.5.10" />
<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="7.0.9" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="7.0.11" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.175" />
<PackageReference Include="MudBlazor" Version="6.7.0" />
<PackageReference Include="MudBlazor" Version="6.10.0" />
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
Expand Down
1 change: 1 addition & 0 deletions src/Models/LapsInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace LAPS_WebUI.Models
{
public class LapsInformation
{
public required string ComputerName { get; set; }
public string? Password { get; set; }
public string? Account { get; set; }
public DateTime? PasswordExpireDate { get; set; }
Expand Down
35 changes: 18 additions & 17 deletions src/Pages/LAPS.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,18 @@
@inject NavigationManager NavigationManager
@inject ILdapService LDAPService
@inject ISnackbar Snackbar
@inject ClipboardService clipboard
@using CurrieTechnologies.Razor.Clipboard

<MudContainer MaxWidth="MaxWidth.Medium" Class="mt-5">
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-5">

@if (!Authenticated)
{
<MudAlert Elevation="3" Severity="Severity.Error" Variant="Variant.Outlined">Access denied!</MudAlert>
}
else
{
<MudAutocomplete Label="Search by Computername" HelperTextOnFocus="true" HelperText="Minimum query length is 4. Wildcard (*) is supported" ShowProgressIndicator="true"
MaxItems=null ValueChanged="@(async (ADComputer a) => OnSelectedItemChangedAsync(a).AndForget())" MinCharacters="4" SearchFunc="@SearchAsync" ToStringFunc="@_ADComputerToStringConverter" Immediate="true" ResetValueOnEmptyText="true"
AdornmentIcon="@Icons.Material.Outlined.Search" AdornmentColor="Color.Default" Adornment="Adornment.Start" SelectValueOnTab=true Clearable=true aria-label="Search Computer" >
<MudAutocomplete Label="Search by Computername" HelperTextOnFocus="true" HelperText="Minimum query length is 4. Wildcard (*) is supported" Variant="Variant.Outlined" ShowProgressIndicator="true" AutoFocus="true" Immediate="true" ResetValueOnEmptyText="true" @ref="AutoCompleteSearchBox"
MaxItems=null ValueChanged="@(async (ADComputer a) => OnSelectedItemChangedAsync(a).AndForget())" MinCharacters="4" MaxLength="15" SearchFunc="@SearchAsync" ToStringFunc="@(x => (x is null ? string.Empty : x.Name))"
AdornmentIcon="@Icons.Material.Outlined.Search" AdornmentColor="Color.Default" Adornment="Adornment.Start" SelectOnClick="true" SelectValueOnTab=true Clearable=true aria-label="Search Computer" >
<ItemTemplate Context="e">
<MudText>
<MudIcon Icon="@Icons.Material.Filled.Computer" Class="mb-n1 mr-3" aria-label=@($"{e.Name}")/>@($"{e.Name}")
Expand All @@ -27,28 +25,39 @@
<MudIcon Icon="@Icons.Material.Filled.Computer" Class="mb-n1 mr-3" aria-label=@($"{e.Name}")/>@($"{e.Name}")
</MudText>
</ItemSelectedTemplate>
<MoreItemsTemplate>
<AutoCompleteMoreItemsFound/>
</MoreItemsTemplate>
<NoItemsTemplate>
<AutoCompleteNoItemsFound />
</NoItemsTemplate>
</MudAutocomplete>

<MudGrid Class="mt-8" Justify="Justify.SpaceEvenly">

@foreach(var computer in SelectedComputers)
{
<MudItem xs="10" sm="8">
<MudItem xs="10" lg="6">
<MudCard>
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.h5">@computer.Name</MudText>
</CardHeaderContent>
<CardHeaderActions>
<MudIconButton Icon="@Icons.Material.Filled.Close" Color="Color.Default" OnClick="@(() => RemoveComputerCard(computer.Name))" aria-label="close" Disabled=@(computer.Loading)/>
<MudStack Row="true" Spacing="0">
<MudTooltip Text="Refresh">
<MudIconButton Icon="@Icons.Material.Filled.Refresh" Color="Color.Primary" OnClick="@(async e => await RefreshComputerDetailsAsync(computer.Name))" aria-label="refresh" Disabled=@(computer.Loading) />
</MudTooltip>
<MudIconButton Icon="@Icons.Material.Filled.Close" Color="Color.Default" OnClick="@(() => RemoveComputerCard(computer.Name))" aria-label="close" Disabled=@(computer.Loading) />
</MudStack>
</CardHeaderActions>
</MudCardHeader>
<MudCardContent>
@if(!computer.Loading)
{
@if(!computer.FailedToRetrieveLAPSDetails)
{
<MudTabs Position="Position.Top" Rounded="true" Border="false" ApplyEffectsToContainer="true" @ref="_tabs" PanelClass="pa-4">
<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.Version == Enums.LAPSVersion.v1 && x.IsCurrent))>
<LapsInformationDetail LapsInfo="computer.LAPSInformations!.SingleOrDefault(x => x.Version == Enums.LAPSVersion.v1)" />
</MudTabPanel>
Expand Down Expand Up @@ -98,14 +107,6 @@
</div>
}
</MudCardContent>
<MudCardActions>
<MudTooltip Text="@(IsCopyToClipboardSupported ? "Copy Password to clipboard" : "Your browser does not support Clipboard API - please use STRG+C")">
<MudIconButton Disabled="@(!IsCopyToClipboardSupported || (_tabs != null && _tabs.ActivePanel != null && _tabs.ActivePanel.ID.ToString() == "history"))" Icon="@Icons.Material.Filled.FileCopy" Color="Color.Tertiary" OnClick="@(async e => await CopyLAPSPasswordToClipboardAsync(computer))" aria-label="Copy Password to clipbard"/>
</MudTooltip>
<MudTooltip Text="Refresh">
<MudIconButton Icon="@Icons.Material.Filled.Refresh" Color="Color.Tertiary" OnClick="@(async e => await RefreshComputerDetailsAsync(computer.Name))" aria-label="refresh"/>
</MudTooltip>
</MudCardActions>
</MudCard>
</MudItem>
}
Expand Down
61 changes: 11 additions & 50 deletions src/Pages/LAPS.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,11 @@ namespace LAPS_WebUI.Pages
{
public partial class LAPS
{
private bool IsCopyToClipboardSupported { get; set; }

private readonly Dictionary<string, MudTabs?> MudTabsDict = new();
private MudAutocomplete<ADComputer>? AutoCompleteSearchBox;
private bool Authenticated { get; set; } = true;

private LdapForNet.LdapCredential? LdapCredential { get; set; }

private List<ADComputer> SelectedComputers { get; set; } = new List<ADComputer>();

private MudTabs? _tabs;

readonly Func<ADComputer, string> _ADComputerToStringConverter = p => (p is null ? string.Empty : p.Name);

protected override async Task OnAfterRenderAsync(bool firstRender)
{
Authenticated = await sessionManager.IsUserLoggedInAsync();
Expand All @@ -26,14 +19,9 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
NavigationManager.NavigateTo("/login");
}

if (firstRender)
if (firstRender && Authenticated)
{
if (Authenticated)
{
LdapCredential = await sessionManager.GetLdapCredentialsAsync();
}

IsCopyToClipboardSupported = await clipboard.IsSupportedAsync();
LdapCredential = await sessionManager.GetLdapCredentialsAsync();
}

await InvokeAsync(StateHasChanged);
Expand All @@ -43,40 +31,11 @@ private async Task OnSelectedItemChangedAsync(ADComputer value)
{
if (value != null && !string.IsNullOrEmpty(value.Name) && !SelectedComputers.Exists(x => x.Name == value.Name))
{
AutoCompleteSearchBox?.Clear();
MudTabsDict.Add(value.Name, null);
await FetchComputerDetailsAsync(value.Name);
}
}

private async Task CopyLAPSPasswordToClipboardAsync(ADComputer computer)
{
string password = string.Empty;

if (_tabs != null && _tabs.ActivePanel != null)
{
if (_tabs.ActivePanel.ID.ToString() == "v1") // aka LAPS v1
{
password = computer.LAPSInformations!.Single(x => x.IsCurrent && x.Version == Enums.LAPSVersion.v1).Password!;
}
if (_tabs.ActivePanel.ID.ToString() == "v2") // aka LAPS v2
{
password = computer.LAPSInformations!.Single(x => x.IsCurrent && x.Version == Enums.LAPSVersion.v2).Password!;
}
}

if (!string.IsNullOrEmpty(password))
{
await clipboard.WriteTextAsync(password);
Snackbar.Add("Copied password to clipboard!", Severity.Success);
}
else
{
Snackbar.Add("Failed to copy password to clipboard!", Severity.Error);
}



}

private async Task RefreshComputerDetailsAsync(string computerName)
{

Expand Down Expand Up @@ -135,10 +94,12 @@ private async Task FetchComputerDetailsAsync(string computerName)
selectedComputer.LAPSInformations = AdComputerObject.LAPSInformations;
selectedComputer.FailedToRetrieveLAPSDetails = AdComputerObject.FailedToRetrieveLAPSDetails;

if (!selectedComputer.FailedToRetrieveLAPSDetails && _tabs != null)
MudTabsDict.TryGetValue(computerName, out MudTabs? _tab);

if (!selectedComputer.FailedToRetrieveLAPSDetails && _tab != null)
{
await InvokeAsync(StateHasChanged);
_tabs.ActivatePanel(_tabs.Panels.First(x => !x.Disabled));
_tab.ActivatePanel(_tab.Panels.First(x => !x.Disabled));
}

}
Expand All @@ -164,7 +125,7 @@ private async Task<IEnumerable<ADComputer>> SearchAsync(string value)
return new List<ADComputer>();
}

var tmp = (await LDAPService.SearchADComputersAsync(LdapCredential ?? await sessionManager.GetLdapCredentialsAsync(), value));
var tmp = await LDAPService.SearchADComputersAsync(LdapCredential ?? await sessionManager.GetLdapCredentialsAsync(), value);

if (tmp != null)
{
Expand Down
19 changes: 8 additions & 11 deletions src/Pages/Login.razor
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@
<MudTextField Label="Username" Immediate="true" @bind-Value="_loginRequest.Username" For="@(() => _loginRequest.Username)" aria-label="username"/>
<MudTextField Label="Password" Immediate="true" Class="mt-3" @bind-Value="_loginRequest.Password" For="@(() => _loginRequest.Password)" InputType="InputType.Password" aria-label="password"/>
</MudStack>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Disabled="@_processing" Color="Color.Primary" StartIcon="@Icons.Material.Outlined.Login" aria-label="login">
@if (_processing)
{
<MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true"/>
<MudText Class="ms-2">Logging you in...</MudText>
}
else
{
<MudText>Log in</MudText>
}
</MudButton>
<MudLoadingButton @bind-Loading="_processing" LoadingAdornment="Adornment.Start" ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Outlined.Login" aria-label="login">
<ChildContent>
Login
</ChildContent>
<LoadingContent>
Logging in...
</LoadingContent>
</MudLoadingButton>
</MudStack>
</MudPaper>

Expand Down
2 changes: 2 additions & 0 deletions src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
builder.Services.AddBlazoredSessionStorage();
builder.Services.AddClipboard();
builder.Services.AddDataProtection();
builder.Services.AddHealthChecks();

builder.Services.Configure<LdapOptions>(builder.Configuration.GetSection("LDAP"));
builder.Services.Configure<LapsOptions>(builder.Configuration.GetSection("LAPS"));
Expand All @@ -50,5 +51,6 @@

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.MapHealthChecks("/healthz");

app.Run();
Loading

0 comments on commit 5a43e33

Please sign in to comment.