From cf6b5c5138c519d05f6b0b827d080584abe67a28 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Tue, 11 Apr 2017 22:22:46 +0100 Subject: [PATCH] #865 more for the authentication --- .../IdentityResolver/IUserIdentityManager.cs | 14 +++ .../IdentityResolver/UserIdentityManager.cs | 90 +++++++++++++++++++ Ombi/Ombi.Core/Ombi.Core.csproj | 1 + .../Ombi.DependencyInjection/IocExtensions.cs | 3 + Ombi/Ombi.Store/Context/IOmbiContext.cs | 1 + Ombi/Ombi.Store/Context/OmbiContext.cs | 1 + Ombi/Ombi.Store/Entities/User.cs | 48 ++++++++++ Ombi/Ombi.Store/Repository/IUserRepository.cs | 13 +++ Ombi/Ombi.Store/Repository/UserRepository.cs | 62 +++++++++++++ Ombi/Ombi/Auth/TokenProviderMiddleware.cs | 6 +- Ombi/Ombi/Auth/TokenProviderOptions.cs | 3 +- Ombi/Ombi/Startup.Auth.cs | 31 ++----- 12 files changed, 248 insertions(+), 25 deletions(-) create mode 100644 Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs create mode 100644 Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs create mode 100644 Ombi/Ombi.Store/Entities/User.cs create mode 100644 Ombi/Ombi.Store/Repository/IUserRepository.cs create mode 100644 Ombi/Ombi.Store/Repository/UserRepository.cs diff --git a/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs b/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs new file mode 100644 index 000000000..78facfc3e --- /dev/null +++ b/Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Store.Entities; + +namespace Ombi.Core.IdentityResolver +{ + public interface IUserIdentityManager + { + Task CreateUser(User user); + Task CredentialsValid(string username, string password); + Task GetUser(string username); + Task> GetUsers(); + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs b/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs new file mode 100644 index 000000000..4f26b7027 --- /dev/null +++ b/Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs @@ -0,0 +1,90 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: UserIdentityManager.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.IdentityResolver +{ + public class UserIdentityManager : IUserIdentityManager + { + public UserIdentityManager(IUserRepository userRepository) + { + UserRepository = userRepository; + } + + private IUserRepository UserRepository { get; } + + public async Task CredentialsValid(string username, string password) + { + var user = await UserRepository.GetUser(username); + var hashedPass = HashPassword(password); + + return hashedPass.Equals(user.Password); + } + + public async Task GetUser(string username) + { + return await UserRepository.GetUser(username); + } + + public async Task> GetUsers() + { + return await UserRepository.GetUsers(); + } + + public async Task CreateUser(User user) + { + user.Password = HashPassword(user.Password); + await UserRepository.CreateUser(user); + } + + private string HashPassword(string password) + { + // generate a 128-bit salt using a secure PRNG + byte[] salt = new byte[128 / 8]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(salt); + } + // derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations) + var hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2( + password: password, + salt: salt, + prf: KeyDerivationPrf.HMACSHA1, + iterationCount: 10000, + numBytesRequested: 256 / 8)); + + return hashed; + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Core/Ombi.Core.csproj b/Ombi/Ombi.Core/Ombi.Core.csproj index 078f56b95..0312ace74 100644 --- a/Ombi/Ombi.Core/Ombi.Core.csproj +++ b/Ombi/Ombi.Core/Ombi.Core.csproj @@ -6,6 +6,7 @@ + diff --git a/Ombi/Ombi.DependencyInjection/IocExtensions.cs b/Ombi/Ombi.DependencyInjection/IocExtensions.cs index 639d26f0e..596b810f3 100644 --- a/Ombi/Ombi.DependencyInjection/IocExtensions.cs +++ b/Ombi/Ombi.DependencyInjection/IocExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Ombi.Core; using Ombi.Core.Engine; +using Ombi.Core.IdentityResolver; using Ombi.Core.Models.Requests; using Ombi.Core.Requests.Models; using Ombi.Core.Settings; @@ -48,6 +49,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>)); return services; } @@ -59,6 +61,7 @@ namespace Ombi.DependencyInjection public static IServiceCollection RegisterIdentity(this IServiceCollection services) { + services.AddTransient(); services.AddAuthorization(auth => { auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder() diff --git a/Ombi/Ombi.Store/Context/IOmbiContext.cs b/Ombi/Ombi.Store/Context/IOmbiContext.cs index 1d555253d..c176a3f99 100644 --- a/Ombi/Ombi.Store/Context/IOmbiContext.cs +++ b/Ombi/Ombi.Store/Context/IOmbiContext.cs @@ -12,5 +12,6 @@ namespace Ombi.Store.Context Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); DbSet Requests { get; set; } DbSet Settings { get; set; } + DbSet Users { get; set; } } } \ No newline at end of file diff --git a/Ombi/Ombi.Store/Context/OmbiContext.cs b/Ombi/Ombi.Store/Context/OmbiContext.cs index 9a97ac69f..57daca0cd 100644 --- a/Ombi/Ombi.Store/Context/OmbiContext.cs +++ b/Ombi/Ombi.Store/Context/OmbiContext.cs @@ -16,6 +16,7 @@ namespace Ombi.Store.Context } public DbSet Requests { get; set; } public DbSet Settings { get; set; } + public DbSet Users { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { diff --git a/Ombi/Ombi.Store/Entities/User.cs b/Ombi/Ombi.Store/Entities/User.cs new file mode 100644 index 000000000..534d062a2 --- /dev/null +++ b/Ombi/Ombi.Store/Entities/User.cs @@ -0,0 +1,48 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: Users.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System.Security.Claims; + +namespace Ombi.Store.Entities +{ + public class User : Entity + { + public string Username { get; set; } + public string Alias { get; set; } + public Claim[] Claims { get; set; } + public string EmailAddress { get; set; } + public string Password { get; set; } + public UserType UserType { get; set; } + } + + public enum UserType + { + LocalUser = 1, + PlexUser = 2, + EmbyUser = 3, + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Store/Repository/IUserRepository.cs b/Ombi/Ombi.Store/Repository/IUserRepository.cs new file mode 100644 index 000000000..468716486 --- /dev/null +++ b/Ombi/Ombi.Store/Repository/IUserRepository.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public interface IUserRepository + { + Task CreateUser(User user); + Task GetUser(string username); + Task> GetUsers(); + } +} \ No newline at end of file diff --git a/Ombi/Ombi.Store/Repository/UserRepository.cs b/Ombi/Ombi.Store/Repository/UserRepository.cs new file mode 100644 index 000000000..ddd73d272 --- /dev/null +++ b/Ombi/Ombi.Store/Repository/UserRepository.cs @@ -0,0 +1,62 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: UserRepository.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Ombi.Store.Context; +using Ombi.Store.Entities; + +namespace Ombi.Store.Repository +{ + public class UserRepository : IUserRepository + { + public UserRepository(IOmbiContext ctx) + { + Db = ctx; + } + + private IOmbiContext Db { get; } + + public async Task GetUser(string username) + { + return await Db.Users.FirstOrDefaultAsync(x => x.Username.ToLower() == username.ToLower()); + } + + public async Task CreateUser(User user) + { + Db.Users.Add(user); + await Db.SaveChangesAsync(); + } + + public async Task> GetUsers() + { + return await Db.Users.ToListAsync(); + } + } +} \ No newline at end of file diff --git a/Ombi/Ombi/Auth/TokenProviderMiddleware.cs b/Ombi/Ombi/Auth/TokenProviderMiddleware.cs index 069cc7b56..cf21abd08 100644 --- a/Ombi/Ombi/Auth/TokenProviderMiddleware.cs +++ b/Ombi/Ombi/Auth/TokenProviderMiddleware.cs @@ -8,7 +8,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Newtonsoft.Json; +using Ombi.Core.IdentityResolver; using Ombi.Models; +using Ombi.Store.Context; +using Ombi.Store.Repository; namespace Ombi.Auth { @@ -23,7 +26,6 @@ namespace Ombi.Auth IOptions options) { _next = next; - _options = options.Value; ThrowIfInvalidOptions(_options); @@ -64,7 +66,7 @@ namespace Ombi.Auth userInfo = JsonConvert.DeserializeObject(body); } - var identity = await _options.IdentityResolver(userInfo.Username, userInfo.Password); + var identity = await _options.IdentityResolver(userInfo.Username, userInfo.Password, new UserIdentityManager(new UserRepository(new OmbiContext()))); if (identity == null) { context.Response.StatusCode = 400; diff --git a/Ombi/Ombi/Auth/TokenProviderOptions.cs b/Ombi/Ombi/Auth/TokenProviderOptions.cs index 3cdb356c1..bb8f7c9fd 100644 --- a/Ombi/Ombi/Auth/TokenProviderOptions.cs +++ b/Ombi/Ombi/Auth/TokenProviderOptions.cs @@ -5,6 +5,7 @@ using System.Security.Claims; using System.Security.Cryptography; using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens; +using Ombi.Core.IdentityResolver; namespace Ombi.Auth { @@ -40,7 +41,7 @@ namespace Ombi.Auth /// /// Resolves a user identity given a username and password. /// - public Func> IdentityResolver { get; set; } + public Func> IdentityResolver { get; set; } /// /// Generates a random value (nonce) for each generated token. diff --git a/Ombi/Ombi/Startup.Auth.cs b/Ombi/Ombi/Startup.Auth.cs index 00ad36eec..21ba6d509 100644 --- a/Ombi/Ombi/Startup.Auth.cs +++ b/Ombi/Ombi/Startup.Auth.cs @@ -7,11 +7,13 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Ombi.Auth; +using Ombi.Core.IdentityResolver; namespace Ombi { public partial class Startup { + public SymmetricSecurityKey signingKey; private void ConfigureAuth(IApplicationBuilder app) { @@ -66,32 +68,17 @@ namespace Ombi } - private Task GetIdentity(string username, string password) + private async Task GetIdentity(string username, string password, IUserIdentityManager userIdentityManager) { - // DEMO CODE, DON NOT USE IN PRODUCTION!!! - if (username == "TEST" && password == "TEST123") + var validLogin = await userIdentityManager.CredentialsValid(username, password); + if (!validLogin) { - var claim = new ClaimsIdentity(new GenericIdentity(username, "Token"), - new[] - { - //new Claim(ClaimTypes.Role, "Admin"), - new Claim(ClaimTypes.Name, "Test"), - - }); - - claim.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, "Admin", ClaimValueTypes.String)); - return Task.FromResult(claim); - } - if (username == "TEST2" && password == "TEST123") - { - return Task.FromResult(new ClaimsIdentity(new GenericIdentity(username, "Token"), new Claim[] { - new Claim(ClaimTypes.Role, "User"), - new Claim(ClaimTypes.Name, "Test2"), })); + return null; } - // Account doesn't exists - return Task.FromResult(null); + var user = await userIdentityManager.GetUser(username); + var claim = new ClaimsIdentity(new GenericIdentity(user.Username, "Token"), user.Claims); + return claim; } - } } \ No newline at end of file