identity server methods

master
Tudor Stanciu 2020-12-20 03:06:43 +02:00
parent aa71156974
commit 5416c982a5
27 changed files with 433 additions and 67 deletions

View File

@ -0,0 +1,41 @@
using IdentityServer.Application.Commands;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace IdentityServer.Api.Controllers
{
[ApiController]
[Route("identity")]
public class IdentityController : ControllerBase
{
private readonly IMediator _mediator;
public IdentityController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost("authenticate/{userName}/{password}")]
public async Task<IActionResult> AuthenticateUser([FromRoute] AuthenticateUser authenticateUser)
{
var result = await _mediator.Send(authenticateUser);
if (result != null)
return Ok(result);
else
return BadRequest();
}
[HttpPost("authorize/{token}")]
public async Task<IActionResult> AuthorizeToken([FromRoute] AuthorizeToken authorizeToken)
{
var result = await _mediator.Send(authorizeToken);
if (result != null)
return Ok(result);
else
return BadRequest();
}
}
}

View File

@ -1,39 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace IdentityServer.Api.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}

View File

@ -1,8 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Serilog.AspNetCore" Version="$(SerilogPackageVersion)" />
<PackageReference Include="Serilog.Extensions.Logging" Version="$(SerilogExtensionsPackageVersion)" />
<PackageReference Include="Serilog.Sinks.Console" Version="$(SerilogSinksConsolePackageVersion)" />
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="$(SerilogSinksMSSqlServerPackageVersion)" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="$(AutoMapperExtensionsPackageVersion)" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="$(MediatRPackageVersion)" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="$(SwashbucklePackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(MicrosoftExtensionsPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IdentityServer.Application\IdentityServer.Application.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -1,15 +0,0 @@
using System;
namespace IdentityServer.Api
{
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string Summary { get; set; }
}
}

View File

@ -1,8 +0,0 @@
using System;
namespace IdentityServer.Application
{
public class Class1
{
}
}

View File

@ -0,0 +1,32 @@
using AutoMapper;
using IdentityServer.Application.Commands;
using IdentityServer.Application.Services;
using IdentityServer.PublishedLanguage.Dto;
using MediatR;
using System.Threading;
using System.Threading.Tasks;
namespace IdentityServer.Application.CommandHandlers
{
public class AuthenticateUserHandler : IRequestHandler<AuthenticateUser, Token>
{
private readonly IUserService _userService;
private readonly IMapper _mapper;
public AuthenticateUserHandler(IUserService userService, IMapper mapper)
{
_userService = userService;
_mapper = mapper;
}
public async Task<Token> Handle(AuthenticateUser command, CancellationToken cancellationToken)
{
var internalToken = await _userService.Authenticate(command.UserName, command.Password);
if (internalToken == null)
return null;
var token = _mapper.Map<Token>(internalToken);
return token;
}
}
}

View File

@ -0,0 +1,32 @@
using AutoMapper;
using IdentityServer.Application.Commands;
using IdentityServer.Application.Services;
using IdentityServer.PublishedLanguage.Dto;
using MediatR;
using System.Threading;
using System.Threading.Tasks;
namespace IdentityServer.Application.CommandHandlers
{
public class AuthorizeTokenHandler : IRequestHandler<AuthorizeToken, User>
{
private readonly IUserService _userService;
private readonly IMapper _mapper;
public AuthorizeTokenHandler(IUserService userService, IMapper mapper)
{
_userService = userService;
_mapper = mapper;
}
public async Task<User> Handle(AuthorizeToken command, CancellationToken cancellationToken)
{
var appUser = await _userService.Authorize(command.Token);
if (appUser == null)
return null;
var user = _mapper.Map<User>(appUser);
return user;
}
}
}

View File

@ -0,0 +1,16 @@
using IdentityServer.PublishedLanguage.Dto;
namespace IdentityServer.Application.Commands
{
public class AuthenticateUser : Command<Token>
{
public string UserName { get; set; }
public string Password { get; set; }
public AuthenticateUser(string userName, string password)
{
UserName = userName;
Password = password;
}
}
}

View File

@ -0,0 +1,9 @@
using IdentityServer.PublishedLanguage.Dto;
namespace IdentityServer.Application.Commands
{
public class AuthorizeToken : Command<User>
{
public string Token { get; set; }
}
}

View File

@ -0,0 +1,45 @@
using MediatR;
using System;
using System.Collections.Generic;
namespace IdentityServer.Application.Commands
{
public abstract class Command<TResponse> : ICommand, IRequest<TResponse>
{
public Metadata Metadata { get; }
protected Command()
{
Metadata = new Metadata() { CorrelationId = Guid.NewGuid() };
}
protected Command(Metadata metadata)
{
Metadata = metadata;
}
}
public interface ICommand
{
}
public class Metadata : Dictionary<string, string>
{
public const string CorrelationIdKey = "CorrelationId";
public Guid CorrelationId
{
get
{
return Guid.Parse(this[CorrelationIdKey]);
}
set
{
if (ContainsKey(CorrelationIdKey))
this[CorrelationIdKey] = value.ToString();
else
Add(CorrelationIdKey, value.ToString());
}
}
}
}

