Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4ccfe6e
Configuring OpenID Connect Authentication with moodle user id
binon Feb 14, 2025
be2e800
Bump sass from 1.86.2 to 1.86.3 in /Auth/LearningHub.Nhs.Auth (#147)
dependabot[bot] Apr 4, 2025
2d3f7b1
TD-3734: Concurrent Sessions Allowed
Swapnamol Apr 4, 2025
58ea79f
Concurrent Sessions Allowed
Swapnamol Apr 4, 2025
e39319a
user null check
Apr 7, 2025
45ccf1e
Bump webpack from 5.98.0 to 5.99.1 in /Auth/LearningHub.Nhs.Auth (#149)
dependabot[bot] Apr 8, 2025
08fe2fe
Merge pull request #148 from TechnologyEnhancedLearning/Develop/Fixes…
swapnamol-abraham Apr 8, 2025
008ad9f
Bump webpack from 5.99.1 to 5.99.5 in /Auth/LearningHub.Nhs.Auth (#153)
dependabot[bot] Apr 9, 2025
972b40c
Merge branch 'RC' into Develop/POC/TD-5284_retrival_of_moodle_user_id
Apr 10, 2025
e52c59f
Merge pull request #156 from TechnologyEnhancedLearning/release-v1.4.…
AnjuJose011 Apr 11, 2025
09ea76d
added new appsetting value - EnableMoodle
Apr 11, 2025
c322872
Merge pull request #157 from TechnologyEnhancedLearning/Automatic_ver…
AnjuJose011 Apr 11, 2025
a9a7009
Merge pull request #158 from TechnologyEnhancedLearning/Develop/POC/T…
binon Apr 14, 2025
1baf49a
Fixed the style cop issue with the user api (Blank line required)
Swapnamol Apr 14, 2025
6762438
TD-5492: Upgrade .NET Framework Version For Database Project
swapnamol-abraham Apr 24, 2025
8766c8e
Merge pull request #162 from TechnologyEnhancedLearning/Develop/Fixes…
swapnamol-abraham Apr 24, 2025
041b19c
TD-5538: IUncluded web.config in the solution
swapnamol-abraham May 12, 2025
fb0c863
TD-5538: IUncluded web.config in the solution
swapnamol-abraham May 12, 2025
60e679a
corrected
swapnamol-abraham May 12, 2025
04523b5
Merge pull request #171 from TechnologyEnhancedLearning/Develop/Fixes…
swapnamol-abraham May 12, 2025
3dd19cf
TD-4982-Blocked all IdentityServer4 logs from being processed by late…
swapnamol-abraham May 15, 2025
fe62003
Merge pull request #173 from TechnologyEnhancedLearning/Develop/Fixes…
swapnamol-abraham May 15, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


<ItemGroup>
<PackageReference Include="elfhHub.Nhs.Models" Version="3.0.8" />
<PackageReference Include="elfhHub.Nhs.Models" Version="3.0.10" />
<PackageReference Include="LearningHub.Nhs.Models" Version="3.0.33" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Microsoft.TestPlatform.TestHost" Version="17.12.0" />
Expand Down
10 changes: 10 additions & 0 deletions Auth/LearningHub.Nhs.Auth/Configuration/ServiceMappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,22 @@ public static void AddServiceMappings(this IServiceCollection services, IConfigu
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
});

services.AddHttpClient<IMoodleHttpClient, MoodleHttpClient>()
.ConfigurePrimaryHttpMessageHandler(
() => new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
});
}
else
{
services.AddHttpClient<IUserApiHttpClient, UserApiHttpClient>();
services.AddHttpClient<IMoodleHttpClient, MoodleHttpClient>();
}

services.AddScoped<IMoodleApiService, MoodleApiService>();
services.AddDistributedMemoryCache();
services.AddScoped<IExternalSystemService, ExternalSystemService>();
services.AddTransient<IRegistrationService, RegistrationService>();
Expand Down
7 changes: 6 additions & 1 deletion Auth/LearningHub.Nhs.Auth/Configuration/WebSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,10 @@ public class WebSettings
/// Gets or sets a value indicating whether IsPasswordUpdate.
/// </summary>
public bool IsPasswordUpdate { get; set; }
}

/// <summary>
/// Gets or sets a value indicating whether gets or sets a value to Enable Moodle.
/// </summary>
public bool EnableMoodle { get; set; }
}
}
55 changes: 34 additions & 21 deletions Auth/LearningHub.Nhs.Auth/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Azure.Core;
using elfhHub.Nhs.Models.Common;
using elfhHub.Nhs.Models.Enums;
using IdentityModel;
Expand All @@ -22,9 +23,11 @@
using LearningHub.Nhs.Models.Common;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using UAParser;

