mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 04:49:33 -07:00
#865 more for the authentication
This commit is contained in:
parent
98fb15c263
commit
cf6b5c5138
12 changed files with 248 additions and 25 deletions
14
Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs
Normal file
14
Ombi/Ombi.Core/IdentityResolver/IUserIdentityManager.cs
Normal file
|
@ -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<bool> CredentialsValid(string username, string password);
|
||||||
|
Task<User> GetUser(string username);
|
||||||
|
Task<IEnumerable<User>> GetUsers();
|
||||||
|
}
|
||||||
|
}
|
90
Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs
Normal file
90
Ombi/Ombi.Core/IdentityResolver/UserIdentityManager.cs
Normal file
|
@ -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<bool> CredentialsValid(string username, string password)
|
||||||
|
{
|
||||||
|
var user = await UserRepository.GetUser(username);
|
||||||
|
var hashedPass = HashPassword(password);
|
||||||
|
|
||||||
|
return hashedPass.Equals(user.Password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User> GetUser(string username)
|
||||||
|
{
|
||||||
|
return await UserRepository.GetUser(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<User>> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="1.1.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
||||||
|
|
|
@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Ombi.Core;
|
using Ombi.Core;
|
||||||
using Ombi.Core.Engine;
|
using Ombi.Core.Engine;
|
||||||
|
using Ombi.Core.IdentityResolver;
|
||||||
using Ombi.Core.Models.Requests;
|
using Ombi.Core.Models.Requests;
|
||||||
using Ombi.Core.Requests.Models;
|
using Ombi.Core.Requests.Models;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
|
@ -48,6 +49,7 @@ namespace Ombi.DependencyInjection
|
||||||
services.AddTransient<IOmbiContext, OmbiContext>();
|
services.AddTransient<IOmbiContext, OmbiContext>();
|
||||||
services.AddTransient<IRequestRepository, RequestJsonRepository>();
|
services.AddTransient<IRequestRepository, RequestJsonRepository>();
|
||||||
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
|
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
|
||||||
|
services.AddTransient<IUserRepository, UserRepository>();
|
||||||
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>));
|
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>));
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +61,7 @@ namespace Ombi.DependencyInjection
|
||||||
|
|
||||||
public static IServiceCollection RegisterIdentity(this IServiceCollection services)
|
public static IServiceCollection RegisterIdentity(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
services.AddTransient<IUserIdentityManager, UserIdentityManager>();
|
||||||
services.AddAuthorization(auth =>
|
services.AddAuthorization(auth =>
|
||||||
{
|
{
|
||||||
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
|
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
|
||||||
|
|
|
@ -12,5 +12,6 @@ namespace Ombi.Store.Context
|
||||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
|
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||||
DbSet<RequestBlobs> Requests { get; set; }
|
DbSet<RequestBlobs> Requests { get; set; }
|
||||||
DbSet<GlobalSettings> Settings { get; set; }
|
DbSet<GlobalSettings> Settings { get; set; }
|
||||||
|
DbSet<User> Users { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,6 +16,7 @@ namespace Ombi.Store.Context
|
||||||
}
|
}
|
||||||
public DbSet<RequestBlobs> Requests { get; set; }
|
public DbSet<RequestBlobs> Requests { get; set; }
|
||||||
public DbSet<GlobalSettings> Settings { get; set; }
|
public DbSet<GlobalSettings> Settings { get; set; }
|
||||||
|
public DbSet<User> Users { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
|
|
48
Ombi/Ombi.Store/Entities/User.cs
Normal file
48
Ombi/Ombi.Store/Entities/User.cs
Normal file
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
13
Ombi/Ombi.Store/Repository/IUserRepository.cs
Normal file
13
Ombi/Ombi.Store/Repository/IUserRepository.cs
Normal file
|
@ -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<User> GetUser(string username);
|
||||||
|
Task<IEnumerable<User>> GetUsers();
|
||||||
|
}
|
||||||
|
}
|
62
Ombi/Ombi.Store/Repository/UserRepository.cs
Normal file
62
Ombi/Ombi.Store/Repository/UserRepository.cs
Normal file
|
@ -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<User> 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<IEnumerable<User>> GetUsers()
|
||||||
|
{
|
||||||
|
return await Db.Users.ToListAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,10 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Ombi.Core.IdentityResolver;
|
||||||
using Ombi.Models;
|
using Ombi.Models;
|
||||||
|
using Ombi.Store.Context;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
|
||||||
namespace Ombi.Auth
|
namespace Ombi.Auth
|
||||||
{
|
{
|
||||||
|
@ -23,7 +26,6 @@ namespace Ombi.Auth
|
||||||
IOptions<TokenProviderOptions> options)
|
IOptions<TokenProviderOptions> options)
|
||||||
{
|
{
|
||||||
_next = next;
|
_next = next;
|
||||||
|
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
ThrowIfInvalidOptions(_options);
|
ThrowIfInvalidOptions(_options);
|
||||||
|
|
||||||
|
@ -64,7 +66,7 @@ namespace Ombi.Auth
|
||||||
userInfo = JsonConvert.DeserializeObject<UserAuthModel>(body);
|
userInfo = JsonConvert.DeserializeObject<UserAuthModel>(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)
|
if (identity == null)
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = 400;
|
context.Response.StatusCode = 400;
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Security.Claims;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Ombi.Core.IdentityResolver;
|
||||||
|
|
||||||
namespace Ombi.Auth
|
namespace Ombi.Auth
|
||||||
{
|
{
|
||||||
|
@ -40,7 +41,7 @@ namespace Ombi.Auth
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves a user identity given a username and password.
|
/// Resolves a user identity given a username and password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<string, string, Task<ClaimsIdentity>> IdentityResolver { get; set; }
|
public Func<string, string, IUserIdentityManager, Task<ClaimsIdentity>> IdentityResolver { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a random value (nonce) for each generated token.
|
/// Generates a random value (nonce) for each generated token.
|
||||||
|
|
|
@ -7,11 +7,13 @@ using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Ombi.Auth;
|
using Ombi.Auth;
|
||||||
|
using Ombi.Core.IdentityResolver;
|
||||||
|
|
||||||
namespace Ombi
|
namespace Ombi
|
||||||
{
|
{
|
||||||
public partial class Startup
|
public partial class Startup
|
||||||
{
|
{
|
||||||
|
|
||||||
public SymmetricSecurityKey signingKey;
|
public SymmetricSecurityKey signingKey;
|
||||||
private void ConfigureAuth(IApplicationBuilder app)
|
private void ConfigureAuth(IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
|
@ -66,32 +68,17 @@ namespace Ombi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Task<ClaimsIdentity> GetIdentity(string username, string password)
|
private async Task<ClaimsIdentity> GetIdentity(string username, string password, IUserIdentityManager userIdentityManager)
|
||||||
{
|
{
|
||||||
// DEMO CODE, DON NOT USE IN PRODUCTION!!!
|
var validLogin = await userIdentityManager.CredentialsValid(username, password);
|
||||||
if (username == "TEST" && password == "TEST123")
|
if (!validLogin)
|
||||||
{
|
{
|
||||||
var claim = new ClaimsIdentity(new GenericIdentity(username, "Token"),
|
return null;
|
||||||
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"), }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account doesn't exists
|
var user = await userIdentityManager.GetUser(username);
|
||||||
return Task.FromResult<ClaimsIdentity>(null);
|
var claim = new ClaimsIdentity(new GenericIdentity(user.Username, "Token"), user.Claims);
|
||||||
|
return claim;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue