Merge pull request 'features/role-updates' (#18) from features/role-updates into main
continuous-integration/drone/push Build is passing Details

Reviewed-on: #18
This commit is contained in:
jtom38 2023-08-06 13:39:45 -07:00
commit d1408ce6a4
14 changed files with 71 additions and 40 deletions

View File

@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newsbot.Collector.Api.Domain; using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Models.Config; using Newsbot.Collector.Domain.Models.Config;
using Newsbot.Collector.Services.Jobs; using Newsbot.Collector.Services.Jobs;

View File

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newsbot.Collector.Api.Domain; using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Requests; using Newsbot.Collector.Domain.Requests;
using Newsbot.Collector.Domain.Response; using Newsbot.Collector.Domain.Response;
using Newsbot.Collector.Domain.Results; using Newsbot.Collector.Domain.Results;

View File

@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newsbot.Collector.Api.Domain; using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Models.Config; using Newsbot.Collector.Domain.Models.Config;
using Newsbot.Collector.Domain.Models.Config.Sources; using Newsbot.Collector.Domain.Models.Config.Sources;
using Newsbot.Collector.Services.Jobs; using Newsbot.Collector.Services.Jobs;

View File

@ -1,17 +1,14 @@
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Api.Domain;
using Newsbot.Collector.Database.Repositories;
using Newsbot.Collector.Domain.Consts; using Newsbot.Collector.Domain.Consts;
using Newsbot.Collector.Domain.Dto; using Newsbot.Collector.Domain.Dto;
using Newsbot.Collector.Domain.Entities; using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces; using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Models;
using Newsbot.Collector.Services.HtmlParser; using Newsbot.Collector.Services.HtmlParser;
namespace Newsbot.Collector.Api.Controllers; namespace Newsbot.Collector.Api.Controllers.v1;
[ApiController] [ApiController]
[Route("api/sources")] [Route("api/sources")]

View File

@ -3,11 +3,10 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newsbot.Collector.Api.Domain; using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Models.Config; using Newsbot.Collector.Domain.Models.Config;
using Newsbot.Collector.Domain.Models.Config.Sources; using Newsbot.Collector.Domain.Models.Config.Sources;
using Newsbot.Collector.Services.Jobs; using Newsbot.Collector.Services.Jobs;
using ILogger = Grpc.Core.Logging.ILogger;
namespace Newsbot.Collector.Api.Controllers.v1; namespace Newsbot.Collector.Api.Controllers.v1;

View File

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Newsbot.Collector.Api.Domain; using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Database; using Newsbot.Collector.Database;
using Newsbot.Collector.Database.Repositories; using Newsbot.Collector.Database.Repositories;
using Newsbot.Collector.Domain.Interfaces; using Newsbot.Collector.Domain.Interfaces;

View File

@ -1,7 +1,7 @@
using System.Text; using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Newsbot.Collector.Api.Domain; using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Models.Config; using Newsbot.Collector.Domain.Models.Config;
namespace Newsbot.Collector.Api.Startup; namespace Newsbot.Collector.Api.Startup;
@ -44,7 +44,9 @@ public static class IdentityStartup
services.AddAuthorization(options => services.AddAuthorization(options =>
{ {
options.AddPolicy(Authorization.AdministratorPolicy, options.AddPolicy(Authorization.AdministratorPolicy,
b => b.RequireClaim(Authorization.AdministratorClaim, "true")); b => b.RequireRole(Authorization.AdministratorsRole, "true"));
options.AddPolicy(Authorization.UserPolicy,
b => b.RequireRole(Authorization.UsersRole, "true"));
}); });
} }
} }

View File

