多角色账号按 All > College > Class > Self 自动取最高权限。
学院管理员限制在所属学院。 辅导员通过稳定账号 ID 绑定行政班,避免重名串班。 教师只能访问本人档案、授课课程和所授课学生。 学生只能访问本人档案及所在班级课程。 教师/学生角色会自动校验并绑定工号或学号档案。 超级管理员可在用户页面调整角色、学院、工号/学号,并预览生效后的数据范围。
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
using System.Security.Claims;
|
||||
using Jiaowu.Api.Domain.Identity;
|
||||
|
||||
namespace Jiaowu.Api.Infrastructure.Auth;
|
||||
|
||||
public interface ICurrentUserDataScope
|
||||
{
|
||||
CurrentUserScope Current { get; }
|
||||
}
|
||||
|
||||
public sealed record CurrentUserScope(
|
||||
Guid UserId,
|
||||
string? DisplayName,
|
||||
Guid? CollegeId,
|
||||
DataScope Scope,
|
||||
IReadOnlySet<string> Roles)
|
||||
{
|
||||
public bool IsInRole(string role) => Roles.Contains(role);
|
||||
|
||||
public bool CanAccessCollege(Guid collegeId) =>
|
||||
Scope == DataScope.All ||
|
||||
Scope == DataScope.College && CollegeId == collegeId;
|
||||
|
||||
public Guid? RestrictedCollegeId => Scope switch
|
||||
{
|
||||
DataScope.All => null,
|
||||
DataScope.College => CollegeId ?? Guid.Empty,
|
||||
_ => Guid.Empty
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class CurrentUserDataScope(IHttpContextAccessor httpContextAccessor)
|
||||
: ICurrentUserDataScope
|
||||
{
|
||||
public CurrentUserScope Current
|
||||
{
|
||||
get
|
||||
{
|
||||
var principal = httpContextAccessor.HttpContext?.User;
|
||||
var roles = new HashSet<string>(
|
||||
principal?.FindAll(ClaimTypes.Role).Select(x => x.Value) ?? [],
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return new CurrentUserScope(
|
||||
ParseGuid(principal?.FindFirstValue(ClaimTypes.NameIdentifier)),
|
||||
principal?.FindFirstValue("display_name") ??
|
||||
principal?.FindFirstValue(ClaimTypes.Name),
|
||||
ParseNullableGuid(principal?.FindFirstValue("college_id")),
|
||||
EffectiveDataScopeResolver.Resolve(roles),
|
||||
roles);
|
||||
}
|
||||
}
|
||||
|
||||
private static Guid ParseGuid(string? value) =>
|
||||
Guid.TryParse(value, out var id) ? id : Guid.Empty;
|
||||
|
||||
private static Guid? ParseNullableGuid(string? value) =>
|
||||
Guid.TryParse(value, out var id) ? id : null;
|
||||
}
|
||||
|
||||
public static class EffectiveDataScopeResolver
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, DataScope> RoleScopes =
|
||||
new Dictionary<string, DataScope>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[SystemRoles.SuperAdmin] = DataScope.All,
|
||||
[SystemRoles.AcademicAdmin] = DataScope.All,
|
||||
[SystemRoles.Leader] = DataScope.All,
|
||||
[SystemRoles.CollegeAdmin] = DataScope.College,
|
||||
[SystemRoles.Counselor] = DataScope.Class,
|
||||
[SystemRoles.Teacher] = DataScope.Self,
|
||||
[SystemRoles.Student] = DataScope.Self
|
||||
};
|
||||
|
||||
public static DataScope Resolve(IEnumerable<string> roles)
|
||||
{
|
||||
var effectiveScope = DataScope.Self;
|
||||
foreach (var role in roles)
|
||||
{
|
||||
if (RoleScopes.TryGetValue(role, out var roleScope) &&
|
||||
roleScope > effectiveScope)
|
||||
{
|
||||
effectiveScope = roleScope;
|
||||
}
|
||||
}
|
||||
|
||||
return effectiveScope;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,8 @@ public sealed class TokenService(IOptions<JwtOptions> options) : ITokenService
|
||||
new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
|
||||
new(JwtRegisteredClaimNames.UniqueName, user.UserName ?? string.Empty),
|
||||
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new(ClaimTypes.Name, user.DisplayName)
|
||||
new(ClaimTypes.Name, user.DisplayName),
|
||||
new("display_name", user.DisplayName)
|
||||
};
|
||||
|
||||
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
|
||||
|
||||
Reference in New Issue
Block a user