Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Поддержка гостевого доступа для страницы со статистикой #402

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ public async Task<IActionResult> RefreshToken()
var tokenMeta = await AuthServiceClient.RefreshToken(UserId!);
return Ok(tokenMeta);
}

[HttpGet("getGuestToken")]
[Authorize(Roles = Roles.LecturerRole)]
[ProducesResponseType(typeof(TokenCredentials), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetGuestToken([FromQuery] string courseId)
{
var tokenMeta = await AuthServiceClient.GetGuestToken(courseId);
return Ok(tokenMeta);
}

[HttpPut("edit")]
[Authorize]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using HwProj.Models.CoursesService.ViewModels;
using HwProj.Models.Roles;
using HwProj.SolutionsService.Client;
using HwProj.Utils.Auth;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

Expand Down Expand Up @@ -70,14 +71,16 @@ public async Task<IActionResult> GetCourseStatistics(long courseId)
}

[HttpGet("{courseId}/charts")]
[Authorize(AuthenticationSchemes = AuthSchemeConstants.QueryStringTokenOrDefaultAuthentication)]
[ProducesResponseType(typeof(AdvancedCourseStatisticsViewModel), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetChartStatistics(long courseId)
public async Task<IActionResult> GetChartStatistics(long courseId, [FromQuery] string token)
{
var course = await _coursesClient.GetCourseById(courseId);
if (course == null)
return Forbid();

var statistics = await _solutionClient.GetCourseStatistics(courseId, UserId);
DedSec256 marked this conversation as resolved.
Show resolved Hide resolved

var studentIds = statistics.Select(t => t.StudentId).ToArray();
var studentsData = await AuthServiceClient.GetAccountsData(studentIds);

Expand Down
58 changes: 56 additions & 2 deletions HwProj.APIGateway/HwProj.APIGateway.API/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using HwProj.AuthService.Client;
using System.Threading.Tasks;
using HwProj.AuthService.Client;
using HwProj.CoursesService.Client;
using HwProj.NotificationsService.Client;
using HwProj.SolutionsService.Client;
using HwProj.Utils.Auth;
using HwProj.Utils.Configuration;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
Expand All @@ -27,7 +29,10 @@ public void ConfigureServices(IServiceCollection services)

const string authenticationProviderKey = "GatewayKey";

services.AddAuthentication()
services.AddAuthentication(options =>
{
options.DefaultScheme = authenticationProviderKey;
})
.AddJwtBearer(authenticationProviderKey, x =>
{
x.RequireHttpsMetadata = false;
Expand All @@ -40,6 +45,55 @@ public void ConfigureServices(IServiceCollection services)
IssuerSigningKey = AuthorizationKey.SecurityKey,
ValidateIssuerSigningKey = true
};
})
.AddJwtBearer(AuthSchemeConstants.QueryStringTokenAuthentication, options =>
{
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "AuthService",
ValidateLifetime = false,
ValidateAudience = false,
IssuerSigningKey = AuthorizationKey.SecurityKey
};

options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Query.ContainsKey("token"))
{
context.Token = context.Request.Query["token"];
}
else
{
context.Fail("Unauthorized");
}

return Task.CompletedTask;
},
OnTokenValidated = async context =>
{
var courseIdClaim = context.Principal.FindFirst("_courseId");
if (courseIdClaim == null)
{
context.Fail("Unauthorized");
return;
}

var authServiceClient = context.HttpContext.RequestServices
.GetRequiredService<IAuthServiceClient>();
var statsAccessToken = await authServiceClient.GetGuestToken(courseIdClaim.Value);
var guestToken = context.Request.Query["token"];

if (statsAccessToken.AccessToken != guestToken)
{
context.Fail("Unauthorized");
}
}
};

});

services.AddHttpClient();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ public async Task<IActionResult> RefreshToken(string userId)
var tokenMeta = await _accountService.RefreshToken(userId);
return Ok(tokenMeta);
}

