Skip to content

Commit 5743d3b

Browse files
Merge pull request #21 from CapstoneProjectCMC/feature/Organization-Service
fix config about not running file in staging env
2 parents d9528ac + 1695f51 commit 5743d3b

File tree

9 files changed

+226
-19
lines changed

9 files changed

+226
-19
lines changed

FileService/FileService.Api/Controllers/FileDocumentController.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77

88
namespace FileService.Api.Controllers
99
{
10-
// [Authorize]
11-
// [Authorize(Policy = "Permission")]
12-
// [Authorize(Roles = "ADMIN")]
10+
// [Authorize]
11+
// [Authorize(Policy = "AdminOnly")]
12+
// [Authorize(Roles = "ADMIN")]
13+
// [AllowAnonymous]
1314
[Route("file/api/[controller]")]
1415
[ApiController]
1516
public class FileDocumentController : BaseApiController

FileService/FileService.Api/Middlewares/ExceptionMiddleware.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using FileService.Core.ApiModels;
2+
using FileService.Core.Enums;
23
using FileService.Core.Exceptions;
34
using Newtonsoft.Json;
45
using Newtonsoft.Json.Serialization;
56
using System.Data;
67
using System.Net;
8+
using System.Security;
79
using System.Text;
810

911
namespace FileService.Api.Middlewares
@@ -31,14 +33,19 @@ public async Task InvokeAsync(HttpContext httpContext)
3133
// await LogResuest(httpContext);
3234
await _next(httpContext);
3335

36+
3437
// log the outgoing response
3538
await LogResponse(httpContext);
3639
}
3740
catch (ErrorException ex)
3841
{
3942
await HandleCustomExceptionAsync(httpContext, ex);
4043
}
41-
catch(UnauthorizedAccessException ex)
44+
catch (SecurityException ex) // Thêm cho 403
45+
{
46+
await HandleForbiddenExceptionAsync(httpContext, ex);
47+
}
48+
catch (UnauthorizedAccessException ex)
4249
{
4350
await HandleUnauthorizedExceptionAsync(httpContext, ex);
4451
}
@@ -55,6 +62,23 @@ public async Task InvokeAsync(HttpContext httpContext)
5562
}
5663
}
5764

65+
private async Task HandleForbiddenExceptionAsync(HttpContext context, SecurityException exception)
66+
{
67+
context.Response.ContentType = "application/json";
68+
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
69+
var apiResponse = new ApiResponseModel(StatusCodeEnum.Forbidden)
70+
{
71+
Message = "Access denied due to insufficient permissions.",
72+
Result = null
73+
};
74+
var jsonResponse = JsonConvert.SerializeObject(apiResponse, new JsonSerializerSettings
75+
{
76+
ContractResolver = new CamelCasePropertyNamesContractResolver()
77+
});
78+
await context.Response.WriteAsync(jsonResponse);
79+
_logger.LogWarning("Forbidden access - {Message}", exception.Message);
80+
}
81+
5882

