From 3c2341cbff73f5f8c3748ae60b406e8aa4d7e129 Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 20 Dec 2023 19:06:39 -0500 Subject: add scopes endpoint and re-add password protection for applications --- .../src/Endpoints/ApplicationEndpoint.cs | 64 +++++++++++++++++++--- .../src/Endpoints/ScopesEndpoint.cs | 61 +++++++++++++++++++++ .../src/OAuth2ClientAppsEntryPoint.cs | 1 + 3 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 Plugins/OAuth2ClientApplications/src/Endpoints/ScopesEndpoint.cs diff --git a/Plugins/OAuth2ClientApplications/src/Endpoints/ApplicationEndpoint.cs b/Plugins/OAuth2ClientApplications/src/Endpoints/ApplicationEndpoint.cs index a548ae0..4b770ca 100644 --- a/Plugins/OAuth2ClientApplications/src/Endpoints/ApplicationEndpoint.cs +++ b/Plugins/OAuth2ClientApplications/src/Endpoints/ApplicationEndpoint.cs @@ -43,10 +43,14 @@ using VNLib.Plugins.Extensions.Validation; using VNLib.Plugins.Extensions.Loading; using VNLib.Plugins.Extensions.Loading.Sql; using VNLib.Plugins.Extensions.Data.Extensions; +using VNLib.Plugins.Essentials.Users; +using VNLib.Plugins.Extensions.Loading.Users; using static VNLib.Plugins.Essentials.Statics; + namespace OAuth2ClientApplications.Endpoints { + [ConfigurationName("applications")] internal sealed class ApplicationEndpoint : ProtectedWebEndpoint { @@ -54,6 +58,7 @@ namespace OAuth2ClientApplications.Endpoints private readonly ApplicationStore Applications; private readonly int MaxAppsPerUser; private readonly string MaxAppOverloadMessage; + private readonly IUserManager Users; private static readonly UserAppValidator Validator = new(); @@ -68,6 +73,8 @@ namespace OAuth2ClientApplications.Endpoints //Load apps Applications = new(plugin.GetContextOptions(), plugin.GetOrCreateSingleton()); + Users = plugin.GetOrCreateSingleton(); + //Complie overload message MaxAppOverloadMessage = $"You have reached the limit of {MaxAppsPerUser} applications, this application cannot be created"; } @@ -116,6 +123,7 @@ namespace OAuth2ClientApplications.Endpoints webm.Result = "OAuth is only available for internal user accounts"; return VirtualClose(entity, webm, HttpStatusCode.Forbidden); } + if (entity.QueryArgs.IsArgumentSet("action", "create")) { return await CreateAppAsync(entity); @@ -134,11 +142,17 @@ namespace OAuth2ClientApplications.Endpoints //Update message will include a challenge and an app id string? appId = update.RootElement.GetPropString("Id"); - if (string.IsNullOrWhiteSpace(appId)) + if (webm.Assert(!string.IsNullOrWhiteSpace(appId), "Application with the specified id does not exist")) { - return VfReturnType.NotFound; + return VirtualClose(entity, webm, HttpStatusCode.NotFound); } - + + //validate the user's password + if (await ValidateUserPassword(entity, update, webm) == false) + { + return VirtualClose(entity, webm, HttpStatusCode.Unauthorized); + } + //Update the app's secret using PrivateString? secret = await Applications.UpdateSecretAsync(entity.Session.UserID, appId); @@ -173,13 +187,19 @@ namespace OAuth2ClientApplications.Endpoints } //Update message will include a challenge and an app id - string? appId = update.RootElement.GetPropString("Id"); - + string? appId = update.RootElement.GetPropString("Id"); + if (string.IsNullOrWhiteSpace(appId)) { return VfReturnType.NotFound; - } - + } + + //validate the password + if(await ValidateUserPassword(entity, update, webm) == false) + { + return VirtualClose(entity, webm, HttpStatusCode.Unauthorized); + } + //Try to delete the app if (await Applications.DeleteAsync(appId, entity.Session.UserID)) { @@ -307,6 +327,36 @@ namespace OAuth2ClientApplications.Endpoints return VfReturnType.VirtualSkip; } + private async Task ValidateUserPassword(HttpEntity entity, JsonDocument request, WebMessage webm) + { + //Get password from request and capture it as a private string + using PrivateString? rawPassword = PrivateString.ToPrivateString(request.RootElement.GetPropString("password"), true); + + if (webm.Assert(rawPassword != null, "Please enter your account password")) + { + //Must sent a 401 to indicate that the password is required + return false; + } + + //Get the current user from the store + using IUser? user = await Users.GetUserFromIDAsync(entity.Session.UserID, entity.EventCancellation); + + if (webm.Assert(user != null, "Please check your password")) + { + return false; + } + + //Validate the password against the user + bool isPasswordValid = await Users.ValidatePasswordAsync(user, rawPassword, PassValidateFlags.None, entity.EventCancellation) == UserPassValResult.Success; + + if (webm.Assert(isPasswordValid, "Please check your password")) + { + return false; + } + + return true; + } + private static string ParsePermissions(string permissions) { StringBuilder builder = new(); diff --git a/Plugins/OAuth2ClientApplications/src/Endpoints/ScopesEndpoint.cs b/Plugins/OAuth2ClientApplications/src/Endpoints/ScopesEndpoint.cs new file mode 100644 index 0000000..b89b591 --- /dev/null +++ b/Plugins/OAuth2ClientApplications/src/Endpoints/ScopesEndpoint.cs @@ -0,0 +1,61 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: OAuth2ClientApplications +* File: ScopesEndpoint.cs +* +* ScopesEndpoint.cs is part of OAuth2ClientApplications which is part of the larger +* VNLib collection of libraries and utilities. +* +* OAuth2ClientApplications is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* OAuth2ClientApplications is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System.Linq; + +using VNLib.Plugins; +using VNLib.Plugins.Essentials; +using VNLib.Plugins.Essentials.Endpoints; +using VNLib.Plugins.Extensions.Loading; + + +namespace OAuth2ClientApplications.Endpoints +{ + [ConfigurationName("scopes")] + internal sealed class ScopesEndpoint : UnprotectedWebEndpoint + { + + private readonly string[] _permissions; + + public ScopesEndpoint(PluginBase plugin, IConfigScope config) + { + //Get configuration variables from plugin + string? path = config["path"].GetString(); + + //Get scope permissions + _permissions = config["scopes"].EnumerateArray() + .Select(p => p.GetString()!) + .Where(p => p!= null) + .ToArray(); + + InitPathAndLog(path, plugin.Log); + } + + protected override VfReturnType Get(HttpEntity entity) + { + //Return the permissions/scopes array + return VirtualOkJson(entity, _permissions); + } + } +} \ No newline at end of file diff --git a/Plugins/OAuth2ClientApplications/src/OAuth2ClientAppsEntryPoint.cs b/Plugins/OAuth2ClientApplications/src/OAuth2ClientAppsEntryPoint.cs index 3390a77..6ad190e 100644 --- a/Plugins/OAuth2ClientApplications/src/OAuth2ClientAppsEntryPoint.cs +++ b/Plugins/OAuth2ClientApplications/src/OAuth2ClientAppsEntryPoint.cs @@ -40,6 +40,7 @@ namespace OAuth2ClientApplications { //Route the applications endpoint this.Route(); + this.Route(); Log.Information("Plugin Loaded"); } -- cgit