[HttpGet("getGuestToken")]
[ProducesResponseType(typeof(TokenCredentials), (int)HttpStatusCode.OK)]
public Task<IActionResult> GetGuestToken([FromQuery] string courseId)
{
var tokenMeta = _accountService.GetGuestToken(courseId);
return Task.FromResult<IActionResult>(Ok(tokenMeta));
}

[HttpPut("edit/{userId}")]
[ProducesResponseType(typeof(Result), (int)HttpStatusCode.OK)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ public async Task<Result<TokenCredentials>> RefreshToken(string userId)
? Result<TokenCredentials>.Failed("Пользователь не найден")
: await GetToken(user);
}

public TokenCredentials GetGuestToken(string courseId)
{
return _tokenService.GetGuestToken(courseId);
}

public async Task<Result<TokenCredentials>> RegisterUserAsync(RegisterDataDTO model)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,26 @@ public async Task<TokenCredentials> GetTokenAsync(User user)

return tokenCredentials;
}

public TokenCredentials GetGuestToken(string courseId)
{
var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_configuration["SecurityKey"]));

var token = new JwtSecurityToken(
issuer: _configuration["ApiName"],
claims: new[]
{
new Claim("_courseId", courseId),
new Claim("_isGuest", "true")
},
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256));

var tokenCredentials = new TokenCredentials
{
AccessToken = new JwtSecurityTokenHandler().WriteToken(token)
};

return tokenCredentials;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public interface IAccountService
Task<Result> EditAccountAsync(string accountId, EditDataDTO model);
Task<Result<TokenCredentials>> LoginUserAsync(LoginViewModel model);
Task<Result<TokenCredentials>> RefreshToken(string userId);
TokenCredentials GetGuestToken(string courseId);
Task<Result> InviteNewLecturer(string emailOfInvitedUser);
Task<IList<User>> GetUsersInRole(string role);
Task<Result> RequestPasswordRecovery(RequestPasswordRecoveryViewModel model);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ namespace HwProj.AuthService.API.Services
public interface IAuthTokenService
{
Task<TokenCredentials> GetTokenAsync(User user);
TokenCredentials GetGuestToken(string courseId);
}
}
10 changes: 10 additions & 0 deletions HwProj.AuthService/HwProj.AuthService.Client/AuthServiceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ public async Task<Result<TokenCredentials>> RefreshToken(string userId)
var response = await _httpClient.SendAsync(httpRequest);
return await response.DeserializeAsync<Result<TokenCredentials>>();
}

public async Task<TokenCredentials> GetGuestToken(string courseId)
{
using var httpRequest = new HttpRequestMessage(
HttpMethod.Get,
_authServiceUri + $"api/account/getGuestToken/?courseId={courseId}");

var response = await _httpClient.SendAsync(httpRequest);
return await response.DeserializeAsync<TokenCredentials>();
}

public async Task<Result> Edit(EditAccountViewModel model, string userId)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public interface IAuthServiceClient
Task<Result<TokenCredentials>> Register(RegisterViewModel model);
Task<Result<TokenCredentials>> Login(LoginViewModel model);
Task<Result<TokenCredentials>> RefreshToken(string userId);
Task<TokenCredentials> GetGuestToken(string courseId);
Task<Result> Edit(EditAccountViewModel model, string userId);
Task<Result> InviteNewLecturer(InviteLecturerViewModel model);
Task<string> FindByEmailAsync(string email);
Expand Down
7 changes: 7 additions & 0 deletions HwProj.Common/HwProj.HttpUtils/RequestHeaderBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,12 @@ public static void TryAddUserId(this HttpRequestMessage request, IHttpContextAcc
var userId = httpContextAccessor.HttpContext.User.FindFirst("_id");
if (userId != null) request.Headers.Add("UserId", userId.Value);
}

