Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DUO MFA Added! #284

Merged
merged 2 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion BLAZAM/BLAZAM.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<ServerGarbageCollection>false</ServerGarbageCollection>
<AssemblyVersion>0.9.0</AssemblyVersion>
<Version>2024.03.30.1734</Version>
<Version>2024.04.01.1814</Version>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<RootNamespace>BLAZAM</RootNamespace>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
Expand Down
10 changes: 8 additions & 2 deletions BLAZAM/Middleware/UserStateMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ public Task Invoke(HttpContext httpContext, ICurrentUserStateService currentUser
currentUserStateService.State = state;

}
if (httpContext.Connection != null && httpContext.Connection.RemoteIpAddress != null && currentUserStateService.State != null && currentUserStateService.State.IPAddress != httpContext.Connection.RemoteIpAddress)
currentUserStateService.State.IPAddress = httpContext.Connection.RemoteIpAddress;
if (httpContext.Connection != null &&
httpContext.Connection.RemoteIpAddress != null &&
currentUserStateService.State != null &&
currentUserStateService.State.IPAddress != httpContext.Connection.RemoteIpAddress.ToString())
{
currentUserStateService.State.IPAddress = httpContext.Connection.RemoteIpAddress.ToString();
}

}
return _next(httpContext);
}
Expand Down
43 changes: 30 additions & 13 deletions BLAZAM/Pages/Login.razor
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@page "/login"
@using BLAZAM.Static;

