diff options
author | vman <public@vaughnnugent.com> | 2022-11-18 17:43:57 -0500 |
---|---|---|
committer | vman <public@vaughnnugent.com> | 2022-11-18 17:43:57 -0500 |
commit | ef98ef0329d6ee8cec7f040f6c472dc1ea68e8dd (patch) | |
tree | 9be4b437895534f1f63b3a281e9e92c2a4a10421 /Libs/VNLib.Plugins.Essentials.Oauth | |
parent | 8b09e20f6dbaf7644fc64833d7d8eeda4b576ad9 (diff) |
Add project files.
Diffstat (limited to 'Libs/VNLib.Plugins.Essentials.Oauth')
9 files changed, 840 insertions, 0 deletions
diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/Applications/Applications.cs b/Libs/VNLib.Plugins.Essentials.Oauth/Applications/Applications.cs new file mode 100644 index 0000000..850ccba --- /dev/null +++ b/Libs/VNLib.Plugins.Essentials.Oauth/Applications/Applications.cs @@ -0,0 +1,260 @@ +using System; +using System.Data; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; + +using Microsoft.EntityFrameworkCore; + +using VNLib.Hashing; +using VNLib.Utils; +using VNLib.Utils.Memory; +using VNLib.Plugins.Extensions.Data; +using VNLib.Plugins.Essentials.Accounts; +using VNLib.Plugins.Essentials.Oauth.Tokens; + +namespace VNLib.Plugins.Essentials.Oauth.Applications +{ + /// <summary> + /// A DbStore for <see cref="UserApplication"/>s for OAuth2 client applications + /// </summary> + public sealed partial class Applications : DbStore<UserApplication> + { + public const int SECRET_SIZE = 32; + public const int CLIENT_ID_SIZE = 16; + + private readonly PasswordHashing SecretHashing; + private readonly DbContextOptions ConextOptions; + private readonly ITokenManager TokenStore; + + /// <summary> + /// Initializes a new <see cref="Applications"/> data store + /// uisng the specified EFCore <see cref="DbContextOptions"/> object. + /// </summary> + /// <param name="conextOptions">EFCore context options for connecting to a remote data-store</param> + /// <param name="secretHashing">A <see cref="PasswordHashing"/> structure for hashing client secrets</param> + public Applications(DbContextOptions conextOptions, PasswordHashing secretHashing) + { + this.ConextOptions = conextOptions; + this.SecretHashing = secretHashing; + this.TokenStore = new TokenStore(conextOptions); + } + + + /// <summary> + /// Updates the secret of an application, and if successful returns the new raw secret data + /// </summary> + /// <param name="userId">The user-id of that owns the application</param> + /// <param name="appId">The id of the application to update</param> + /// <returns>A task that resolves to the raw secret that was used to generate the hash, or null if the operation failed</returns> + public async Task<PrivateString?> UpdateSecretAsync(string userId, string appId) + { + /* + * Delete open apps first, incase there are any issues, worse case + * the user's will have to re-authenticate. + * + * If we delete tokens after update, the user wont see the new + * secret and may lose access to the updated app, not a big deal + * but avoidable. + */ + await TokenStore.RevokeTokensForAppAsync(appId, CancellationToken.None); + //Generate the new secret + PrivateString secret = GenerateSecret(); + //Hash the secret + using PrivateString secretHash = SecretHashing.Hash(secret); + //Open new db context + await using UserAppContext Database = new(ConextOptions); + //Open transaction + await Database.OpenTransactionAsync(); + //Get the app to update the secret on + UserApplication? app = await (from ap in Database.OAuthApps + where ap.UserId == userId && ap.Id == appId + select ap) + .SingleOrDefaultAsync(); + if (app == null) + { + return null; + } + //Store the new secret hash + app.SecretHash = (string)secretHash; + //Save changes + if (await Database.SaveChangesAsync() <= 0) + { + return null; + } + //Commit transaction + await Database.CommitTransactionAsync(); + //return the raw secret + return secret; + } + + /// <summary> + /// Attempts to retreive an application by the specified client id and compares the raw secret against the + /// stored secret hash. + /// </summary> + /// <param name="clientId">The clientid of the application to search</param> + /// <param name="secret">The secret to compare against</param> + /// <returns>True if the application was found and the secret matches the stored secret, false if the appliation was not found or the secret does not match</returns> + public async Task<UserApplication?> VerifyAppAsync(string clientId, PrivateString secret) + { + UserApplication? app; + //Open new db context + await using (UserAppContext Database = new(ConextOptions)) + { + //Open transaction + await Database.OpenTransactionAsync(); + //Get the application with its secret + app = await (from userApp in Database.OAuthApps + where userApp.ClientId == clientId + select userApp) + .FirstOrDefaultAsync(); + //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)) + { + app.SecretHash = null; + //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> + /// <returns>The result of the operation</returns> + public override async Task<ERRNO> CreateAsync(UserApplication record) + { + record.RawSecret = GenerateSecret(); + //Hash the secret + using PrivateString secretHash = SecretHashing.Hash(record.RawSecret); + record.ClientId = GenerateClientID(); + record.SecretHash = (string)secretHash; + //Wait for the rescord to be created before wiping the secret + return await base.CreateAsync(record); + } + + /// <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 userId) + { + 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 + }; + } + ///<inheritdoc/> + protected override IQueryable<UserApplication> GetCollectionQueryBuilder(TransactionalDbContext context, params string[] args) + { + return GetCollectionQueryBuilder(context, args[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; + } + + //DO NOT ALLOW PAGINATION YET + public override Task<int> GetPageAsync(ICollection<UserApplication> collection, int page, int limit) + { + throw new NotSupportedException(); + } + } +}
\ No newline at end of file diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/Applications/UserAppContext.cs b/Libs/VNLib.Plugins.Essentials.Oauth/Applications/UserAppContext.cs new file mode 100644 index 0000000..5a24b49 --- /dev/null +++ b/Libs/VNLib.Plugins.Essentials.Oauth/Applications/UserAppContext.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; + + +using VNLib.Plugins.Extensions.Data; +using VNLib.Plugins.Essentials.Oauth.Tokens; + +namespace VNLib.Plugins.Essentials.Oauth.Applications +{ + internal class UserAppContext : TransactionalDbContext + { + public DbSet<UserApplication> OAuthApps { get; set; } + public DbSet<ActiveToken> OAuthTokens { get; set; } +#nullable disable + public UserAppContext(DbContextOptions options) : base(options) + { + + } + } +}
\ No newline at end of file diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/Applications/UserApplication.cs b/Libs/VNLib.Plugins.Essentials.Oauth/Applications/UserApplication.cs new file mode 100644 index 0000000..1fa7671 --- /dev/null +++ b/Libs/VNLib.Plugins.Essentials.Oauth/Applications/UserApplication.cs @@ -0,0 +1,133 @@ +using System; +using System.Text.Json; +using System.ComponentModel; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; +using VNLib.Hashing.IdentityUtility; +using VNLib.Plugins.Extensions.Data; +using VNLib.Plugins.Extensions.Data.Abstractions; + +using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; + + +namespace VNLib.Plugins.Essentials.Oauth.Applications +{ + /// <summary> + /// + /// </summary> + [Index(nameof(ClientId), IsUnique = true)] + public class UserApplication : DbModelBase, IUserEntity, IJsonOnDeserialized + { + ///<inheritdoc/> + [Key, Required] + public override string? Id { get; set; } + ///<inheritdoc/> + public override DateTime Created { get; set; } + ///<inheritdoc/> + public override DateTime LastModified { get; set; } + + ///<inheritdoc/> + [Required] + [JsonIgnore] + public string? UserId { get; set; } + + /// <summary> + /// The OAuth2 application's associated client-id + /// </summary> + [Required] + [JsonPropertyName("client_id")] + public string? ClientId { get; set; } + + /// <summary> + /// The hash of the application's secret (no json-serializable) + /// </summary> + [JsonIgnore] + [MaxLength(1000)] + public string? SecretHash { get; set; } + + /// <summary> + /// The user-defined name of the application + /// </summary> + [DisplayName("Application Name")] + [JsonPropertyName("name")] + public string? AppName { get; set; } + + /// <summary> + /// The user-defined description for the application + /// </summary> + [JsonPropertyName("description")] + [DisplayName("Application Description")] + public string? AppDescription { get; set; } + + /// <summary> + /// The permissions for the application + /// </summary> + [JsonPropertyName("permissions")] + [Column("permissions")] + public string? Permissions { get; set; } + + + [NotMapped] + [JsonIgnore] + public PrivateString? RawSecret { get; set; } + + void IJsonOnDeserialized.OnDeserialized() + { + Id = Id?.Trim(); + ClientId = ClientId?.Trim(); + UserId = UserId?.Trim(); + AppName = AppName?.Trim(); + AppDescription = AppDescription?.Trim(); + Permissions = Permissions?.Trim(); + } + + + /// <summary> + /// Creates a new <see cref="UserApplication"/> instance + /// from the supplied <see cref="JsonElement"/> assuming + /// JWT format + /// </summary> + /// <param name="appEl">The application JWT payalod element</param> + /// <returns>The recovered application</returns> + public static UserApplication FromJwtDoc(in JsonElement appEl) + { + return new() + { + UserId = appEl.GetPropString("sub"), + ClientId = appEl.GetPropString("azp"), + Id = appEl.GetPropString("appid"), + Permissions = appEl.GetPropString("scope"), + }; + } + /// <summary> + /// Stores the + /// </summary> + /// <param name="app">The application to serialze to JWT format</param> + /// <param name="dict">Jwt dictionary payload</param> + public static void ToJwtDict(UserApplication app, IDictionary<string, string?> dict) + { + dict["appid"] = app.Id; + dict["azp"] = app.ClientId; + dict["sub"] = app.UserId; + dict["scope"] = app.Permissions; + } + + /// <summary> + /// Stores the + /// </summary> + /// <param name="app"></param> + /// <param name="payload">JW payload parameter</param> + public static void ToJwtPayload(UserApplication app, in JwtPayload payload) + { + payload["appid"] = app.Id; + payload["azp"] = app.ClientId; + payload["sub"] = app.UserId; + payload["scope"] = app.Permissions; + } + } +}
\ No newline at end of file diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/ActiveToken.cs b/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/ActiveToken.cs new file mode 100644 index 0000000..29760c2 --- /dev/null +++ b/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/ActiveToken.cs @@ -0,0 +1,16 @@ +using System; + +using VNLib.Plugins.Extensions.Data; + +namespace VNLib.Plugins.Essentials.Oauth.Tokens +{ + public class ActiveToken : DbModelBase + { + public override string Id { get; set; } + public override DateTime Created { get; set; } + public override DateTime LastModified { get; set; } + + public string? ApplicationId { get; set; } + public string? RefreshToken { get; set; } + } +} diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/IOAuth2TokenResult.cs b/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/IOAuth2TokenResult.cs new file mode 100644 index 0000000..a96810f --- /dev/null +++ b/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/IOAuth2TokenResult.cs @@ -0,0 +1,14 @@ +namespace VNLib.Plugins.Essentials.Oauth.Tokens +{ + /// <summary> + /// The result of an OAuth2Token creation + /// </summary> + public interface IOAuth2TokenResult + { + string? IdentityToken { get; } + string? AccessToken { get; } + string? RefreshToken { get; } + string? TokenType { get; } + int ExpiresSeconds { get; } + } +}
\ No newline at end of file diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/ITokenManager.cs b/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/ITokenManager.cs new file mode 100644 index 0000000..046d512 --- /dev/null +++ b/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/ITokenManager.cs @@ -0,0 +1,27 @@ +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace VNLib.Plugins.Essentials.Oauth.Tokens +{ + /// <summary> + /// Provides token creation and revocation + /// </summary> + public interface ITokenManager + { + /// <summary> + /// Revokes a colleciton of toke + /// </summary> + /// <param name="tokens">A collection of tokens to revoke</param> + /// <param name="cancellation">A token to cancel the operation</param> + /// <returns>A task that completes when the tokens have been revoked</returns> + Task RevokeTokensAsync(IReadOnlyCollection<string> tokens, CancellationToken cancellation = default); + /// <summary> + /// Attempts to revoke tokens that belong to a specified application + /// </summary> + /// <param name="appId">The application to revoke tokens for</param> + /// <param name="cancellation">A token to cancel the operation</param> + /// <returns>A task that completes when the work is complete</returns> + Task RevokeTokensForAppAsync(string appId, CancellationToken cancellation = default); + } +}
\ No newline at end of file diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/TokenStore.cs b/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/TokenStore.cs new file mode 100644 index 0000000..56bb9f7 --- /dev/null +++ b/Libs/VNLib.Plugins.Essentials.Oauth/Tokens/TokenStore.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.EntityFrameworkCore; + +using VNLib.Utils; +using VNLib.Plugins.Essentials.Oauth.Applications; + +namespace VNLib.Plugins.Essentials.Oauth.Tokens +{ + /// <summary> + /// Represents a database backed <see cref="ITokenManager"/> + /// that allows for communicating token information to + /// plugins + /// </summary> + public sealed class TokenStore : ITokenManager + { + private readonly DbContextOptions Options; + + /// <summary> + /// Initializes a new <see cref="TokenStore"/> that will make quries against + /// the supplied <see cref="DbContextOptions"/> + /// </summary> + /// <param name="options">The DB connection context</param> + public TokenStore(DbContextOptions options) => Options = options; + + /// <summary> + /// Inserts a new token into the table for a specified application id. Also determines if + /// the user has reached the maximum number of allowed tokens + /// </summary> + /// <param name="token">The token (or session id)</param> + /// <param name="appId">The applicaiton the token belongs to</param> + /// <param name="refreshToken">The tokens refresh token</param> + /// <param name="maxTokens">The maxium number of allowed tokens for a given application</param> + /// <param name="cancellation">A token to cancel the operation</param> + /// <returns> + /// <see cref="ERRNO.SUCCESS"/> if the opreation succeeds (aka 0x01), + /// <see cref="ERRNO.E_FAIL"/> if the operation fails, or the number + /// of active tokens if the maximum has been reached. + /// </returns> + public async Task<ERRNO> InsertTokenAsync(string token, string appId, string? refreshToken, int maxTokens, CancellationToken cancellation) + { + await using UserAppContext ctx = new (Options); + await ctx.OpenTransactionAsync(cancellation); + + //Check active token count + int count = await (from t in ctx.OAuthTokens + where t.ApplicationId == appId + select t) + .CountAsync(cancellation); + //Check count + if(count >= maxTokens) + { + return count; + } + + //Try to add the new token + ActiveToken newToken = new() + { + ApplicationId = appId, + Id = token, + RefreshToken = refreshToken, + Created = DateTime.UtcNow, + LastModified = DateTime.UtcNow + }; + //Add token to store + ctx.OAuthTokens.Add(newToken); + //commit changes + ERRNO result = await ctx.SaveChangesAsync(cancellation); + if (result) + { + //End transaction + await ctx.CommitTransactionAsync(cancellation); + } + else + { + await ctx.RollbackTransctionAsync(cancellation); + } + return result; + } + + /// <summary> + /// Revokes/removes a single token from the store by its ID + /// </summary> + /// <param name="token">The token to remove</param> + /// <param name="cancellation"></param> + /// <returns>A task that revolves when the token is removed from the table if it exists</returns> + public async Task RevokeTokenAsync(string token, CancellationToken cancellation) + { + await using UserAppContext ctx = new (Options); + await ctx.OpenTransactionAsync(cancellation); + //Get the token from the db if it exists + ActiveToken? at = await (from t in ctx.OAuthTokens + where t.Id == token + select t) + .FirstOrDefaultAsync(cancellation); + if(at == null) + { + return; + } + //delete token + ctx.OAuthTokens.Remove(at); + //Save changes + await ctx.SaveChangesAsync(cancellation); + await ctx.CommitTransactionAsync(cancellation); + } + /// <summary> + /// Removes all token entires that were created before the specified time + /// </summary> + /// <param name="validAfter">The time before which all tokens are invaid</param> + /// <param name="cancellation">A token the cancel the operation</param> + /// <returns>A task that resolves to a collection of tokens that were removed</returns> + public async Task<IReadOnlyCollection<ActiveToken>> CleanupExpiredTokensAsync(DateTimeOffset validAfter, CancellationToken cancellation) + { + await using UserAppContext ctx = new (Options); + await ctx.OpenTransactionAsync(cancellation); + //Get the token from the db if it exists + ActiveToken[] at = await (from t in ctx.OAuthTokens + where t.Created < validAfter + select t) + .ToArrayAsync(cancellation); + + //delete token + ctx.OAuthTokens.RemoveRange(at); + //Save changes + int count = await ctx.SaveChangesAsync(cancellation); + await ctx.CommitTransactionAsync(cancellation); + return at; + } + ///<inheritdoc/> + public async Task RevokeTokensAsync(IReadOnlyCollection<string> tokens, CancellationToken cancellation = default) + { + await using UserAppContext ctx = new (Options); + await ctx.OpenTransactionAsync(cancellation); + //Get all tokenes that are contained in the collection + ActiveToken[] at = await (from t in ctx.OAuthTokens + where tokens.Contains(t.Id) + select t) + .ToArrayAsync(cancellation); + + //delete token + ctx.OAuthTokens.RemoveRange(at); + //Save changes + await ctx.SaveChangesAsync(cancellation); + await ctx.CommitTransactionAsync(cancellation); + } + ///<inheritdoc/> + async Task ITokenManager.RevokeTokensForAppAsync(string appId, CancellationToken cancellation) + { + await using UserAppContext ctx = new (Options); + await ctx.OpenTransactionAsync(cancellation); + //Get the token from the db if it exists + ActiveToken[] at = await (from t in ctx.OAuthTokens + where t.ApplicationId == appId + select t) + .ToArrayAsync(cancellation); + //Set created time to 0 to invalidate the token + foreach(ActiveToken t in at) + { + //Expire token so next cleanup round will wipe tokens + t.Created = DateTime.MinValue; + } + //Save changes + await ctx.SaveChangesAsync(cancellation); + await ctx.CommitTransactionAsync(cancellation); + } + } +} diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/VNLib.Plugins.Essentials.Oauth.csproj b/Libs/VNLib.Plugins.Essentials.Oauth/VNLib.Plugins.Essentials.Oauth.csproj new file mode 100644 index 0000000..49c3c84 --- /dev/null +++ b/Libs/VNLib.Plugins.Essentials.Oauth/VNLib.Plugins.Essentials.Oauth.csproj @@ -0,0 +1,45 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <Platforms>AnyCPU;x64</Platforms> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.11" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\..\..\VNLib\Essentials\VNLib.Plugins.Essentials.csproj" /> + <ProjectReference Include="..\..\..\..\VNLib\Hashing\VNLib.Hashing.Portable.csproj" /> + <ProjectReference Include="..\..\..\..\VNLib\Utils\src\VNLib.Utils.csproj" /> + <ProjectReference Include="..\..\..\Extensions\VNLib.Plugins.Extensions.Data\VNLib.Plugins.Extensions.Data.csproj" /> + </ItemGroup> + + <PropertyGroup> + <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> + <Authors>Vaughn Nugent</Authors> + <Copyright>Copyright © 2022 Vaughn Nugent</Copyright> + <PackageProjectUrl>www.vaughnnugent.com/resources</PackageProjectUrl> + <Version>1.0.2.1</Version> + <Nullable>enable</Nullable> + <GenerateDocumentationFile>True</GenerateDocumentationFile> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <DocumentationFile></DocumentationFile> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <DocumentationFile></DocumentationFile> + </PropertyGroup> + +</Project> diff --git a/Libs/VNLib.Plugins.Essentials.Oauth/VNLib.Plugins.Essentials.Oauth.xml b/Libs/VNLib.Plugins.Essentials.Oauth/VNLib.Plugins.Essentials.Oauth.xml new file mode 100644 index 0000000..ad8e968 --- /dev/null +++ b/Libs/VNLib.Plugins.Essentials.Oauth/VNLib.Plugins.Essentials.Oauth.xml @@ -0,0 +1,155 @@ +<?xml version="1.0"?> +<doc> + <assembly> + <name>VNLib.Plugins.Essentials.Oauth</name> + </assembly> + <members> + <member name="T:VNLib.Plugins.Essentials.Oauth.Applications"> + <summary> + A DbStore for <see cref="T:VNLib.Plugins.Essentials.Oauth.UserApplication"/>s for OAuth2 client applications + </summary> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.#ctor(Microsoft.EntityFrameworkCore.DbContextOptions,VNLib.Plugins.Essentials.Accounts.PasswordHashing,System.String,System.String)"> + <summary> + Initializes a new <see cref="T:VNLib.Plugins.Essentials.Oauth.Applications"/> data store + uisng the specified EFCore <see cref="T:Microsoft.EntityFrameworkCore.DbContextOptions"/> object. + </summary> + <param name="ConextOptions">EFCore context options for connecting to a remote data-store</param> + <param name="secretHashing">A <see cref="T:VNLib.Plugins.Essentials.Accounts.PasswordHashing"/> structure for hashing client secrets</param> + <param name="tableName">The name of the applications table</param> + <param name="tokenTableName">The name of the active OAuth2 token table</param> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.UpdateSecretAsync(System.String,System.String)"> + <summary> + Updates the secret of an application, and if successful returns the new raw secret data + </summary> + <param name="userId">The user-id of that owns the application</param> + <param name="appId">The id of the application to update</param> + <returns>A task that resolves to the raw secret that was used to generate the hash, or null if the operation failed</returns> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.VerifyAppAsync(System.String,VNLib.Utils.Memory.PrivateString)"> + <summary> + Attempts to retreive an application by the specified client id and compares the raw secret against the + stored secret hash. + </summary> + <param name="clientId">The clientid of the application to search</param> + <param name="secret">The secret to compare against</param> + <param name="app">The found application</param> + <returns>True if the application was found and the secret matches the stored secret, false if the appliation was not found or the secret does not match</returns> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.DeleteActiveTokensAsync(System.String)"> + <summary> + Deletes all active tokens + </summary> + <param name="appid"></param> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.GenerateSecret"> + <summary> + Generates a client application secret using the <see cref="T:VNLib.Hashing.RandomHash"/> library + </summary> + <returns>The RNG secret</returns> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.CreateAsync(VNLib.Plugins.Essentials.Oauth.UserApplication)"> + <summary> + Creates and initializes a new <see cref="T:VNLib.Plugins.Essentials.Oauth.UserApplication"/> with a random clientid and + secret that must be disposed + </summary> + <param name="record">The new record to create</param> + <returns>The result of the operation</returns> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.GenerateClientID"> + <summary> + Generates a new client ID using the <see cref="T:VNLib.Hashing.RandomHash"/> library + </summary> + <returns>The new client ID</returns> + </member> + <member name="P:VNLib.Plugins.Essentials.Oauth.Applications.RecordIdBuilder"> + <inheritdoc/> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.NewContext"> + <inheritdoc/> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.AddOrUpdateQueryBuilder(VNLib.Plugins.Extensions.Data.TransactionalDbContext,VNLib.Plugins.Essentials.Oauth.UserApplication)"> + <inheritdoc/> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.OnRecordUpdate(VNLib.Plugins.Essentials.Oauth.UserApplication,VNLib.Plugins.Essentials.Oauth.UserApplication)"> + <inheritdoc/> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.GetCollectionQueryBuilder(VNLib.Plugins.Extensions.Data.TransactionalDbContext,System.String)"> + <inheritdoc/> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.GetCollectionQueryBuilder(VNLib.Plugins.Extensions.Data.TransactionalDbContext,System.String[])"> + <inheritdoc/> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.GetSingleQueryBuilder(VNLib.Plugins.Extensions.Data.TransactionalDbContext,System.String[])"> + <inheritdoc/> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.GetSingleQueryBuilder(VNLib.Plugins.Extensions.Data.TransactionalDbContext,VNLib.Plugins.Essentials.Oauth.UserApplication)"> + <inheritdoc/> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Applications.UpdateQueryBuilder(VNLib.Plugins.Extensions.Data.TransactionalDbContext,VNLib.Plugins.Essentials.Oauth.UserApplication)"> + <inheritdoc/> + </member> + <member name="T:VNLib.Plugins.Essentials.Oauth.Sessions.O2SessionHandle"> + <summary> + Provides a one-time-use handle (similar to asyncReleaser, or openHandle) + that holds exclusive access to a session until it is released + </summary> + </member> + <member name="T:VNLib.Plugins.Essentials.Oauth.Sessions.OAuthSessionStore"> + <summary> + Provides OAuth2 sessions (with caching) using VNLib caching store + </summary> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Sessions.OAuthSessionStore.#ctor(System.Func{System.Data.Common.DbConnection},System.String,System.Int32,System.Func{System.String})"> + <summary> + Initializes a new <see cref="T:VNLib.Plugins.Essentials.Oauth.Sessions.OAuthSessionStore"/> + </summary> + <param name="connectionFactory">The a <see cref="T:System.Data.Common.DbConnection"/> factory function</param> + <param name="tableName">The name of the table that the backing store indexes</param> + <param name="maxCacheItems">The maximum number of sessions to keep in memory</param> + <param name="accessTokenFactory">A secure access token factory function</param> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Sessions.OAuthSessionStore.GetActiveTokenCountAsync(System.String)"> + <summary> + Asynchronously gets the number of active tokens for a given application + </summary> + <param name="appId">The application id to get the count of</param> + <returns>A task that resolves the number of active tokens</returns> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Sessions.OAuthSessionStore.GetSessionAsync(System.String,System.Threading.CancellationToken)"> + <summary> + Gets a session handle for the current token to attach to a connection + </summary> + <param name="sessionId">The access token (or session id) to get the session of</param> + <param name="cancellationToken">A token to cancel the operation</param> + <returns>A task the resolves a <see cref="T:VNLib.Plugins.Essentials.Sessions.ISessionHandle"/> around the session to connect to the token and entity</returns> + <exception cref="T:VNLib.Plugins.Essentials.Sessions.SessionException"></exception> + <exception cref="T:System.OperationCanceledException"></exception> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.Sessions.OAuthSessionStore.CreateSessionAsync(System.String,System.Net.IPAddress,System.Threading.CancellationToken)"> + <summary> + Creates a new OAuth2 session in the store and returns a handle to the new session + </summary> + <param name="appId">The application id</param> + <param name="userIp">The IP address of the client that created the token</param> + <param name="cancellationToken">A token to cancel the operation</param> + <returns>A task that compeltes with an <see cref="T:VNLib.Plugins.Essentials.Sessions.ISessionHandle"/> used to release the session</returns> + <exception cref="T:System.ObjectDisposedException"></exception> + </member> + <member name="P:VNLib.Plugins.Essentials.Oauth.OauthSession.Released"> + <summary> + A value that indicates if the session has been released, returns false if the instance has not been initialized + </summary> + </member> + <member name="M:VNLib.Plugins.Essentials.Oauth.OauthSession.WaitAndLoadAsync(System.Net.IPAddress,System.Threading.CancellationToken)"> + <summary> + Waits for exclusive access to the resource and lazily initializes the resource + from its backing store + </summary> + <param name="userIp">Optional ipaddressfor initalization</param> + <param name="cancellationToken">A token to cancel the operation</param> + <returns>A task the resolves to a value that indicates if the session is in a usable state</returns> + </member> + </members> +</doc> |