View File

@ -0,0 +1,20 @@
using IdentityServer.Application.Services;
using IdentityServer.Application.Stores;
using Microsoft.Extensions.DependencyInjection;
namespace IdentityServer.Application
{
public static class DependencyInjectionExtensions
{
public static void AddApplicationServices(this IServiceCollection services)
{
services.AddStores();
services.AddScoped<IUserService, UserService>();
}
private static void AddStores(this IServiceCollection services)
{
services.AddSingleton<ISecurityStore, SecurityStore>();
}
}
}

View File

@ -4,4 +4,18 @@
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="$(AutoMapperPackageVersion)" />
<PackageReference Include="MediatR" Version="$(MediatRPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\IdentityServer.Domain\IdentityServer.Domain.csproj" />
<ProjectReference Include="..\IdentityServer.PublishedLanguage\IdentityServer.PublishedLanguage.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,16 @@
using AutoMapper;
using IdentityServer.Domain.Entities;
using dto = IdentityServer.PublishedLanguage.Dto;
using models = IdentityServer.Domain.Models;
namespace IdentityServer.Application.Mappings
{
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<models.Token, dto.Token>();
CreateMap<AppUser, dto.User>();
}
}
}

View File

@ -0,0 +1,6 @@
using MediatR;
namespace IdentityServer.Application.Queries
{
public abstract class Query<TResponse> : IRequest<TResponse>, IBaseRequest { }
}

View File

@ -0,0 +1,12 @@
using IdentityServer.Domain.Entities;
using IdentityServer.Domain.Models;
using System.Threading.Tasks;
namespace IdentityServer.Application.Services
{
public interface IUserService
{
Task<Token> Authenticate(string userName, string password);
Task<AppUser> Authorize(string token);
}
}

View File

@ -0,0 +1,46 @@
using IdentityServer.Application.Stores;
using IdentityServer.Domain.Entities;
using IdentityServer.Domain.Models;
using IdentityServer.Domain.Repositories;
using System;
using System.Threading.Tasks;
namespace IdentityServer.Application.Services
{
public class UserService : IUserService
{
private readonly ISecurityStore _securityStore;
private readonly IIdentityRepository _identityRepository;
public UserService(ISecurityStore securityStore, IIdentityRepository identityRepository)
{
_securityStore = securityStore;
_identityRepository = identityRepository;
}
public async Task<Token> Authenticate(string userName, string password)
{
var user = await _identityRepository.GetAppUser(userName, password);
if (user == null)
return null;
var tokenRaw = $"{Guid.NewGuid()}-{Guid.NewGuid()}-{user.UserId}";
_securityStore.SetToken(tokenRaw, user.UserId);
var token = new Token() { Raw = tokenRaw };
return token;
}
public async Task<AppUser> Authorize(string token)
{
var tokenValidation = _securityStore.ValidateToken(token);
if (tokenValidation.Success)
{
var user = await _identityRepository.GetAppUser(tokenValidation.UserId);
return user;
}
return null;
}
}
}

View File

@ -0,0 +1,10 @@
using IdentityServer.Domain.Models;
namespace IdentityServer.Application.Stores
{
public interface ISecurityStore
{
void SetToken(string token, int userId);
TokenValidation ValidateToken(string token);
}
}

View File

@ -0,0 +1,50 @@
using IdentityServer.Domain.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace IdentityServer.Application.Stores
{
public class SecurityStore : ISecurityStore
{
private ConcurrentDictionary<int, List<Token>> Tokens { get; }
public SecurityStore()
{
Tokens = new ConcurrentDictionary<int, List<Token>>();
}
public void SetToken(string token, int userId)
{
var registered = Tokens.TryGetValue(userId, out List<Token> list);
if (registered)
list.Add(new Token() { Raw = token });
else
Tokens.TryAdd(userId, new List<Token>() { new Token() { Raw = token } });
}
public TokenValidation ValidateToken(string token)
{
var lastIndexOfSeparator = token.LastIndexOf('-') + 1;
var userIdString = token.Substring(lastIndexOfSeparator, token.Length - lastIndexOfSeparator);
if (!int.TryParse(userIdString, out int userId))
return InvalidToken;
var registered = Tokens.TryGetValue(userId, out List<Token> list);
if (!registered)
return InvalidToken;
var valid = list.FirstOrDefault(z => z.Raw == token);
if (valid != null)
return new TokenValidation() { Success = true, UserId = userId };
return InvalidToken;
}
private TokenValidation InvalidToken => new TokenValidation() { Success = false };
}
}

View File

