Skip to content

Commit

Permalink
feat: use atc center internal api
Browse files Browse the repository at this point in the history
  • Loading branch information
xfoxfu committed Oct 9, 2024
1 parent 44f4711 commit 1ca75da
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 18 deletions.
32 changes: 14 additions & 18 deletions Net.Vatprc.Uniapi/Controllers/SectorController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Mvc;
using Net.Vatprc.Uniapi.Utils;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Authorization;

namespace Net.Vatprc.Uniapi.Controllers;

Expand All @@ -11,7 +12,8 @@ namespace Net.Vatprc.Uniapi.Controllers;
[ApiController, Route("api/sectors")]
public class SectorController(
VATPRCContext DbContext,
VatsimService VatsimService) : ControllerBase
ILogger<SectorController> Logger,
VatprcAtcService VatprcAtcService) : ControllerBase
{
public record SectorPermissionResponse
{
Expand All @@ -28,32 +30,26 @@ public SectorPermissionResponse(bool hasPermission)
[
"Online Permission",
"ATC Student",
"S3 Controller",
"S2 Controller",
"S1 Controller",
"C1 Controller",
"C3 Controller",
];

protected IEnumerable<VatprcAtcService.Role> FlattenRoles(IEnumerable<VatprcAtcService.Role> Roles)
{
return Roles.SelectMany(r => FlattenRoles(r.AllSuperroles)).Concat(Roles);
}

[HttpGet("current/permission")]
[AllowAnonymous]
public async Task<SectorPermissionResponse> GetPermission()
{
var user = await DbContext.User.FindAsync(this.GetUserId()) ??
throw new ApiError.UserNotFound(this.GetUserId());

// FIXME: This is a temporary solution to allow the user to access the sector
if (user.Cid == "1638882")
{
return new SectorPermissionResponse(true);
}
var roles = await VatprcAtcService.GetUserRole(user.Cid);
var flattenRoles = FlattenRoles(roles);
Logger.LogInformation("User {Cid} has roles {Roles}", user.Cid,
string.Join(", ", flattenRoles.Select(r => r.Name)));
var hasPermission = flattenRoles.Any(r => AllowedRoles.Contains(r.Name));

var controllers = await VatsimService.GetAtcList();
var atc = controllers.FirstOrDefault(c => c.Id.ToString() == user.Cid);
if (atc == null)
{
return new SectorPermissionResponse(false);
}
var hasPermission = atc.Roles.Any(r => AllowedRoles.Contains(r.Name));
return new SectorPermissionResponse(hasPermission);
}
}
1 change: 1 addition & 0 deletions Net.Vatprc.Uniapi/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ error message example.

RudiMetarService.ConfigureOn(builder);
VatsimService.ConfigureOn(builder);
VatprcAtcService.ConfigureOn(builder);

var app = builder.Build();

Expand Down
96 changes: 96 additions & 0 deletions Net.Vatprc.Uniapi/Services/VatprcAtcService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using Flurl;
using Flurl.Http;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace Net.Vatprc.Uniapi.Services;

public class VatprcAtcService(IOptions<VatprcAtcService.Option> Options,
IOptions<TokenService.Option> TokenOptions)
{
public static WebApplicationBuilder ConfigureOn(WebApplicationBuilder builder)
{
builder.Services.Configure<Option>(builder.Configuration.GetSection(Option.LOCATION));
builder.Services.ConfigureOptions<OptionConfigure>();
builder.Services.AddSingleton<VatprcAtcService>();
return builder;
}

public class Option
{
public const string LOCATION = "Authentication:Internal:VatprcAtcService";

public required string Endpoint { get; set; }

public required string TokenAudience { get; set; }

public required string PrivateKey { get; set; }

public required string PublicKey { get; set; }

public SecurityKey SecurityKey { get; set; } = null!;

public SigningCredentials Credentials { get; set; } = null!;
}

public class OptionConfigure : IConfigureOptions<Option>
{
public void Configure(Option opts)
{
var key = ECDsa.Create();
key.ImportFromPem(opts.PrivateKey);
opts.SecurityKey = new ECDsaSecurityKey(key);
opts.Credentials = new(opts.SecurityKey, opts.SecurityKey.KeySize switch
{
256 => SecurityAlgorithms.EcdsaSha256,
384 => SecurityAlgorithms.EcdsaSha384,
521 => SecurityAlgorithms.EcdsaSha512,
_ => throw new NotSupportedException(),
});
}
}

public async Task<IEnumerable<Role>> GetUserRole(string cid)
{
var claims = new List<Claim>
{
new (JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
new (JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var token = new JwtSecurityToken(
issuer: TokenOptions.Value.Issuer,
audience: Options.Value.TokenAudience,
expires: DateTime.Now.Add(TimeSpan.FromMinutes(5)),
notBefore: DateTime.Now,
claims: claims,
signingCredentials: Options.Value.Credentials);
var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
return await Options.Value.Endpoint
.AppendPathSegments("v1/internal/users/", cid, "/roles")
.WithHeader("User-Agent", UniapiUserAgent)
.WithHeader("Token", tokenStr)
.GetJsonAsync<IEnumerable<Role>>() ??
throw new Exception("Unexpected null on fetch vatprc atcapi data");
}

public class Role
{
[JsonPropertyName("id")]
public long Id { get; set; }

[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;

[JsonPropertyName("expiration_time")]
public DateTimeOffset? ExpirationTime { get; set; }

[JsonPropertyName("all_superroles")]
public Role[] AllSuperroles { get; set; } = [];
}
}

0 comments on commit 1ca75da

Please sign in to comment.