Endpoint authorization
Tokens are validated for some of the endpoints. Need to add other measures to rule out bogus tokens.
This commit is contained in:
@@ -220,8 +220,9 @@ namespace Icarus.Controllers.Managers
|
|||||||
|
|
||||||
song.SongDirectory = _tempDirectoryRoot;
|
song.SongDirectory = _tempDirectoryRoot;
|
||||||
song.DateCreated = DateTime.Now;
|
song.DateCreated = DateTime.Now;
|
||||||
|
var tempPath = song.SongPath();
|
||||||
|
|
||||||
using (var filestream = new FileStream(song.SongPath(), FileMode.Create))
|
using (var filestream = new FileStream(tempPath, FileMode.Create))
|
||||||
{
|
{
|
||||||
_logger.Info("Saving song to temporary directory");
|
_logger.Info("Saving song to temporary directory");
|
||||||
await songFile.CopyToAsync(filestream);
|
await songFile.CopyToAsync(filestream);
|
||||||
@@ -246,11 +247,22 @@ namespace Icarus.Controllers.Managers
|
|||||||
|
|
||||||
using (var fileStream = new FileStream(filePath, FileMode.Create))
|
using (var fileStream = new FileStream(filePath, FileMode.Create))
|
||||||
{
|
{
|
||||||
var songBytes = await System.IO.File.ReadAllBytesAsync(song.SongPath());
|
var songBytes = await System.IO.File.ReadAllBytesAsync(tempPath);
|
||||||
|
|
||||||
await System.IO.File.WriteAllBytesAsync(filePath, songBytes);
|
try
|
||||||
System.IO.File.Delete(song.SongPath());
|
{
|
||||||
song.SongDirectory = dirMgr.SongDirectory;
|
if (!System.IO.File.Exists(filePath))
|
||||||
|
{
|
||||||
|
await System.IO.File.WriteAllBytesAsync(filePath, songBytes);
|
||||||
|
System.IO.File.Delete(tempPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var msg = ex.Message;
|
||||||
|
_logger.Error($"An error occurred: {msg}");
|
||||||
|
}
|
||||||
|
// song.SongDirectory = dirMgr.SongDirectory;
|
||||||
|
|
||||||
_logger.Info("Song successfully saved to filesystem");
|
_logger.Info("Song successfully saved to filesystem");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,26 @@ using Icarus.Models;
|
|||||||
|
|
||||||
namespace Icarus.Controllers.Managers
|
namespace Icarus.Controllers.Managers
|
||||||
{
|
{
|
||||||
|
#region Classes
|
||||||
|
/**
|
||||||
|
{
|
||||||
|
"scope":"download:songs read:song_details upload:songs delete:songs read:albums read:artists update:songs stream:songs read:genre read:year download:cover_art",
|
||||||
|
"exp":1661616071,
|
||||||
|
"aud":"http://localhost:5002",
|
||||||
|
"iss":"http://localhost:5002",
|
||||||
|
"iat":1661529671
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
public class TokenManager : BaseManager
|
public class TokenManager : BaseManager
|
||||||
{
|
{
|
||||||
#region Fields
|
#region Fields
|
||||||
private string _clientId;
|
private string _clientId;
|
||||||
private string _clientSecret;
|
private string _clientSecret;
|
||||||
private string _privateKeyPath;
|
private string _privateKeyPath;
|
||||||
|
private string _privateKey;
|
||||||
private string _publicKeyPath;
|
private string _publicKeyPath;
|
||||||
|
private string _publicKey;
|
||||||
private string _audience;
|
private string _audience;
|
||||||
private string _grantType;
|
private string _grantType;
|
||||||
private string _url;
|
private string _url;
|
||||||
@@ -70,7 +83,7 @@ namespace Icarus.Controllers.Managers
|
|||||||
|
|
||||||
_logger.Info("Deserializing response");
|
_logger.Info("Deserializing response");
|
||||||
var tokenResult = JsonConvert
|
var tokenResult = JsonConvert
|
||||||
.DeserializeObject<Token>(response.Content);
|
.DeserializeObject<TokenTierOne>(response.Content);
|
||||||
_logger.Info("Response deserialized");
|
_logger.Info("Response deserialized");
|
||||||
|
|
||||||
return new LoginResult
|
return new LoginResult
|
||||||
@@ -84,7 +97,7 @@ namespace Icarus.Controllers.Managers
|
|||||||
|
|
||||||
public LoginResult LogIn(User user)
|
public LoginResult LogIn(User user)
|
||||||
{
|
{
|
||||||
var tokenResult = new Token();
|
var tokenResult = new TokenTierOne();
|
||||||
tokenResult.TokenType = "Jwt";
|
tokenResult.TokenType = "Jwt";
|
||||||
|
|
||||||
var privateKey = ReadKeyContent(_privateKeyPath).Result;
|
var privateKey = ReadKeyContent(_privateKeyPath).Result;
|
||||||
@@ -110,6 +123,50 @@ namespace Icarus.Controllers.Managers
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsTokenValid(string scope, string accessToken)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
var token = DecodeToken(accessToken);
|
||||||
|
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = (!token.TokenExpired() && token.ContainsScope(scope)) ? true : false;
|
||||||
|
|
||||||
|
// What would make a token valid?
|
||||||
|
// 1. The expiration date must be before the current date
|
||||||
|
// 2. The desired scope must be part of the scopes within the access token
|
||||||
|
// 3. Must be able to be decoded
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Token? DecodeToken(string accessToken)
|
||||||
|
{
|
||||||
|
var rsaParams = GetRSAPublic(_publicKey);
|
||||||
|
Token tok = null;
|
||||||
|
|
||||||
|
using (var rsa = new RSACryptoServiceProvider())
|
||||||
|
{
|
||||||
|
rsa.ImportParameters(rsaParams);
|
||||||
|
|
||||||
|
IJsonSerializer serializer = new JsonNetSerializer();
|
||||||
|
IDateTimeProvider provider = new UtcDateTimeProvider();
|
||||||
|
IJwtValidator validator = new JwtValidator(serializer, provider);
|
||||||
|
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
|
||||||
|
var algorithm = new JWT.Algorithms.RS256Algorithm(rsa);
|
||||||
|
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
|
||||||
|
|
||||||
|
var json = decoder.Decode(accessToken);
|
||||||
|
tok = JsonConvert.DeserializeObject<Token>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return tok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private string AllScopes()
|
private string AllScopes()
|
||||||
{
|
{
|
||||||
@@ -269,6 +326,8 @@ namespace Icarus.Controllers.Managers
|
|||||||
_url = $"https://{_config["Auth0:Domain"]}";
|
_url = $"https://{_config["Auth0:Domain"]}";
|
||||||
_privateKeyPath = _config["RSAKeys:PrivateKeyPath"];
|
_privateKeyPath = _config["RSAKeys:PrivateKeyPath"];
|
||||||
_publicKeyPath = _config["RSAKeys:PublicKeyPath"];
|
_publicKeyPath = _config["RSAKeys:PublicKeyPath"];
|
||||||
|
_privateKey = System.IO.File.ReadAllText(_privateKeyPath);
|
||||||
|
_publicKey = System.IO.File.ReadAllText(_publicKeyPath);
|
||||||
|
|
||||||
PrintCredentials();
|
PrintCredentials();
|
||||||
}
|
}
|
||||||
@@ -299,7 +358,7 @@ namespace Icarus.Controllers.Managers
|
|||||||
[JsonProperty("grant_type")]
|
[JsonProperty("grant_type")]
|
||||||
public string GrantType { get; set; }
|
public string GrantType { get; set; }
|
||||||
}
|
}
|
||||||
private class Token
|
private class TokenTierOne
|
||||||
{
|
{
|
||||||
[JsonProperty("access_token")]
|
[JsonProperty("access_token")]
|
||||||
public string AccessToken { get; set; }
|
public string AccessToken { get; set; }
|
||||||
@@ -310,4 +369,5 @@ namespace Icarus.Controllers.Managers
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,10 +43,40 @@ namespace Icarus.Controllers.V1
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
private string ParseBearerTokenFromHeader()
|
||||||
|
{
|
||||||
|
var token = string.Empty;
|
||||||
|
const string tokenType = "Bearer";
|
||||||
|
|
||||||
|
var req = Request;
|
||||||
|
var auth = req.Headers.Authorization;
|
||||||
|
var val = auth.ToString();
|
||||||
|
|
||||||
|
if (val.Contains(tokenType) && val.Split(" ").Count() > 1)
|
||||||
|
{
|
||||||
|
var split = val.Split(" ");
|
||||||
|
token = split[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
#region HTTP Endpoints
|
||||||
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize("read:song_details")]
|
// [Authorize("read:song_details")]
|
||||||
public IActionResult Get()
|
public IActionResult Get()
|
||||||
{
|
{
|
||||||
|
var token = ParseBearerTokenFromHeader();
|
||||||
|
var tokMgr = new TokenManager(_config);
|
||||||
|
|
||||||
|
if (!tokMgr.IsTokenValid("read:song_details", token))
|
||||||
|
{
|
||||||
|
return StatusCode(401, "Not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
List<Song> songs = new List<Song>();
|
List<Song> songs = new List<Song>();
|
||||||
Console.WriteLine("Attemtping to retrieve songs");
|
Console.WriteLine("Attemtping to retrieve songs");
|
||||||
_logger.LogInformation("Attempting to retrieve songs");
|
_logger.LogInformation("Attempting to retrieve songs");
|
||||||
@@ -100,5 +130,7 @@ namespace Icarus.Controllers.V1
|
|||||||
|
|
||||||
return Ok(songRes);
|
return Ok(songRes);
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,27 @@ namespace Icarus.Controllers.V1
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
private string ParseBearerTokenFromHeader()
|
||||||
|
{
|
||||||
|
var token = string.Empty;
|
||||||
|
const string tokenType = "Bearer";
|
||||||
|
const string otherTokenType = "Jwt";
|
||||||
|
|
||||||
|
var req = Request;
|
||||||
|
var auth = req.Headers.Authorization;
|
||||||
|
var val = auth.ToString();
|
||||||
|
|
||||||
|
if (val.Contains(tokenType) && val.Split(" ").Count() > 1 ||
|
||||||
|
val.Contains(otherTokenType) && val.Split(" ").Count() > 1)
|
||||||
|
{
|
||||||
|
var split = val.Split(" ");
|
||||||
|
token = split[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("download/{id}")]
|
[HttpGet("download/{id}")]
|
||||||
[Route("private-scoped")]
|
[Route("private-scoped")]
|
||||||
@@ -112,9 +133,17 @@ namespace Icarus.Controllers.V1
|
|||||||
//
|
//
|
||||||
[HttpPost("upload/with/data")]
|
[HttpPost("upload/with/data")]
|
||||||
[Route("private-scoped")]
|
[Route("private-scoped")]
|
||||||
[Authorize("upload:songs")]
|
// [Authorize("upload:songs")]
|
||||||
public async Task<IActionResult> Post ([FromForm] UploadSongWithDataForm up)
|
public async Task<IActionResult> Post ([FromForm] UploadSongWithDataForm up)
|
||||||
{
|
{
|
||||||
|
var token = ParseBearerTokenFromHeader();
|
||||||
|
var tokMgr = new TokenManager(_config);
|
||||||
|
|
||||||
|
if (!tokMgr.IsTokenValid("upload:songs", token))
|
||||||
|
{
|
||||||
|
return StatusCode(401, "Not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (up.SongData.Length > 0 && up.CoverArtData.Length > 0 && !string.IsNullOrEmpty(up.SongFile))
|
if (up.SongData.Length > 0 && up.CoverArtData.Length > 0 && !string.IsNullOrEmpty(up.SongFile))
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
using Icarus.Models;
|
using Icarus.Models;
|
||||||
|
using Icarus.Controllers.Managers;
|
||||||
using Icarus.Database.Contexts;
|
using Icarus.Database.Contexts;
|
||||||
|
|
||||||
namespace Icarus.Controllers.V1
|
namespace Icarus.Controllers.V1
|
||||||
@@ -42,11 +43,40 @@ namespace Icarus.Controllers.V1
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
private string ParseBearerTokenFromHeader()
|
||||||
|
{
|
||||||
|
var token = string.Empty;
|
||||||
|
const string tokenType = "Bearer";
|
||||||
|
|
||||||
|
var req = Request;
|
||||||
|
var auth = req.Headers.Authorization;
|
||||||
|
var val = auth.ToString();
|
||||||
|
|
||||||
|
if (val.Contains(tokenType) && val.Split(" ").Count() > 1)
|
||||||
|
{
|
||||||
|
var split = val.Split(" ");
|
||||||
|
token = split[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
#region HTTP endpoints
|
#region HTTP endpoints
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
[Authorize("stream:songs")]
|
// [Authorize("stream:songs")]
|
||||||
public async Task<IActionResult> Get(int id)
|
public async Task<IActionResult> Get(int id)
|
||||||
{
|
{
|
||||||
|
var token = ParseBearerTokenFromHeader();
|
||||||
|
var tokMgr = new TokenManager(_config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
if (!tokMgr.IsTokenValid("stream:songs", token))
|
||||||
|
{
|
||||||
|
return StatusCode(401, "Not allowed");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
var context = new SongContext(_config.GetConnectionString("DefaultConnection"));
|
var context = new SongContext(_config.GetConnectionString("DefaultConnection"));
|
||||||
|
|
||||||
var song = context.Songs.FirstOrDefault(sng => sng.SongID == id);
|
var song = context.Songs.FirstOrDefault(sng => sng.SongID == id);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Icarus.Models
|
||||||
|
{
|
||||||
|
public class Token
|
||||||
|
{
|
||||||
|
#region Properties
|
||||||
|
[JsonProperty("scope")]
|
||||||
|
public string Scope { get; set; }
|
||||||
|
[JsonProperty("exp")]
|
||||||
|
public int Expiration { get; set; }
|
||||||
|
[JsonProperty("aud")]
|
||||||
|
public string Audience { get; set; }
|
||||||
|
[JsonProperty("iss")]
|
||||||
|
public string Issuer { get; set; }
|
||||||
|
[JsonProperty("iat")]
|
||||||
|
public int Issued { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
public bool TokenExpired()
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
var currentDate = DateTime.Now;
|
||||||
|
|
||||||
|
var currentDateInSeconds = Math.Floor((currentDate - DateTime.UnixEpoch).TotalSeconds);
|
||||||
|
|
||||||
|
result = (currentDateInSeconds >= Expiration) ? true : false;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsScope(string desiredScope)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
result = Scope.Contains(desiredScope);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -151,6 +151,7 @@ namespace Icarus
|
|||||||
var domain = $"https://{auth_id}/";
|
var domain = $"https://{auth_id}/";
|
||||||
var audience = Configuration["Auth0:ApiIdentifier"];
|
var audience = Configuration["Auth0:ApiIdentifier"];
|
||||||
|
|
||||||
|
/**
|
||||||
services
|
services
|
||||||
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||||
.AddJwtBearer(options =>
|
.AddJwtBearer(options =>
|
||||||
@@ -159,7 +160,6 @@ namespace Icarus
|
|||||||
options.Audience = audience;
|
options.Audience = audience;
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
services.AddAuthorization(options =>
|
services.AddAuthorization(options =>
|
||||||
{
|
{
|
||||||
options.AddPolicy("download:songs", policy =>
|
options.AddPolicy("download:songs", policy =>
|
||||||
|
|||||||
Reference in New Issue
Block a user