@ -15,6 +15,11 @@ namespace IdentityServer.Domain.Data.Repositories
_dbContext = dbContext; _dbContext = dbContext;
} }
public Task<AppUser> GetAppUser(int userId)
{
return _dbContext.AppUsers.FirstOrDefaultAsync(z => z.UserId == userId);
}
public Task<AppUser> GetAppUser(string userName, string password) public Task<AppUser> GetAppUser(string userName, string password)
{ {
return _dbContext.AppUsers.FirstOrDefaultAsync(z => z.UserName == userName && z.Password == password); return _dbContext.AppUsers.FirstOrDefaultAsync(z => z.UserName == userName && z.Password == password);

View File

@ -0,0 +1,10 @@
using System;
namespace IdentityServer.Domain.Models
{
public class Token
{
public string Raw { get; set; }
public DateTime ValidUntil { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace IdentityServer.Domain.Models
{
public class TokenValidation
{
public bool Success { get; set; }
public int UserId { get; set; }
}
}

View File

@ -1,10 +1,11 @@
using System; using IdentityServer.Domain.Entities;
using System.Collections.Generic; using System.Threading.Tasks;
using System.Text;
namespace IdentityServer.Domain.Repositories namespace IdentityServer.Domain.Repositories
{ {
public interface IIdentityRepository public interface IIdentityRepository
{ {
Task<AppUser> GetAppUser(int userId);
Task<AppUser> GetAppUser(string userName, string password);
} }
} }

View File

@ -0,0 +1,10 @@
using System;
namespace IdentityServer.PublishedLanguage.Dto
{
public class Token
{
public string Raw { get; set; }
public DateTime ValidUntil { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace IdentityServer.PublishedLanguage.Dto
{
public class User
{
public int UserId { get; set; }
public string UserName { get; set; }
}
}

View File

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityServer.Domain", "Id
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityServer.Domain.Data", "IdentityServer.Domain.Data\IdentityServer.Domain.Data.csproj", "{CE81A435-49AC-4544-A381-FAC91BEB3C49}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityServer.Domain.Data", "IdentityServer.Domain.Data\IdentityServer.Domain.Data.csproj", "{CE81A435-49AC-4544-A381-FAC91BEB3C49}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdentityServer.PublishedLanguage", "IdentityServer.PublishedLanguage\IdentityServer.PublishedLanguage.csproj", "{67B4D1FF-D02E-4DA6-9FB8-F71667360448}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -43,6 +45,10 @@ Global
{CE81A435-49AC-4544-A381-FAC91BEB3C49}.Debug|Any CPU.Build.0 = Debug|Any CPU {CE81A435-49AC-4544-A381-FAC91BEB3C49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CE81A435-49AC-4544-A381-FAC91BEB3C49}.Release|Any CPU.ActiveCfg = Release|Any CPU {CE81A435-49AC-4544-A381-FAC91BEB3C49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE81A435-49AC-4544-A381-FAC91BEB3C49}.Release|Any CPU.Build.0 = Release|Any CPU {CE81A435-49AC-4544-A381-FAC91BEB3C49}.Release|Any CPU.Build.0 = Release|Any CPU
{67B4D1FF-D02E-4DA6-9FB8-F71667360448}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67B4D1FF-D02E-4DA6-9FB8-F71667360448}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67B4D1FF-D02E-4DA6-9FB8-F71667360448}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67B4D1FF-D02E-4DA6-9FB8-F71667360448}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -52,6 +58,7 @@ Global
{6556D255-AF22-478E-A71A-BE37C16D5EE4} = {5A8FF505-3E4D-4258-BC3E-CACD74A7B98C} {6556D255-AF22-478E-A71A-BE37C16D5EE4} = {5A8FF505-3E4D-4258-BC3E-CACD74A7B98C}
{5890B079-3CB0-4AD6-8809-BB2E081590B1} = {5A8FF505-3E4D-4258-BC3E-CACD74A7B98C} {5890B079-3CB0-4AD6-8809-BB2E081590B1} = {5A8FF505-3E4D-4258-BC3E-CACD74A7B98C}
{CE81A435-49AC-4544-A381-FAC91BEB3C49} = {5A8FF505-3E4D-4258-BC3E-CACD74A7B98C} {CE81A435-49AC-4544-A381-FAC91BEB3C49} = {5A8FF505-3E4D-4258-BC3E-CACD74A7B98C}
{67B4D1FF-D02E-4DA6-9FB8-F71667360448} = {5A8FF505-3E4D-4258-BC3E-CACD74A7B98C}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E93DC46D-9C55-4A05-B299-497CDD90747E} SolutionGuid = {E93DC46D-9C55-4A05-B299-497CDD90747E}

View File

@ -4,4 +4,8 @@ dotnet publish --configuration Release --runtime win7-x64
Create windows service: Create windows service:
sc create NetworkResurrector.Api binPath= "<path_to_the_service_executable>" sc create NetworkResurrector.Api binPath= "<path_to_the_service_executable>"
####################################################################################################################################################### #######################################################################################################################################################
TO DO:
- Cache for users