@ -1,17 +1,13 @@
using System.Data; using Microsoft.EntityFrameworkCore;
using Dapper;
using Microsoft.Extensions.Configuration;
using Newsbot.Collector.Domain.Entities; using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces; using Newsbot.Collector.Domain.Interfaces;
using Newsbot.Collector.Domain.Models;
using Npgsql;
namespace Newsbot.Collector.Database.Repositories; namespace Newsbot.Collector.Database.Repositories;
public class SourcesTable : ISourcesRepository public class SourcesTable : ISourcesRepository
{ {
//private readonly string _connectionString; //private readonly string _connectionString;
private DatabaseContext _context; private readonly DatabaseContext _context;
public SourcesTable(string connectionString) public SourcesTable(string connectionString)
{ {
@ -114,6 +110,14 @@ public class SourcesTable : ISourcesRepository
return res; return res;
} }
public async Task<int> TotalByTypeAsync(string type)
{
var res = await _context.Sources
.Where(f => f.Type == type )
.CountAsync();
return res;
}
public int Disable(Guid id) public int Disable(Guid id)
{ {
//using var context = new DatabaseContext(_connectionString); //using var context = new DatabaseContext(_connectionString);

View File

@ -1,12 +1,13 @@
namespace Newsbot.Collector.Api.Domain; namespace Newsbot.Collector.Api.Domain.Consts;
public static class Authorization public static class Authorization
{ {
public const string AdministratorPolicy = "Administrator"; public const string AdministratorPolicy = "Administrator";
public const string AdministratorsRole = AdministratorPolicy;
public const string AdministratorClaim = "administrator"; public const string AdministratorClaim = "administrator";
public const string AdministratorsRole = AdministratorPolicy;
public const string UserPolicy = "User"; public const string UserPolicy = "User";
public const string UsersRole = UserPolicy; public const string UsersRole = UserPolicy;
public const string UserClaim = "user";
} }

View File

@ -14,6 +14,7 @@ public interface ISourcesRepository
public List<SourceEntity> List(int page, int count); public List<SourceEntity> List(int page, int count);
public List<SourceEntity> ListBySource(string source,int page, int limit); public List<SourceEntity> ListBySource(string source,int page, int limit);
public List<SourceEntity> ListByType(string type,int page, int limit = 25); public List<SourceEntity> ListByType(string type,int page, int limit = 25);
public Task<int> TotalByTypeAsync(string type);
public int Disable(Guid id); public int Disable(Guid id);
public int Enable(Guid id); public int Enable(Guid id);
public void Delete(Guid id); public void Delete(Guid id);

View File

@ -3,6 +3,7 @@ using System.Security.Claims;
using System.Text; using System.Text;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Newsbot.Collector.Api.Domain.Consts;
using Newsbot.Collector.Domain.Results; using Newsbot.Collector.Domain.Results;
using Newsbot.Collector.Domain.Entities; using Newsbot.Collector.Domain.Entities;
using Newsbot.Collector.Domain.Interfaces; using Newsbot.Collector.Domain.Interfaces;
@ -16,22 +17,24 @@ public interface IIdentityService
AuthenticationResult Register(string email, string password); AuthenticationResult Register(string email, string password);
AuthenticationResult Login(string email, string password); AuthenticationResult Login(string email, string password);
AuthenticationResult RefreshToken(string token, string refreshToken); AuthenticationResult RefreshToken(string token, string refreshToken);
void AddRole(string roleName, string userId); void AddRole(string name, string userId);
} }
public class IdentityService : IIdentityService public class IdentityService : IIdentityService
{ {
private readonly UserManager<IdentityUser> _userManager; private readonly UserManager<IdentityUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly JwtSettings _jwtSettings; private readonly JwtSettings _jwtSettings;
private readonly TokenValidationParameters _tokenValidationParameters; private readonly TokenValidationParameters _tokenValidationParameters;
private readonly IRefreshTokenRepository _refreshTokenRepository; private readonly IRefreshTokenRepository _refreshTokenRepository;
public IdentityService(UserManager<IdentityUser> userManager, JwtSettings jwtSettings, TokenValidationParameters tokenValidationParameters, IRefreshTokenRepository refreshTokenRepository) public IdentityService(UserManager<IdentityUser> userManager, JwtSettings jwtSettings, TokenValidationParameters tokenValidationParameters, IRefreshTokenRepository refreshTokenRepository, RoleManager<IdentityRole> roleManager)
{ {
_userManager = userManager; _userManager = userManager;
_jwtSettings = jwtSettings; _jwtSettings = jwtSettings;
_tokenValidationParameters = tokenValidationParameters; _tokenValidationParameters = tokenValidationParameters;
_refreshTokenRepository = refreshTokenRepository; _refreshTokenRepository = refreshTokenRepository;
_roleManager = roleManager;
} }
public AuthenticationResult Register(string email, string password) public AuthenticationResult Register(string email, string password)
@ -64,6 +67,9 @@ public class IdentityService : IIdentityService
}; };
} }
var addRole = _userManager.AddToRoleAsync(newUser, Authorization.UsersRole);
addRole.Wait();
return GenerateJwtToken(newUser); return GenerateJwtToken(newUser);
} }
@ -109,8 +115,9 @@ public class IdentityService : IIdentityService
var expiryDateUnix = long.Parse(validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Exp).Value); var expiryDateUnix = long.Parse(validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Exp).Value);
// generate the unix epoc, add expiry time // generate the unix epoc, add expiry time
var expiryDateTimeUtc = new DateTime(1970, 0, 0, 0, 0, 0, DateTimeKind.Utc)
.AddSeconds(expiryDateUnix); var unixTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var expiryDateTimeUtc = unixTime.AddSeconds(expiryDateUnix);
// if it expires in the future // if it expires in the future
if (expiryDateTimeUtc > DateTime.Now) if (expiryDateTimeUtc > DateTime.Now)
@ -179,7 +186,7 @@ public class IdentityService : IIdentityService
return GenerateJwtToken(user.Result); return GenerateJwtToken(user.Result);
} }
public void AddRole(string roleName, string userId) public void AddRole(string name, string userId)
{ {
var user = _userManager.FindByIdAsync(userId); var user = _userManager.FindByIdAsync(userId);
user.Wait(); user.Wait();
@ -189,7 +196,14 @@ public class IdentityService : IIdentityService
throw new Exception("User was not found"); throw new Exception("User was not found");
} }
_userManager.AddToRoleAsync(user.Result, roleName); if (!name.Equals(Authorization.AdministratorClaim)
|| !name.Equals(Authorization.UserClaim))
{
throw new Exception("Invalid role");
}
var addRole = _userManager.AddToRoleAsync(user.Result, name);
addRole.Wait();
} }
private ClaimsPrincipal? CheckTokenSigner(string token) private ClaimsPrincipal? CheckTokenSigner(string token)
@ -221,15 +235,25 @@ public class IdentityService : IIdentityService
{ {
var tokenHandler = new JwtSecurityTokenHandler(); var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtSettings.Secret ?? ""); var key = Encoding.ASCII.GetBytes(_jwtSettings.Secret ?? "");
var tokenDescriptor = new SecurityTokenDescriptor
{ var claims = new List<Claim>
Subject = new ClaimsIdentity(new[]
{ {
new Claim(JwtRegisteredClaimNames.Sub, user.Email ?? ""), new Claim(JwtRegisteredClaimNames.Sub, user.Email ?? ""),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email ?? ""), new Claim(JwtRegisteredClaimNames.Email, user.Email ?? ""),
new Claim("id", user.Id) new Claim("id", user.Id)
}), };
var userRoles = _userManager.GetRolesAsync(user);
userRoles.Wait();
foreach (var role in userRoles.Result)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(3), Expires = DateTime.UtcNow.AddHours(3),
SigningCredentials = SigningCredentials =
new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
@ -239,6 +263,7 @@ public class IdentityService : IIdentityService
var refreshToken = new RefreshTokenEntity var refreshToken = new RefreshTokenEntity
{ {
Token = token.Id,
JwtId = token.Id, JwtId = token.Id,
UserId = user.Id, UserId = user.Id,
CreatedDate = DateTime.UtcNow, CreatedDate = DateTime.UtcNow,

View File

@ -181,7 +181,7 @@ public class CodeProjectWatcherJob
Tags = source.Tags, Tags = source.Tags,
Title = item.Title.Text, Title = item.Title.Text,
Url = itemUrl, Url = itemUrl,
PubDate = item.LastUpdatedTime.DateTime, PubDate = item.LastUpdatedTime.DateTime.ToUniversalTime(),
Thumbnail = parser.Data.Header.Image, Thumbnail = parser.Data.Header.Image,
Description = item.Title.Text, Description = item.Title.Text,
CodeIsRelease = isRelease, CodeIsRelease = isRelease,

View File

@ -105,7 +105,7 @@ public class RssWatcherJob
Title = post.Title.Text, Title = post.Title.Text,
Tags = FetchTags(post), Tags = FetchTags(post),
Url = articleUrl, Url = articleUrl,
PubDate = post.PublishDate.DateTime, PubDate = post.PublishDate.DateTime.ToUniversalTime(),
Thumbnail = meta.Data.Header.Image, Thumbnail = meta.Data.Header.Image,
Description = meta.Data.Header.Description, Description = meta.Data.Header.Description,
SourceId = sourceId SourceId = sourceId

View File

@ -56,7 +56,9 @@ public class YoutubeWatcherJob
private void Execute() private void Execute()
{ {
var sources = _source.ListByType(SourceTypes.YouTube, 100); var totalSources = _source.TotalByTypeAsync(SourceTypes.YouTube);
var sources = _source.ListByType(SourceTypes.YouTube, 0);
foreach (var source in sources) foreach (var source in sources)
{ {
@ -165,7 +167,7 @@ public class YoutubeWatcherJob
Title = post.Title.Text, Title = post.Title.Text,
Tags = FetchTags(post), Tags = FetchTags(post),
Url = articleUrl, Url = articleUrl,
PubDate = post.PublishDate.DateTime, PubDate = post.PublishDate.DateTime.ToUniversalTime(),
Thumbnail = videoDetails.Data.Header.Image, Thumbnail = videoDetails.Data.Header.Image,
Description = videoDetails.Data.Header.Description, Description = videoDetails.Data.Header.Description,
SourceId = sourceId, SourceId = sourceId,