diff --git a/Controllers/Managers/SongManager.cs b/Controllers/Managers/SongManager.cs index 96e0c20..be7097a 100644 --- a/Controllers/Managers/SongManager.cs +++ b/Controllers/Managers/SongManager.cs @@ -220,8 +220,9 @@ namespace Icarus.Controllers.Managers song.SongDirectory = _tempDirectoryRoot; 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"); await songFile.CopyToAsync(filestream); @@ -246,11 +247,22 @@ namespace Icarus.Controllers.Managers 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); - System.IO.File.Delete(song.SongPath()); - song.SongDirectory = dirMgr.SongDirectory; + try + { + 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"); } diff --git a/Controllers/Managers/TokenManager.cs b/Controllers/Managers/TokenManager.cs index 8944d24..c2ca547 100644 --- a/Controllers/Managers/TokenManager.cs +++ b/Controllers/Managers/TokenManager.cs @@ -20,13 +20,26 @@ using Icarus.Models; 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 { #region Fields private string _clientId; private string _clientSecret; private string _privateKeyPath; + private string _privateKey; private string _publicKeyPath; + private string _publicKey; private string _audience; private string _grantType; private string _url; @@ -70,7 +83,7 @@ namespace Icarus.Controllers.Managers _logger.Info("Deserializing response"); var tokenResult = JsonConvert - .DeserializeObject(response.Content); + .DeserializeObject(response.Content); _logger.Info("Response deserialized"); return new LoginResult @@ -84,7 +97,7 @@ namespace Icarus.Controllers.Managers public LoginResult LogIn(User user) { - var tokenResult = new Token(); + var tokenResult = new TokenTierOne(); tokenResult.TokenType = "Jwt"; 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(json); + } + + + return tok; + } + private string AllScopes() { @@ -269,6 +326,8 @@ namespace Icarus.Controllers.Managers _url = $"https://{_config["Auth0:Domain"]}"; _privateKeyPath = _config["RSAKeys:PrivateKeyPath"]; _publicKeyPath = _config["RSAKeys:PublicKeyPath"]; + _privateKey = System.IO.File.ReadAllText(_privateKeyPath); + _publicKey = System.IO.File.ReadAllText(_publicKeyPath); PrintCredentials(); } @@ -299,7 +358,7 @@ namespace Icarus.Controllers.Managers [JsonProperty("grant_type")] public string GrantType { get; set; } } - private class Token + private class TokenTierOne { [JsonProperty("access_token")] public string AccessToken { get; set; } @@ -310,4 +369,5 @@ namespace Icarus.Controllers.Managers } #endregion } + #endregion } diff --git a/Controllers/v1/SongController.cs b/Controllers/v1/SongController.cs index 82a72d6..77d755b 100644 --- a/Controllers/v1/SongController.cs +++ b/Controllers/v1/SongController.cs @@ -43,10 +43,40 @@ namespace Icarus.Controllers.V1 #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] - [Authorize("read:song_details")] + // [Authorize("read:song_details")] public IActionResult Get() { + var token = ParseBearerTokenFromHeader(); + var tokMgr = new TokenManager(_config); + + if (!tokMgr.IsTokenValid("read:song_details", token)) + { + return StatusCode(401, "Not allowed"); + } + List songs = new List(); Console.WriteLine("Attemtping to retrieve songs"); _logger.LogInformation("Attempting to retrieve songs"); @@ -100,5 +130,7 @@ namespace Icarus.Controllers.V1 return Ok(songRes); } + #endregion + #endregion } } diff --git a/Controllers/v1/SongDataController.cs b/Controllers/v1/SongDataController.cs index 8b66acc..deb37d9 100644 --- a/Controllers/v1/SongDataController.cs +++ b/Controllers/v1/SongDataController.cs @@ -47,6 +47,27 @@ namespace Icarus.Controllers.V1 #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}")] [Route("private-scoped")] @@ -112,9 +133,17 @@ namespace Icarus.Controllers.V1 // [HttpPost("upload/with/data")] [Route("private-scoped")] - [Authorize("upload:songs")] + // [Authorize("upload:songs")] public async Task Post ([FromForm] UploadSongWithDataForm up) { + var token = ParseBearerTokenFromHeader(); + var tokMgr = new TokenManager(_config); + + if (!tokMgr.IsTokenValid("upload:songs", token)) + { + return StatusCode(401, "Not allowed"); + } + try { if (up.SongData.Length > 0 && up.CoverArtData.Length > 0 && !string.IsNullOrEmpty(up.SongFile)) diff --git a/Controllers/v1/SongStreamController.cs b/Controllers/v1/SongStreamController.cs index a15607d..01a8c44 100644 --- a/Controllers/v1/SongStreamController.cs +++ b/Controllers/v1/SongStreamController.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Icarus.Models; +using Icarus.Controllers.Managers; using Icarus.Database.Contexts; namespace Icarus.Controllers.V1 @@ -42,11 +43,40 @@ namespace Icarus.Controllers.V1 #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 [HttpGet("{id}")] - [Authorize("stream:songs")] + // [Authorize("stream:songs")] public async Task 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 song = context.Songs.FirstOrDefault(sng => sng.SongID == id); diff --git a/Models/Token.cs b/Models/Token.cs new file mode 100644 index 0000000..b1d4214 --- /dev/null +++ b/Models/Token.cs @@ -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 + } +} \ No newline at end of file diff --git a/Startup.cs b/Startup.cs index 13323a9..05fb68d 100644 --- a/Startup.cs +++ b/Startup.cs @@ -151,6 +151,7 @@ namespace Icarus var domain = $"https://{auth_id}/"; var audience = Configuration["Auth0:ApiIdentifier"]; + /** services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => @@ -159,7 +160,6 @@ namespace Icarus options.Audience = audience; }); - /** services.AddAuthorization(options => { options.AddPolicy("download:songs", policy =>