/// <summary>
/// Account Controller operations.
Expand Down Expand Up @@ -163,34 +166,44 @@ await this.interaction.GrantConsentAsync(

if (loginResult.IsAuthenticated)
{
await this.SignInUser(userId, model.Username.Trim(), model.RememberLogin, context.Parameters["ext_referer"]);

if (context != null)
var uaParser = Parser.GetDefault();
var clientInfo = uaParser.Parse(this.Request.Headers["User-Agent"]);
var result = await this.UserService.CheckUserHasAnActiveSessionAsync(userId);
if (result.Items.Count == 0 || result.Items[0].BrowserName == clientInfo.UA.Family)
{
if (await this.ClientStore.IsPkceClientAsync(context.Client.ClientId))
await this.SignInUser(userId, model.Username.Trim(), model.RememberLogin, context.Parameters["ext_referer"]);

if (context != null)
{
// if the client is PKCE then we assume it's native, so this change in how to
// return the response is for better UX for the end user.
return this.View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
if (await this.ClientStore.IsPkceClientAsync(context.Client.ClientId))
{
// if the client is PKCE then we assume it's native, so this change in how to
// return the response is for better UX for the end user.
return this.View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
}

// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return this.Redirect(model.ReturnUrl);
}

// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
return this.Redirect(model.ReturnUrl);
}

// request for a local page
if (this.Url.IsLocalUrl(model.ReturnUrl))
{
return this.Redirect(model.ReturnUrl);
}
else if (string.IsNullOrEmpty(model.ReturnUrl))
{
return this.Redirect("~/");
// request for a local page
if (this.Url.IsLocalUrl(model.ReturnUrl))
{
return this.Redirect(model.ReturnUrl);
}
else if (string.IsNullOrEmpty(model.ReturnUrl))
{
return this.Redirect("~/");
}
else
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
}
}
else
{
// user might have clicked on a malicious link - should be logged
throw new Exception("invalid return URL");
return this.View("AlreadyActiveSession");
}
}
else if (userId > 0)
Expand Down
17 changes: 17 additions & 0 deletions Auth/LearningHub.Nhs.Auth/Interfaces/IMoodleApiService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace LearningHub.Nhs.Auth.Interfaces
{
using System.Threading.Tasks;

/// <summary>
/// IMoodleApiService.
/// </summary>
public interface IMoodleApiService
{
/// <summary>
/// GetResourcesAsync.
/// </summary>
/// <param name="currentUserId">The current User Id.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
Task<int> GetMoodleUserIdByUsernameAsync(int currentUserId);
}
}
23 changes: 23 additions & 0 deletions Auth/LearningHub.Nhs.Auth/Interfaces/IMoodleHttpClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace LearningHub.Nhs.Auth.Interfaces
{
using System.Net.Http;
using System.Threading.Tasks;

/// <summary>
/// The Moodle Http Client interface.
/// </summary>
public interface IMoodleHttpClient
{
/// <summary>
/// The get cient async.
/// </summary>
/// <returns>The <see cref="Task"/>.</returns>
Task<HttpClient> GetClient();

/// <summary>
/// GetDefaultParameters.
/// </summary>
/// <returns>defaultParameters.</returns>
string GetDefaultParameters();
}
}
7 changes: 7 additions & 0 deletions Auth/LearningHub.Nhs.Auth/Interfaces/IUserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ public interface IUserService
/// </returns>
Task StoreUserHistoryAsync(UserHistoryViewModel userHistory);

/// <summary>
/// check user has an laredy active session.
/// </summary>
/// <param name="userId">The userId.</param>
/// <returns>The <see cref="Task"/>.</returns>
Task<PagedResultSet<UserHistoryViewModel>> CheckUserHasAnActiveSessionAsync(int userId);