5983
private async Task HandleCustomExceptionAsync(HttpContext context, ErrorException exception)
6084
{
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using FileService.Core.ApiModels;
2+
using FileService.Core.Enums;
3+
using Microsoft.AspNetCore.Authorization;
4+
using Microsoft.Extensions.Caching.Memory;
5+
using Microsoft.IdentityModel.Tokens;
6+
using System.IdentityModel.Tokens.Jwt;
7+
using System.Security.Claims;
8+
using System.Text;
9+
10+
namespace FileService.Api.Middlewares
11+
{
12+
public class RoleCheckMiddleware
13+
{
14+
private readonly RequestDelegate _next;
15+
private readonly ILogger<RoleCheckMiddleware> _logger;
16+
private readonly IMemoryCache _cache;
17+
18+
public RoleCheckMiddleware(RequestDelegate next, ILogger<RoleCheckMiddleware> logger, IMemoryCache cache)
19+
{
20+
_next = next;
21+
_logger = logger;
22+
_cache = cache;
23+
}
24+
25+
public async Task InvokeAsync(HttpContext context)
26+
{
27+
// Bỏ qua middleware nếu endpoint có [AllowAnonymous]
28+
var endpoint = context.GetEndpoint();
29+
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
30+
{
31+
_logger.LogDebug("Bypassing RoleCheckMiddleware for anonymous endpoint: {Path}", context.Request.Path);
32+
await _next(context);
33+
return;
34+
}
35+
36+
// Lấy token từ header
37+
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
38+
if (string.IsNullOrEmpty(token))
39+
{
40+
_logger.LogWarning("Missing token in request to {Path}", context.Request.Path);
41+
await WriteError(context, StatusCodeEnum.Unauthorized, "Missing token");
42+
return;
43+
}
44+
45+
try
46+
{
47+
// Kiểm tra cache trước khi decode token
48+
var cacheKey = $"claims_{token}";
49+
List<Claim> claims;
50+
if (!_cache.TryGetValue(cacheKey, out claims))
51+
{
52+
var tokenHandler = new JwtSecurityTokenHandler();
53+
var jwtToken = tokenHandler.ReadJwtToken(token);
54+
55+
var roles = jwtToken.Claims
56+
.Where(c => c.Type == "role" || c.Type == "roles")
57+
.Select(c => c.Value)
58+
.ToList();
59+
_logger.LogInformation("Roles extracted from token: {Roles}", string.Join(", ", roles));
60+
61+
if (!roles.Any())
62+
{
63+
await WriteError(context, StatusCodeEnum.Forbidden, "No roles found in token");
64+
return;
65+
}
66+
claims = jwtToken.Claims.ToList();
67+
_cache.Set(cacheKey, claims, TimeSpan.FromMinutes(5));
68+
}
69+
else
70+
{
71+
// Check roles again from cached claims
72+
var roles = claims.Where(c => c.Type == "role" || c.Type == "roles").Select(c => c.Value).ToList();
73+
_logger.LogInformation("Roles extracted from cached claims: {Roles}", string.Join(", ", roles));
74+
if (!roles.Any())
75+
{
76+
_logger.LogWarning("No roles found in cached token claims for request to {Path}", context.Request.Path);
77+
await WriteError(context, StatusCodeEnum.Forbidden, "No roles found in cached token claims");
78+
return;
79+
}
80+
}
81+
82+
// Gắn claims vào context.User
83+
context.User = new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt"));
84+
_logger.LogInformation("Passing RoleCheckMiddleware successfully for {Path}", context.Request.Path);
85+
86+
await _next(context);
87+
}
88+
catch (Exception ex)
89+
{
90+
_logger.LogError(ex, "Error processing token for request to {Path}", context.Request.Path);
91+
await WriteError(context, StatusCodeEnum.Unauthorized, "Invalid token format");
92+
}
93+
}
94+
95+
private async Task WriteError(HttpContext context, StatusCodeEnum status, string message)
96+
{
97+
context.Response.StatusCode = (int)status;
98+
context.Response.ContentType = "application/json";
99+
100+
var response = new ApiResponseModel<object>(status)
101+
{
102+
Result = null,
103+
Message = message
104+
};
105+
106+
await context.Response.WriteAsJsonAsync(response);
107+
}
108+
}
109+
110+
public static class RoleCheckMiddlewareExtensions
111+
{
112+
public static IApplicationBuilder UseRoleCheck(this IApplicationBuilder builder)
113+
{
114+
return builder.UseMiddleware<RoleCheckMiddleware>();
115+
}
116+
}
117+
}

FileService/FileService.Api/Program.cs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using FileService.Service.Interfaces;
2020
using Microsoft.Extensions.FileProviders;
2121
using System.Security.Claims;
22+
using Microsoft.AspNetCore.DataProtection;
2223

2324
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard));
2425

@@ -40,7 +41,6 @@
4041

4142
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
4243
Console.WriteLine($"ASPNETCORE_ENVIRONMENT: {env}");
43-
//builder.Services.Configure<MinioConfig>(builder.Configuration.GetSection("MinioConfig"));
4444