public static void TryAddGuestMode(this HttpRequestMessage request, IHttpContextAccessor httpContextAccessor)
{
var guestMode = httpContextAccessor.HttpContext.User.FindFirst("_isGuest");
if (guestMode != null)
request.Headers.Add("GuestMode", guestMode.Value);
}
}
}
3 changes: 3 additions & 0 deletions HwProj.Common/HwProj.Utils/Auth/AuthExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ public static class AuthExtensions
{
public static string? GetUserIdFromHeader(this HttpRequest request) =>
request.Headers.TryGetValue("UserId", out var id) ? id.FirstOrDefault() : null;

public static string? GetGuestModeFromHeader(this HttpRequest request) =>
request.Headers.TryGetValue("GuestMode", out var isGuest) ? isGuest.FirstOrDefault() : null;

public static AccountDataDto ToAccountDataDto(this User user, string role)
{
Expand Down
45 changes: 45 additions & 0 deletions HwProj.Common/HwProj.Utils/Auth/GuestModeAuthenticationHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using HwProj.Utils.Authorization;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace HwProj.Utils.Auth
{
public class GuestModeAuthenticationOptions : AuthenticationSchemeOptions
{
}

public class GuestModeAuthenticationHandler : AuthenticationHandler<GuestModeAuthenticationOptions>
{
public GuestModeAuthenticationHandler(
IOptionsMonitor<GuestModeAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var isGuest = Request.GetGuestModeFromHeader();
if (isGuest != "true")
return Task.FromResult(AuthenticateResult.Fail("Unauthorized"));

var claims = new List<Claim>
{
new Claim("_isGuest", isGuest)
};

var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new System.Security.Principal.GenericPrincipal(identity, null);
var ticket = new AuthenticationTicket(principal, Scheme.Name);

return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ namespace HwProj.Utils.Auth
public static class AuthSchemeConstants
{
public const string UserIdAuthentication = "UserIdAuth";
public const string GuestModeAuthentication = "GuestModeAuth";
public const string QueryStringTokenAuthentication = "QueryStringTokenAuth";
public const string QueryStringTokenOrDefaultAuthentication = QueryStringTokenAuthentication + "," + "GatewayKey";
public const string GuestModeOrUseridAuthentication = GuestModeAuthentication + "," + UserIdAuthentication;
}

public class UserIdAuthenticationOptions : AuthenticationSchemeOptions
Expand Down
17 changes: 13 additions & 4 deletions HwProj.Common/HwProj.Utils/Configuration/StartupExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using HwProj.EventBus.Client.Interfaces;
using HwProj.Utils.Auth;
using HwProj.Utils.Configuration.Middleware;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
Expand Down Expand Up @@ -179,14 +180,22 @@ public static IApplicationBuilder ConfigureHwProj(this IApplicationBuilder app,
return app;
}

public static IServiceCollection AddUserIdAuthentication(this IServiceCollection services)
public static AuthenticationBuilder AddUserIdAuthentication(this AuthenticationBuilder builder)
{
services
.AddAuthentication(AuthSchemeConstants.UserIdAuthentication)
builder
.AddScheme<UserIdAuthenticationOptions, UserIdAuthenticationHandler>(
AuthSchemeConstants.UserIdAuthentication, null);

return services;
return builder;
}

public static AuthenticationBuilder AddGuestModeAuthentication(this AuthenticationBuilder builder)
{
builder
.AddScheme<GuestModeAuthenticationOptions, GuestModeAuthenticationHandler>(
AuthSchemeConstants.GuestModeAuthentication, null);

return builder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ public class CourseDataFilterAttribute : ResultFilterAttribute
{
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var isGuest = context.HttpContext.Request.GetGuestModeFromHeader();
var userId = context.HttpContext.Request.GetUserIdFromHeader();

if (userId == null)
if (userId == null && isGuest != "true")
{
context.Result = new ForbidResult();
}
else
else if (isGuest == null)
{
var result = context.Result as ObjectResult;
if (result?.Value is CourseDTO courseDto && !courseDto.MentorIds.Contains(userId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public async Task<CoursePreview[]> GetAllCourses()
_coursesServiceUri + $"api/Courses/{courseId}");

httpRequest.TryAddUserId(_httpContextAccessor);
httpRequest.TryAddGuestMode(_httpContextAccessor);
var response = await _httpClient.SendAsync(httpRequest);
return response.IsSuccessStatusCode ? await response.DeserializeAsync<CourseDTO>() : null;
}
Expand Down
Loading