/// <summary>
/// The store user history async.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion Auth/LearningHub.Nhs.Auth/LearningHub.Nhs.Auth.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.4.0" />
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Keys" Version="1.3.0" />
<PackageReference Include="Azure.Identity" Version="1.13.2" />
<PackageReference Include="elfhHub.Nhs.Models" Version="3.0.8" />
<PackageReference Include="elfhHub.Nhs.Models" Version="3.0.10" />
<PackageReference Include="FluentValidation" Version="11.11.0" />
<PackageReference Include="IdentityServer4" Version="4.1.2" />
<PackageReference Include="IdentityServer4.Contrib.RedisStore" Version="4.0.0" />
Expand Down
121 changes: 121 additions & 0 deletions Auth/LearningHub.Nhs.Auth/Models/MoodleUserResponseViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
namespace LearningHub.Nhs.Auth.Models
{
using System.Collections.Generic;

/// <summary>
/// MoodleUserResponseViewModel.
/// </summary>
public class MoodleUserResponseViewModel
{
/// <summary>
/// Gets or sets the list of users.
/// </summary>
public List<MoodleUser> Users { get; set; }

/// <summary>
/// Gets or sets the warnings.
/// </summary>
public List<object> Warnings { get; set; }

/// <summary>
/// MoodleUser.
/// </summary>
public class MoodleUser
{
/// <summary>
/// Gets or sets the user ID.
/// </summary>
public int Id { get; set; }

/// <summary>
/// Gets or sets the username.
/// </summary>
public string Username { get; set; }

/// <summary>
/// Gets or sets the first name.
/// </summary>
public string FirstName { get; set; }

/// <summary>
/// Gets or sets the last name.
/// </summary>
public string LastName { get; set; }

/// <summary>
/// Gets or sets the full name.
/// </summary>
public string FullName { get; set; }

/// <summary>
/// Gets or sets the email.
/// </summary>
public string Email { get; set; }

/// <summary>
/// Gets or sets the department.
/// </summary>
public string Department { get; set; }

/// <summary>
/// Gets or sets the first access timestamp.
/// </summary>
public long FirstAccess { get; set; }

/// <summary>
/// Gets or sets the last access timestamp.
/// </summary>
public long LastAccess { get; set; }

/// <summary>
/// Gets or sets the authentication method.
/// </summary>
public string Auth { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the user is suspended.
/// </summary>
public bool Suspended { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the user is confirmed.
/// </summary>
public bool Confirmed { get; set; }

/// <summary>
/// Gets or sets the language.
/// </summary>
public string Lang { get; set; }

/// <summary>
/// Gets or sets the theme.
/// </summary>
public string Theme { get; set; }

/// <summary>
/// Gets or sets the timezone.
/// </summary>
public string Timezone { get; set; }

/// <summary>
/// Gets or sets the mail format.
/// </summary>
public int MailFormat { get; set; }

/// <summary>
/// Gets or sets the forum tracking preference.
/// </summary>
public int TrackForums { get; set; }

/// <summary>
/// Gets or sets the small profile image URL.
/// </summary>
public string ProfileImageUrlSmall { get; set; }

/// <summary>
/// Gets or sets the profile image URL.
/// </summary>
public string ProfileImageUrl { get; set; }
}
}
}
4 changes: 4 additions & 0 deletions Auth/LearningHub.Nhs.Auth/NLog.config
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
</targets>

<rules>

<!-- Block all IdentityServer4 logs from being processed by later rules (like DB) -->
<logger name="IdentityServer4.*" final="true" />

<!--<logger name="*" minlevel="Trace" writeTo="logfile" />-->
<logger name="*" minlevel="Error" writeTo="database" />
</rules>
Expand Down
67 changes: 67 additions & 0 deletions Auth/LearningHub.Nhs.Auth/Services/MoodleApiService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
namespace LearningHub.Nhs.Auth.Services
{
using System;
using System.Net.Http;
using System.Threading.Tasks;
using LearningHub.Nhs.Auth.Interfaces;
using LearningHub.Nhs.Auth.Models;
using Newtonsoft.Json;

/// <summary>
/// MoodleApiService.
/// </summary>
public class MoodleApiService : IMoodleApiService
{
private readonly IMoodleHttpClient moodleHttpClient;

/// <summary>
/// Initializes a new instance of the <see cref="MoodleApiService"/> class.
/// </summary>
/// <param name="moodleHttpClient">moodleHttpClient.</param>
public MoodleApiService(IMoodleHttpClient moodleHttpClient)
{
this.moodleHttpClient = moodleHttpClient;
}

/// <summary>
/// GetMoodleUserIdByUsernameAsync.
/// </summary>
/// <param name="currentUserId">current User Id.</param>
/// <returns>UserId from Moodle.</returns>
public async Task<int> GetMoodleUserIdByUsernameAsync(int currentUserId)
{
int moodleUserId = 0;
string additionalParameters = $"&criteria[0][key]=username&criteria[0][value]={currentUserId}";
string defaultParameters = this.moodleHttpClient.GetDefaultParameters();

var client = await this.moodleHttpClient.GetClient();

string url = $"&wsfunction=core_user_get_users{additionalParameters}";

HttpResponseMessage response = await client.GetAsync("?" + defaultParameters + url);
if (response.IsSuccessStatusCode)
{
var result = response.Content.ReadAsStringAsync().Result;
var viewmodel = JsonConvert.DeserializeObject<MoodleUserResponseViewModel>(result);

if (viewmodel?.Users != null)
{
foreach (var user in viewmodel.Users)
{
if (user.Username == currentUserId.ToString())
{
moodleUserId = user.Id;
}
}
}
}
else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized ||
response.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
throw new Exception("AccessDenied");
}

return moodleUserId;
}
}
}
Loading
Loading