@inject IHttpContextAccessor HttpContextAccessor
@inherits ValidatedForm
@inject IJSRuntime JSRuntime
@layout LoginLayout
Expand Down Expand Up @@ -64,6 +64,10 @@
}
else if (ApplicationInfo.InDemoMode)
{
LoginRequest.Username = "demo";
LoginRequest.Password = "demo";


<MudContainer Class="d-none">

<MudTextField name="username" Value=@("demo") hidden />
Expand Down Expand Up @@ -96,7 +100,11 @@

</MudElement>
</MudContainer>

<AppModal @ref=_mfaModal Title="MFA" Options="new(){ MaxWidth=MaxWidth.Large, FullWidth=true}">
<ChildContent>
<iframe class="w-100" style="height:70vh" src="@_modalUri"></iframe>
</ChildContent>
</AppModal>
<style>
.brand-icon {
position: fixed;
Expand All @@ -119,20 +127,22 @@
@code {
MudTextField<string>? _usernameTextField;
MudTextField<string>? _passwordTextField;

string? _modalUri;
bool attemptingSignIn = false;
string redirectUrl;

AppModal? _mfaModal;
bool DemoCustomLogin = false;
LoginRequest LoginRequest = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
redirectUrl = Nav.Uri;
LoginRequest.ReturnUrl = Nav.Uri;
if (Monitor.AppReady != ServiceConnectionState.Up)
Monitor.OnAppReadyChanged += AppReadyChanged;
}


async void AppReadyChanged(ServiceConnectionState state)
{
if (state == ServiceConnectionState.Up)
Expand All @@ -144,11 +154,12 @@
{
attemptingSignIn = true;
await InvokeAsync(StateHasChanged);
string? authenticationResult;
LoginRequest? authenticationResult;
if (ValidateInput(out authenticationResult))
try
{
authenticationResult = await JSRuntime.InvokeAsync<string>("attemptSignIn", null);
var response = await JSRuntime.InvokeAsync<string>("attemptSignIn", LoginRequest);
authenticationResult = JsonConvert.DeserializeObject<LoginRequest>(response);
}
catch (Exception ex)
{
Expand All @@ -158,11 +169,10 @@

attemptingSignIn = false;

if (!authenticationResult.IsNullOrEmpty())
if (authenticationResult!=null)
{
int resultCode = int.Parse(authenticationResult);

switch ((LoginResultStatus)resultCode)
switch (authenticationResult.AuthenticationResult)
{

case LoginResultStatus.NoUsername:
Expand All @@ -189,27 +199,34 @@
case LoginResultStatus.UnknownFailure:
SnackBarService.Error("Unknown error while attempting to log in");

break;
case LoginResultStatus.MFARequested:
attemptingSignIn = true;
Nav.NavigateTo(authenticationResult.MFARedirect);
break;
case LoginResultStatus.OK:
attemptingSignIn = true;
Nav.NavigateTo(redirectUrl, true);
if (!_mfaModal.IsShown)
{
Nav.NavigateTo(redirectUrl, true);
}
break;
}

}

await InvokeAsync(StateHasChanged);
}
bool ValidateInput(out string? validationResult)
bool ValidateInput(out LoginRequest? validationResult)
{
validationResult = null;
if (LoginRequest.Valid || (ApplicationInfo.InDemoMode && !DemoCustomLogin)) return true;


if (LoginRequest.Password.IsNullOrEmpty())
validationResult = ((int)LoginResultStatus.NoPassword).ToString();
validationResult.AuthenticationResult = LoginResultStatus.NoPassword;
if (LoginRequest.Username.IsNullOrEmpty())
validationResult = ((int)LoginResultStatus.NoUsername).ToString();
validationResult.AuthenticationResult = LoginResultStatus.NoUsername;
return false;
}

Expand Down
8 changes: 8 additions & 0 deletions BLAZAM/Pages/MFACallback.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@page
@using BLAZAM.Pages
@model MFACallbackModel
<p>Authenticated... logging you in...</p>
@Model.AuthResponse
@{

}
94 changes: 94 additions & 0 deletions BLAZAM/Pages/MFACallback.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using BLAZAM.Server;
using BLAZAM.Services;
using BLAZAM.Services.Audit;
using BLAZAM.Services.Duo;
using BLAZAM.Session.Interfaces;
using DuoUniversal;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Identity.Client;
using Octokit;
using Org.BouncyCastle.Ocsp;
using System.Security.Claims;
using System.Text.Json;

namespace BLAZAM.Pages
{
public class MFACallbackModel : PageModel
{
private readonly AuditLogger _audit;
private readonly AppAuthenticationStateProvider _auth;
private readonly IDuoClientProvider _duoClientProvider;
private readonly IApplicationUserStateService _userStateService;

public MFACallbackModel(IDuoClientProvider duoClientProvider,
IApplicationUserStateService userStateService,
AppAuthenticationStateProvider appAuthenticationStateProvider,
AuditLogger logger
)
{
_audit = logger;
_auth = appAuthenticationStateProvider;
_duoClientProvider = duoClientProvider;
_userStateService = userStateService;
}

public string AuthResponse { get; private set; }

public async Task<IActionResult> OnGet(string? state=null,string? code=null)
{
// Duo should have sent a 'state' and 'code' parameter. If either is missing or blank, something is wrong.
if (string.IsNullOrWhiteSpace(state))
{
throw new DuoException("Required state value was empty");
}
if (string.IsNullOrWhiteSpace(code))
{
throw new DuoException("Required code value was empty");
}
if(User!=null && User.HasClaim(c=>c.Type == ClaimTypes.Rsa))
{
if (state == User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Rsa)?.Value)
{

//This is a valid callback for this user
var user = _userStateService.GetMFAUser(state);
if (user != null)
{



// Get the Duo client again. This can be either be cached in the session or newly built.
// The only stateful information in the Client is your configuration, so you could even use the same client for multiple
// user authentications if desired.
Client duoClient = _duoClientProvider.GetDuoClient();
var username = user.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.WindowsAccountName)?.Value;
// Get a summary of the authentication from Duo. This will trigger an exception if the username does not match.
IdToken token = await duoClient.ExchangeAuthorizationCodeFor2faResult(code, username);
if (token.AuthResult.Result.Equals("allow", StringComparison.InvariantCultureIgnoreCase))
{
var authenticatedState = await _auth.SetUser(user.User);
await HttpContext.SignInAsync(user.User);
await _audit.Logon.Login(user.User,HttpContext.Connection.RemoteIpAddress?.ToString());
return new RedirectResult("/");
}



}




}
else
{
throw new DuoException("Session state did not match the expected state");
}

}
return Page();
}
}
}
2 changes: 1 addition & 1 deletion BLAZAM/Pages/SSO.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public async Task<IActionResult> OnPost([FromFormAttribute]LoginRequest req)
try
{
var result = await Auth.Login(req);
if (result != null && result.Status == LoginResultStatus.OK)
if (result != null && result.AuthenticationResult == LoginResultStatus.OK)
{
await HttpContext.SignInAsync(result.AuthenticationState.User);
await AuditLogger.Logon.Login(result.AuthenticationState.User);
Expand Down
4 changes: 2 additions & 2 deletions BLAZAM/Pages/SignIn.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
}
<div class="container-fluid">
Redirecting...
</div>
</div>@*
<script>
window.location.href='@Model.RedirectUri';
</script>
</script> *@
29 changes: 17 additions & 12 deletions BLAZAM/Pages/SignIn.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace BLAZAM.Server.Pages
[IgnoreAntiforgeryToken]
public class SignInModel : PageModel
{
public SignInModel(AppAuthenticationStateProvider auth, NavigationManager _nav,ConnMonitor _monitor,AuditLogger logger)
public SignInModel(AppAuthenticationStateProvider auth, NavigationManager _nav, ConnMonitor _monitor, AuditLogger logger)
{
Auth = auth;
Nav = _nav;
Expand All @@ -30,7 +30,7 @@ public SignInModel(AppAuthenticationStateProvider auth, NavigationManager _nav,C
public ConnMonitor Monitor { get; private set; }
public AuditLogger AuditLogger { get; private set; }

public void OnGet(string returnUrl="")
public void OnGet(string returnUrl = "")
{
ViewData["Layout"] = "_Layout";
if (returnUrl.IsUrlLocalToHost())
Expand All @@ -39,39 +39,44 @@ public void OnGet(string returnUrl="")
}
}


/// <summary>
/// The authentication endpoint for web clients
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
public async Task<IActionResult> OnPost([FromFormAttribute]LoginRequest req)
public async Task<IActionResult> OnPost([FromFormAttribute] LoginRequest req)
{
try
{
req.IPAddress = HttpContext.Connection.RemoteIpAddress;
}catch(Exception ex)
req.IPAddress = HttpContext.Connection.RemoteIpAddress?.ToString();
}
catch (Exception ex)
{
Loggers.SystemLogger.Error("Error setting ip address for login request {@Error}", ex);
}
try
{

var result = await Auth.Login(req);
if (result != null && result.Status == LoginResultStatus.OK)
req.Password = null;
req.AuthenticationResult = result.AuthenticationResult;
if (result != null && (result.AuthenticationResult == LoginResultStatus.OK || result.AuthenticationResult == LoginResultStatus.MFARequested))
{

await HttpContext.SignInAsync(result.AuthenticationState.User);
await AuditLogger.Logon.Login(result.AuthenticationState.User,req.IPAddress);
if (result.AuthenticationState.User.Identity?.IsAuthenticated == true)
await AuditLogger.Logon.Login(result.AuthenticationState.User, req.IPAddress);
}
return new ObjectResult(result?.Status);
return new JsonResult(req);

}
catch (Exception ex)
{
return new ObjectResult(ex.Message);
}


}


Expand Down
2 changes: 1 addition & 1 deletion BLAZAM/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public static void Main(string[] args)
AppInstance.UseCookiePolicy();
AppInstance.UseAuthentication();
AppInstance.UseAuthorization();

AppInstance.UseSession();
//AppInstance.MapControllers();
AppInstance.MapBlazorHub();
AppInstance.MapFallbackToPage("/_Host");
Expand Down
3 changes: 3 additions & 0 deletions BLAZAM/ProgramEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@ public static void InvokeTemplatesChanged()
{
TemplatesChanged?.Invoke();
}



}
}
Loading
Loading