4545
builder.Services.Configure<MongoDbSettings>(builder.Configuration.GetSection("MongoDbSettings"));
4646
builder.Services.Configure<FfmpegSettings>(builder.Configuration.GetSection("FfmpegSettings"));
@@ -84,6 +84,14 @@
8484
options.Password.RequiredUniqueChars = 1;
8585
});
8686

87+
// Add Data Protection with persistence and encryption
88+
builder.Services.AddDataProtection()
89+
.PersistKeysToFileSystem(new DirectoryInfo("/root/.aspnet/DataProtection-Keys"))
90+
.ProtectKeysWithDpapi() // Use DPAPI for Windows compatibility
91+
.SetApplicationName("FileService");
92+
93+
94+
//add authen
8795
builder.Services.AddAuthentication(options =>
8896
{
8997
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
@@ -93,18 +101,34 @@
93101
{
94102
options.TokenValidationParameters = new TokenValidationParameters
95103
{
96-
ValidateIssuer = true,
97-
ValidateAudience = true,
98-
ValidateLifetime = true,
104+
ValidateIssuer = false,
105+
ValidateAudience = false,
106+
ValidateLifetime = false,
99107
ValidateIssuerSigningKey = true,
100108
ValidIssuer = appSettings.Jwt.Issuer,
101-
ValidAudience = appSettings.Jwt.Audience,
109+
// ValidAudience = appSettings.Jwt.Audience,
102110
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appSettings.Jwt.Key)),
103111

104112
// Cấu hình claim để nhận Role
105-
RoleClaimType = ClaimTypes.Role
106-
113+
// RoleClaimType = "roles"
107114
};
115+
options.TokenValidationParameters.RoleClaimType = "roles"; // Khớp với token
116+
});
117+
118+
119+
// Thêm MemoryCache để cache claims
120+
builder.Services.AddMemoryCache();
121+
122+
//add author policies
123+
builder.Services.AddAuthorization(options =>
124+
{
125+
options.AddPolicy("AdminOnly", policy => policy.RequireRole("ADMIN"));
126+
options.AddPolicy("TeacherOrAdmin", policy => policy.RequireRole("TEACHER", "SYS_ADMIN"));
127+
options.AddPolicy("SysAdminOnly", policy => policy.RequireRole("SYS_ADMIN"));
128+
options.AddPolicy("OrgAdminOnly", policy => policy.RequireRole("ORG_ADMIN"));
129+
options.AddPolicy("TeacherOnly", policy => policy.RequireRole("TEACHER"));
130+
options.AddPolicy("UserOnly", policy => policy.RequireRole("USER"));
131+
options.AddPolicy("LoggedInUsers", policy => policy.RequireRole("USER", "TEACHER", "ORG_ADMIN", "SYS_ADMIN"));
108132
});
109133

110134
builder.Services.AddCors(options =>
@@ -164,6 +188,15 @@
164188
options.Limits.MaxRequestBodySize = 6L * 1024 * 1024 * 1024; // 6GB
165189
});
166190

191+
// Configure HTTP client for MinIO with SSL handling
192+
builder.Services.AddHttpClient("MinioClient").ConfigurePrimaryHttpMessageHandler(() =>
193+
{
194+
return new HttpClientHandler
195+
{
196+
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true // Temporary for dev/staging, remove in production
197+
};
198+
});
199+
167200

168201
var app = builder.Build();
169202

@@ -183,10 +216,14 @@
183216

184217
app.UseHttpsRedirection();
185218
app.UseAuthentication();
219+
// Middleware pipeline
220+
//app.UseRoleCheck();
186221
app.UseAuthorization();
187222

188-
app.UseMiddleware<ExceptionMiddleware>();
223+
189224
app.UseMiddleware<AuthenMiddleware>();
225+
app.UseMiddleware<ExceptionMiddleware>();
226+
//app.UseMiddleware<RoleCheckMiddleware>();
190227
app.UseMiddleware<UserContextMiddleware>();
191228

192229
app.MapControllers();

