diff options
Diffstat (limited to 'Libs/VNLib.Plugins.Essentials.Oauth/src/Applications')
3 files changed, 120 insertions, 113 deletions
diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/ApplicationStore.cs b/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/ApplicationStore.cs index da70a17..17db978 100644 --- a/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/ApplicationStore.cs +++ b/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/ApplicationStore.cs @@ -27,7 +27,6 @@ using System.Data; using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore; @@ -37,6 +36,8 @@ using VNLib.Utils.Memory; using VNLib.Plugins.Extensions.Data; using VNLib.Plugins.Essentials.Accounts; using VNLib.Plugins.Essentials.Oauth.Tokens; +using VNLib.Plugins.Extensions.Data.Abstractions; +using VNLib.Plugins.Extensions.Data.Extensions; namespace VNLib.Plugins.Essentials.Oauth.Applications { @@ -52,6 +53,7 @@ namespace VNLib.Plugins.Essentials.Oauth.Applications private readonly DbContextOptions ConextOptions; private readonly ITokenManager TokenStore; + /// <summary> /// Initializes a new <see cref="ApplicationStore"/> data store /// uisng the specified EFCore <see cref="DbContextOptions"/> object. @@ -64,8 +66,27 @@ namespace VNLib.Plugins.Essentials.Oauth.Applications SecretHashing = secretHashing; TokenStore = new TokenStore(conextOptions); } - - + + + /// <summary> + /// Generates a client application secret using the <see cref="RandomHash"/> library + /// </summary> + /// <returns>The RNG secret</returns> + public static PrivateString GenerateSecret(int secretSize = SECRET_SIZE) => (PrivateString)RandomHash.GetRandomHex(secretSize).ToLower(null)!; + + /// <inheritdoc/> + public override IDbContextHandle GetNewContext() => new UserAppContext(ConextOptions); + + /// <inheritdoc/> + public override string GetNewRecordId() => RandomHash.GetRandomHex(CLIENT_ID_SIZE).ToLower(null); + + ///<inheritdoc/> + public override void OnRecordUpdate(UserApplication newRecord, UserApplication currentRecord) + { + currentRecord.AppDescription = newRecord.AppDescription; + currentRecord.AppName = newRecord.AppName; + } + /// <summary> /// Updates the secret of an application, and if successful returns the new raw secret data /// </summary> @@ -123,6 +144,7 @@ namespace VNLib.Plugins.Essentials.Oauth.Applications public async Task<UserApplication?> VerifyAppAsync(string clientId, PrivateString secret) { UserApplication? app; + //Open new db context await using (UserAppContext Database = new(ConextOptions)) { @@ -136,14 +158,17 @@ namespace VNLib.Plugins.Essentials.Oauth.Applications //commit the transaction await Database.CommitTransactionAsync(); } + //make sure app exists if (string.IsNullOrWhiteSpace(app?.UserId) || !app.ClientId!.Equals(clientId, StringComparison.Ordinal)) { //Not found or not valid return null; } + //Convert the secret hash to a private string so it will be cleaned up using PrivateString secretHash = (PrivateString)app.SecretHash!; + //Verify the secret against the hash if (SecretHashing.Verify(secretHash, secret)) { @@ -151,134 +176,114 @@ namespace VNLib.Plugins.Essentials.Oauth.Applications //App was successfully verified return app; } + //Not found or not valid return null; } - ///<inheritdoc/> - public override async Task<ERRNO> DeleteAsync(params string[] specifiers) - { - //get app id to remove tokens from - string appId = specifiers[0]; - //Delete app from store - ERRNO result = await base.DeleteAsync(specifiers); - if(result) - { - //Delete active tokens - await TokenStore.RevokeTokensForAppAsync(appId, CancellationToken.None); - } - return result; - } /// <summary> - /// Generates a client application secret using the <see cref="RandomHash"/> library - /// </summary> - /// <returns>The RNG secret</returns> - public static PrivateString GenerateSecret() => (PrivateString)RandomHash.GetRandomHex(SECRET_SIZE).ToLower()!; - /// <summary> /// Creates and initializes a new <see cref="UserApplication"/> with a random clientid and /// secret that must be disposed /// </summary> /// <param name="record">The new record to create</param> + /// <param name="cancellation"></param> /// <returns>The result of the operation</returns> - public override async Task<ERRNO> CreateAsync(UserApplication record, CancellationToken cancellation = default) + public async Task<ERRNO> CreateAppAsync(UserApplication record, CancellationToken cancellation = default) { record.RawSecret = GenerateSecret(); //Hash the secret using PrivateString secretHash = SecretHashing.Hash(record.RawSecret); - record.ClientId = GenerateClientID(); + record.ClientId = GetNewRecordId(); record.SecretHash = (string)secretHash; - //Wait for the rescord to be created before wiping the secret - return await base.CreateAsync(record, cancellation); + //Wait for the record to be created before wiping the secret + return await this.CreateAsync(record, cancellation); } + - /// <summary> - /// Generates a new client ID using the <see cref="RandomHash"/> library - /// </summary> - /// <returns>The new client ID</returns> - public static string GenerateClientID() => RandomHash.GetRandomHex(CLIENT_ID_SIZE).ToLower(); - ///<inheritdoc/> - public override string RecordIdBuilder => RandomHash.GetRandomHex(CLIENT_ID_SIZE).ToLower(); - ///<inheritdoc/> - public override TransactionalDbContext NewContext() => new UserAppContext(ConextOptions); - ///<inheritdoc/> - protected override IQueryable<UserApplication> AddOrUpdateQueryBuilder(TransactionalDbContext context, UserApplication record) - { - UserAppContext ctx = (context as UserAppContext)!; - //get a single record by the id for the specific user - return from userApp in ctx.OAuthApps - where userApp.UserId == record.UserId - && userApp.Id == record.Id - select userApp; - } - ///<inheritdoc/> - protected override void OnRecordUpdate(UserApplication newRecord, UserApplication currentRecord) - { - currentRecord.AppDescription = newRecord.AppDescription; - currentRecord.AppName = newRecord.AppName; - } - ///<inheritdoc/> - protected override IQueryable<UserApplication> GetCollectionQueryBuilder(TransactionalDbContext context, string specifier) - { - UserAppContext ctx = (context as UserAppContext)!; - //Get the user's applications based on their userid - return from userApp in ctx.OAuthApps - where userApp.UserId == specifier - orderby userApp.Created ascending - select new UserApplication - { - AppDescription = userApp.AppDescription, - Id = userApp.Id, - AppName = userApp.AppName, - ClientId = userApp.ClientId, - Created = userApp.Created, - Permissions = userApp.Permissions - }; - } - ///<inheritdoc/> - protected override IQueryable<UserApplication> GetCollectionQueryBuilder(TransactionalDbContext context, params string[] constraints) - { - return GetCollectionQueryBuilder(context, constraints[0]); - } - ///<inheritdoc/> - protected override IQueryable<UserApplication> GetSingleQueryBuilder(TransactionalDbContext context, params string[] constraints) - { - string appId = constraints[0]; - string userId = constraints[1]; - UserAppContext ctx = (context as UserAppContext)!; - //Query to get a new single application with limit results output - return from userApp in ctx.OAuthApps - where userApp.UserId == userId - && userApp.Id == appId - select new UserApplication - { - AppDescription = userApp.AppDescription, - Id = userApp.Id, - AppName = userApp.AppName, - ClientId = userApp.ClientId, - Created = userApp.Created, - Permissions = userApp.Permissions, - Version = userApp.Version - }; - } - ///<inheritdoc/> - protected override IQueryable<UserApplication> GetSingleQueryBuilder(TransactionalDbContext context, UserApplication record) - { - //Use the query for single record with the other record's userid and record id - return GetSingleQueryBuilder(context, record.Id, record.UserId); - } - ///<inheritdoc/> - protected override IQueryable<UserApplication> UpdateQueryBuilder(TransactionalDbContext context, UserApplication record) - { - UserAppContext ctx = (context as UserAppContext)!; - return from userApp in ctx.OAuthApps - where userApp.UserId == record.UserId && userApp.Id == record.Id - select userApp; - } + public override IDbQueryLookup<UserApplication> QueryTable { get; } = new ApplicationQueries(); - //DO NOT ALLOW PAGINATION YET - public override Task<int> GetPageAsync(ICollection<UserApplication> collection, int page, int limit, CancellationToken cancellation = default) + sealed class ApplicationQueries : IDbQueryLookup<UserApplication> { - throw new NotSupportedException(); + ///<inheritdoc/> + public IQueryable<UserApplication> GetCollectionQueryBuilder(IDbContextHandle context, params string[] constraints) + { + //When only a single contraint is specified, we are getting all applications for a user + if (constraints.Length == 1) + { + string userId = constraints[0]; + + UserAppContext ctx = (context as UserAppContext)!; + //Get the user's applications based on their userid + return from userApp in ctx.OAuthApps + where userApp.UserId == userId + orderby userApp.Created ascending + select new UserApplication + { + AppDescription = userApp.AppDescription, + Id = userApp.Id, + AppName = userApp.AppName, + ClientId = userApp.ClientId, + Created = userApp.Created, + Permissions = userApp.Permissions + }; + } + //When two constraints are specified, we are getting a single application + else + { + string appId = constraints[0]; + string userId = constraints[1]; + + UserAppContext ctx = (context as UserAppContext)!; + //Query to get a new single application with limit results output + return from userApp in ctx.OAuthApps + where userApp.UserId == userId + && userApp.Id == appId + select new UserApplication + { + AppDescription = userApp.AppDescription, + Id = userApp.Id, + AppName = userApp.AppName, + ClientId = userApp.ClientId, + Created = userApp.Created, + Permissions = userApp.Permissions, + Version = userApp.Version + }; + } + } + + ///<inheritdoc/> + public IQueryable<UserApplication> GetSingleQueryBuilder(IDbContextHandle context, params string[] constraints) + { + string appId = constraints[0]; + string userId = constraints[1]; + UserAppContext ctx = (context as UserAppContext)!; + //Query to get a new single application with limit results output + return from userApp in ctx.OAuthApps + where userApp.UserId == userId + && userApp.Id == appId + select new UserApplication + { + AppDescription = userApp.AppDescription, + Id = userApp.Id, + AppName = userApp.AppName, + ClientId = userApp.ClientId, + Created = userApp.Created, + Permissions = userApp.Permissions, + Version = userApp.Version + }; + } + + ///<inheritdoc/> + public IQueryable<UserApplication> AddOrUpdateQueryBuilder(IDbContextHandle context, UserApplication record) + { + UserAppContext ctx = (context as UserAppContext)!; + //get a single record by the id for the specific user + return from userApp in ctx.OAuthApps + where userApp.UserId == record.UserId + && userApp.Id == record.Id + select userApp; + } + } } }
\ No newline at end of file diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/UserAppContext.cs b/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/UserAppContext.cs index 26dfbe6..e4d98e6 100644 --- a/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/UserAppContext.cs +++ b/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/UserAppContext.cs @@ -32,7 +32,9 @@ namespace VNLib.Plugins.Essentials.Oauth.Applications public class UserAppContext : TransactionalDbContext { public DbSet<UserApplication> OAuthApps { get; set; } + public DbSet<ActiveToken> OAuthTokens { get; set; } + #nullable disable public UserAppContext(DbContextOptions options) : base(options) { diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/UserApplication.cs b/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/UserApplication.cs index 9c2f543..37f4e77 100644 --- a/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/UserApplication.cs +++ b/Libs/VNLib.Plugins.Essentials.Oauth/src/Applications/UserApplication.cs @@ -42,7 +42,7 @@ using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace VNLib.Plugins.Essentials.Oauth.Applications { /// <summary> - /// + /// Represents an OAuth2 application for a user /// </summary> [Index(nameof(ClientId), IsUnique = true)] public class UserApplication : DbModelBase, IUserEntity, IJsonOnDeserialized |