FileService/FileService.Api/appsettings.Development.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"Jwt": {
2424
"Key": "1TjXchw5FloESb63Kc+DFhTARvpWL4jUGCwfGWxuG5SIf/1y/LgJxHnMqaF6A/ij",
2525
"Issuer": "Code Campus",
26-
"Audience": "Code Campus",
26+
//"Audience": "Code Campus",
2727
"AccessTokenExpiresTime": 60,
2828
"RefreshTokenExpiresTime": 300
2929
},

FileService/FileService.Api/appsettings.Staging.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"Jwt": {
2424
"Key": "1TjXchw5FloESb63Kc+DFhTARvpWL4jUGCwfGWxuGSSIf/1y/LgJxHnMqaF6A/ij",
2525
"Issuer": "Code Campus",
26-
"Audience": "Code Campus",
26+
// "Audience": "Code Campus",
2727
"AccessTokenExpiresTime": 60,
2828
"RefreshTokenExpiresTime": 3
2929
},
@@ -43,5 +43,15 @@
4343
"BucketName": "",
4444
"Secure": false
4545
}
46+
},
47+
"Kestrel": {
48+
"Endpoints": {
49+
"Http": {
50+
"Url": "http://+:8082"
51+
},
52+
"Https": {
53+
"Url": "https://+:443"
54+
}
55+
}
4656
}
4757
}

FileService/FileService.Core/ApiModels/AppSettings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class Jwt
2121
{
2222
public string Key { get; set; }
2323
public string Issuer { get; set; }
24-
public string Audience { get; set; }
24+
// public string Audience { get; set; }
2525
public int AccessTokenExpiresTime { get; set; }
2626
public int RefreshTokenExpiresTime { get; set; }
2727
}

FileService/FileService.Core/Enums/StatusCodeEnum.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ namespace FileService.Core.Enums
99
{
1010
public enum StatusCodeEnum
1111
{
12-
[Description("Thành công.")]
13-
Success = 2008200,
1412

1513
[Description("Lỗi hệ thống.")]
1614
Error = 5008201,
@@ -51,13 +49,29 @@ public enum StatusCodeEnum
5149
[Description("Download Interrupted. Please check your internet connection and try again.")]
5250
C05 = 5038214,
5351

54-
[Description("Bad request.")]
55-
BadRequest = 4008215,
5652

5753
[Description("Invalid filter option.")]
5854
InvalidOption = 4008216,
5955

6056
[Description("Unmatched columns found.")]
6157
UnmatchedColumns = 4008217,
58+
59+
[Description("Success")]
60+
Success = 200,
61+
62+
[Description("Bad Request")]
63+
BadRequest = 400,
64+
65+
[Description("Unauthorized")]
66+
Unauthorized = 401,
67+
68+
[Description("Forbidden")]
69+
Forbidden = 403,
70+
71+
[Description("Not Found")]
72+
NotFound = 404,
73+
74+
[Description("Internal Server Error")]
75+
InternalServerError = 500
6276
}
6377
}

docker/docker-compose.services.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ services:
153153
environment:
154154
# cấu hình môi trường ASP.NET
155155
- ASPNETCORE_ENVIRONMENT=staging
156+
- ASPNETCORE_URLS=http://+:8082;https://+:443
157+
- ASPNETCORE_HTTPS_PORT=443
156158
# ---- MongoDB (file-db) ----
157159
- MongoDbSettings__ConnectionStrings="mongodb://${FILE_USERNAME}:${FILE_PASSWORD}@file-db:27017/${FILE_DATABASE}?authSource=${FILE_DATABASE}"
158160
- MongoDbSettings__DatabaseName=${FILE_DATABASE}
@@ -165,6 +167,8 @@ services:
165167
- AppSettings__MinioConfig__Secure=false
166168
ports:
167169
- "8082:8082"
170+
volumes:
171+
- C:\DockerVolumes\DataProtectionKeys:/root/.aspnet/DataProtection-Keys
168172
networks: [ backend ]
169173

170174
gateway-service:

0 commit comments

Comments
 (0)