From 3dae392872e13059597c82c983252ee4b18e2295 Mon Sep 17 00:00:00 2001 From: "Dmitry A. Grechka" Date: Fri, 2 Sep 2022 20:47:18 +0300 Subject: [PATCH] Create codeql-analysis.yml (#4) * Create codeql-analysis.yml * Adding inputs validation * permitting - and _ in the identifiers --- .github/workflows/codeql-analysis.yml | 72 +++++++ Controllers/PetCardsController.cs | 175 +++++++++++------ Controllers/PetPhotosController.cs | 270 ++++++++++++++++---------- Scripts/create_kashtanka_keyspace.cql | 2 +- Security.cs | 31 +++ Storage/CassandraStorage.cs | 63 +++--- Storage/ICardStorage.cs | 11 +- Storage/IPhotoStorage.cs | 21 +- Storage/MemoryTestStorage.cs | 33 ++-- 9 files changed, 450 insertions(+), 228 deletions(-) create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 Security.cs diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..dfecc36 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,72 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '27 4 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/Controllers/PetCardsController.cs b/Controllers/PetCardsController.cs index 3f9e836..01dd4ae 100644 --- a/Controllers/PetCardsController.cs +++ b/Controllers/PetCardsController.cs @@ -6,6 +6,7 @@ using PatCardStorageAPI.Storage; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; +using CardStorageRestAPI; // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 @@ -23,118 +24,164 @@ public PetCardsController(ICardStorage storage) // GET /pet911ru/rf123 [EnableCors] - [HttpGet("{ns}/{localID}")] - public async Task> Get(string ns, string localID, [FromQuery] bool includeSensitiveData = false) + [HttpGet("{nsStr}/{localIDstr}")] + public async Task> Get(string nsStr, string localIDstr, [FromQuery] bool includeSensitiveData = false) { try { - Trace.TraceInformation($"Getting card for {ns}/{localID}"); - var result = await this.storage.GetPetCardAsync(ns, localID); - if (result == null) - { - Trace.TraceInformation($"card for {ns}/{localID} not found"); - return NotFound(); - } - else + // verifying inputs + AsciiIdentifier ns = new(nsStr); + AsciiIdentifier localID = new(localIDstr); + + try { - Trace.TraceInformation($"Successfully retrieved card for {ns}/{localID}"); - if (!includeSensitiveData) + + Trace.TraceInformation($"Getting card for {ns}/{localID}"); + var result = await this.storage.GetPetCardAsync(ns, localID); + if (result == null) { - Trace.TraceInformation($"Wiping out sensitive data for {ns}/{localID}"); - var contacts = result.ContactInfo; - if (contacts != null) { - contacts.Email = new string[0]; - contacts.Name = ""; - contacts.Tel = new string[0]; - contacts.Website = new string[0]; - } + Trace.TraceInformation($"card for {ns}/{localID} not found"); + return NotFound(); } else { - Trace.TraceInformation($"Returning sensitive data to the client for {ns}/{localID}"); + Trace.TraceInformation($"Successfully retrieved card for {ns}/{localID}"); + if (!includeSensitiveData) + { + Trace.TraceInformation($"Wiping out sensitive data for {ns}/{localID}"); + var contacts = result.ContactInfo; + if (contacts != null) + { + contacts.Email = new string[0]; + contacts.Name = ""; + contacts.Tel = new string[0]; + contacts.Website = new string[0]; + } + } + else + { + Trace.TraceInformation($"Returning sensitive data to the client for {ns}/{localID}"); + } + return new ActionResult(result); } - return new ActionResult(result); + } + catch (Exception err) + { + Trace.TraceError($"Except card for {ns}/{localID}: {err}"); + return StatusCode(500, err.ToString()); } } - catch (Exception err) - { - Trace.TraceError($"Except card for {ns}/{localID}: {err}"); - return StatusCode(500, err.ToString()); + catch (ArgumentException ae) { + Trace.TraceError(ae.ToString()); + return BadRequest(ae); } } - [HttpPut("{ns}/{localID}/features/{featuresIdent}")] - public async Task PutFeatures(string ns, string localID, string featuresIdent, [FromBody] JsonPoco.FeaturesPOCO features) + [HttpPut("{nsStr}/{localIDstr}/features/{featuresIdentStr}")] + public async Task PutFeatures(string nsStr, string localIDstr, string featuresIdentStr, [FromBody] JsonPoco.FeaturesPOCO features) { try { - Trace.TraceInformation($"Setting features {featuresIdent} for {ns}/{localID}"); - await this.storage.SetCardFeatureVectorAsync(ns, localID, featuresIdent, features.Features); - Trace.TraceInformation($"Successfully set features {featuresIdent} for {ns}/{localID}"); - return Ok(); + // verifying inputs + AsciiIdentifier ns = new(nsStr); + AsciiIdentifier localID = new(localIDstr); + AsciiIdentifier featuresIdent = new(featuresIdentStr); + try + { + Trace.TraceInformation($"Setting features {featuresIdent} for {ns}/{localID}"); + await this.storage.SetCardFeatureVectorAsync(ns, localID, featuresIdent, features.Features); + Trace.TraceInformation($"Successfully set features {featuresIdent} for {ns}/{localID}"); + return Ok(); + } + catch (Exception err) + { + Trace.TraceError($"Except card for {ns}/{localID}: {err}"); + return StatusCode(500, err.ToString()); + } } - catch (Exception err) - { - Trace.TraceError($"Except card for {ns}/{localID}: {err}"); - return StatusCode(500, err.ToString()); + catch (ArgumentException ae) { + Trace.TraceError(ae.ToString()); + return BadRequest(ae); } } - // PUT - [HttpPut("{ns}/{localID}")] + [HttpPut("{nsStr}/{localIDstr}")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task Put(string ns, string localID, [FromBody] PetCard value) + public async Task Put(string nsStr, string localIDstr, [FromBody] PetCard value) { try { - Trace.TraceInformation($"Storing {ns}/{localID} into storage"); - var res = await this.storage.SetPetCardAsync(ns, localID, value); - if (res) + // verifying inputs + AsciiIdentifier ns = new(nsStr); + AsciiIdentifier localID = new(localIDstr); + + try { - Trace.TraceInformation($"Stored {ns}/{localID}"); - return CreatedAtAction(nameof(Get),new { ns= ns, localID = localID },null); + Trace.TraceInformation($"Storing {ns}/{localID} into storage"); + var res = await this.storage.SetPetCardAsync(ns, localID, value); + if (res) + { + Trace.TraceInformation($"Stored {ns}/{localID}"); + return CreatedAtAction(nameof(Get),new { ns= ns, localID = localID },null); + } + else + { + Trace.TraceWarning($"Card {ns}/{localID} already exists"); + return Conflict(); + } } - else + catch (Exception err) { - Trace.TraceWarning($"Card {ns}/{localID} already exists"); - return Conflict(); + Trace.TraceError($"Exception while storing {ns}/{localID}: {err}"); + return StatusCode(500, err.ToString()); } } - catch (Exception err) + catch (ArgumentException ae) { - Trace.TraceError($"Exception while storing {ns}/{localID}: {err}"); - return StatusCode(500, err.ToString()); + Trace.TraceError(ae.ToString()); + return BadRequest(ae); } - } // DELETE /pet911ru/rf123 - [HttpDelete("{ns}/{localID}")] - public async Task Delete(string ns, string localID) + [HttpDelete("{nsStr}/{localIDstr}")] + public async Task Delete(string nsStr, string localIDstr) { try { - Trace.TraceInformation($"Received delete request for {ns}/{localID}"); - var res = await this.storage.DeletePetCardAsync(ns, localID); - if (res) + // verifying inputs + AsciiIdentifier ns = new(nsStr); + AsciiIdentifier localID = new(localIDstr); + + try { - Trace.TraceInformation($"Successfully deleted {ns}/{localID} from storage"); - return Ok(); + Trace.TraceInformation($"Received delete request for {ns}/{localID}"); + var res = await this.storage.DeletePetCardAsync(ns, localID); + if (res) + { + Trace.TraceInformation($"Successfully deleted {ns}/{localID} from storage"); + return Ok(); + } + else + { + Trace.TraceError($"Failed to delete {ns}/{localID}"); + return StatusCode(500); + } } - else + catch (Exception err) { - Trace.TraceError($"Failed to delete {ns}/{localID}"); - return StatusCode(500); + Trace.TraceError($"Exception while deleting {ns}/{localID}: {err}"); + return StatusCode(500, err.ToString()); } } - catch (Exception err) + catch (ArgumentException ae) { - Trace.TraceError($"Exception while deleting {ns}/{localID}: {err}"); - return StatusCode(500, err.ToString()); + Trace.TraceError(ae.ToString()); + return BadRequest(ae); } } } diff --git a/Controllers/PetPhotosController.cs b/Controllers/PetPhotosController.cs index ef59ded..42eb29f 100644 --- a/Controllers/PetPhotosController.cs +++ b/Controllers/PetPhotosController.cs @@ -7,6 +7,7 @@ using PatCardStorageAPI.Storage; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; +using CardStorageRestAPI; // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 @@ -32,60 +33,76 @@ public PetPhotosController(IPhotoStorage storage) /// In the order of preference. comma separated identifications /// [EnableCors] - [HttpGet("{ns}/{localID}/{imNum}")] - public async Task GetImage(string ns, string localID, int imNum, [FromQuery]string? preferableProcessingsStr) { + [HttpGet("{nsStr}/{localIDstr}/{imNum}")] + public async Task GetImage(string nsStr, string localIDstr, int imNum, [FromQuery]string? preferableProcessingsStr) { try { - Trace.TraceInformation($"Getting raw photo #{imNum} for {ns}/{localID}"); - var photo = await this.storage.GetOriginalPhotoAsync(ns, localID, imNum); - if (photo == null) + // verifying inputs + AsciiIdentifier ns = new(nsStr); + AsciiIdentifier localID = new(localIDstr); + + try { - Trace.TraceInformation($"photo #{imNum} for {ns}/{localID} does not exist. Returning not found"); - return NotFound(); - } - else - { - Trace.TraceInformation($"Extracted photo #{imNum} for {ns}/{localID} from storage. uuid: {photo.Uuid}"); - string? mimeType = null; - byte[]? imageContent = null; - if(!string.IsNullOrEmpty(preferableProcessingsStr)) + Trace.TraceInformation($"Getting raw photo #{imNum} for {ns}/{localID}"); + var photo = await this.storage.GetOriginalPhotoAsync(ns, localID, imNum); + if (photo == null) { - - foreach (var processingIdent in preferableProcessingsStr.Split(',').Select(s => s.Trim())) + Trace.TraceInformation($"photo #{imNum} for {ns}/{localID} does not exist. Returning not found"); + return NotFound(); + } + else + { + Trace.TraceInformation($"Extracted photo #{imNum} for {ns}/{localID} from storage. uuid: {photo.Uuid}"); + string? mimeType = null; + byte[]? imageContent = null; + if(!string.IsNullOrEmpty(preferableProcessingsStr)) { - Trace.TraceInformation($"Checking existence of {processingIdent} for {photo.Uuid}"); - var processedPhoto = await this.storage.GetProcessedPetPhotoAsync(photo.Uuid, processingIdent); - if (processedPhoto != null) { - Trace.TraceInformation($"Transmitting processed image ({processingIdent}) to client according to the client preferences"); - mimeType = processedPhoto.ImageMimeType ?? "image"; - imageContent = processedPhoto.Image; - break; + + foreach (var processingIdent in preferableProcessingsStr.Split(',').Select(s => new AsciiIdentifier(s))) + { + Trace.TraceInformation($"Checking existence of {processingIdent} for {photo.Uuid}"); + var processedPhoto = await this.storage.GetProcessedPetPhotoAsync(photo.Uuid, processingIdent); + if (processedPhoto != null) { + Trace.TraceInformation($"Transmitting processed image ({processingIdent}) to client according to the client preferences"); + mimeType = processedPhoto.ImageMimeType ?? "image"; + imageContent = processedPhoto.Image; + break; + } } } - } - if(imageContent == null) { - Trace.TraceInformation($"Transmitting unprocessed image to client"); - mimeType = photo.ImageMimeType ?? "image"; - imageContent = photo.Image; - } + if(imageContent == null) { + Trace.TraceInformation($"Transmitting unprocessed image to client"); + mimeType = photo.ImageMimeType ?? "image"; + imageContent = photo.Image; + } - return File(imageContent, mimeType); + return File(imageContent, mimeType); + } + } + catch (Exception ex) + { + Trace.TraceError($"Exception during retrieving raw photo {imNum} for {ns}/{localID}: {ex}"); + return StatusCode(500, ex.ToString()); } } - catch (Exception ex) + catch (ArgumentException ae) { - Trace.TraceError($"Exception during retrieving raw photo {imNum} for {ns}/{localID}: {ex}"); - return StatusCode(500, ex.ToString()); + Trace.TraceError(ae.ToString()); + return BadRequest(ae); } } // GET: /pet911ru/rf123 - [HttpGet("{ns}/{localID}")] + [HttpGet("{nsStr}/{localIDstr}")] [EnableCors] - public async IAsyncEnumerable GetAll(string ns, string localID, [FromQuery] string? featuresToInclude) + public async IAsyncEnumerable GetAll(string nsStr, string localIDstr, [FromQuery] string? featuresToInclude) { + // verifying inputs + AsciiIdentifier ns = new(nsStr); + AsciiIdentifier localID = new(localIDstr); + string[] featuresToIncludeSplit = new string[0]; if (featuresToInclude != null) { featuresToIncludeSplit = featuresToInclude.Trim().Split(",", StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); @@ -102,7 +119,7 @@ public async Task GetImage(string ns, string localID, int imNum, } Trace.TraceInformation($"Yielding photo {photo.ImageNum} for {ns}/{localID}"); - var featuresTasks = featuresToIncludeSplit.Select(featuresIdent => this.storage.GetPhotoFeatures(photo.Uuid, featuresIdent)).ToArray(); + var featuresTasks = featuresToIncludeSplit.Select(featuresIdent => this.storage.GetPhotoFeatures(photo.Uuid, new AsciiIdentifier(featuresIdent))).ToArray(); Dictionary featuresDict = new Dictionary(); for (int i = 0; i < featuresToIncludeSplit.Length; i++) { @@ -113,119 +130,170 @@ public async Task GetImage(string ns, string localID, int imNum, } } - [HttpPut("{ns}/{localID}/{imNum}")] + [HttpPut("{nsStr}/{localIDstr}/{imNum}")] [ProducesResponseType(typeof(Guid), StatusCodes.Status201Created)] [ProducesResponseType(typeof(Guid), StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task Put(string ns, string localID, int imNum, [FromBody ]JsonPoco.PetPhoto photo) + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task Put(string nsStr, string localIDstr, int imNum, [FromBody ]JsonPoco.PetPhoto photo) { try { - Trace.TraceInformation($"Adding photo {imNum} for {ns}/{localID}"); - (Guid uuid, bool created) = await this.storage.AddOriginalPetPhotoAsync(ns, localID, imNum, photo.ToStoragePetPhoto()); - if(created) + // verifying inputs + AsciiIdentifier ns = new(nsStr); + AsciiIdentifier localID = new(localIDstr); + + try { - Trace.TraceInformation($"successfully added photo {imNum} for {ns}/{localID}. UUID: {uuid}"); - var routeValues = new { ns = ns, localID = localID, imNum = imNum }; - return CreatedAtAction(nameof(GetImage), routeValues, uuid); + Trace.TraceInformation($"Adding photo {imNum} for {ns}/{localID}"); + (Guid uuid, bool created) = await this.storage.AddOriginalPetPhotoAsync(ns, localID, imNum, photo.ToStoragePetPhoto()); + if(created) + { + Trace.TraceInformation($"successfully added photo {imNum} for {ns}/{localID}. UUID: {uuid}"); + var routeValues = new { ns = ns, localID = localID, imNum = imNum }; + return CreatedAtAction(nameof(GetImage), routeValues, uuid); + } + else + { + Trace.TraceError($"Photo {imNum} for {ns}/{localID} already exists"); + return new ConflictObjectResult(uuid); + } } - else + catch (Exception ex) { - Trace.TraceError($"Photo {imNum} for {ns}/{localID} already exists"); - return new ConflictObjectResult(uuid); + Trace.TraceError($"Exception during adding a photo {imNum} for {ns}/{localID}: {ex}"); + return StatusCode(StatusCodes.Status500InternalServerError, ex.ToString()); } } - catch (Exception ex) + catch (ArgumentException ae) { - Trace.TraceError($"Exception during adding a photo {imNum} for {ns}/{localID}: {ex}"); - return StatusCode(StatusCodes.Status500InternalServerError, ex.ToString()); + Trace.TraceError(ae.ToString()); + return BadRequest(ae); } } - [HttpPut("{ns}/{localID}/{imNum}/processed/{processingIdent}")] + [HttpPut("{nsStr}/{localIDstr}/{imNum}/processed/{processingIdentStr}")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(typeof(string), StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task Put(string ns, string localID, int imNum,string processingIdent, [FromBody] JsonPoco.PetPhoto photo) + public async Task Put(string nsStr, string localIDstr, int imNum,string processingIdentStr, [FromBody] JsonPoco.PetPhoto photo) { try { - Trace.TraceInformation($"Adding processed ({processingIdent}) photo {imNum} for {ns}/{localID}"); - processingIdent = processingIdent.Trim(); - // TODO: guard against injections here - var orig = await storage.GetOriginalPhotoAsync(ns, localID, imNum); - if (orig == null) { - return NotFound($"Original photo identified by {ns}/{localID}/{imNum} is not found"); - } - - bool created = await this.storage.AddProcessedPetPhotoAsync(orig.Uuid, processingIdent, photo.ToStoragePetPhoto()); - if (created) + // verifying inputs + AsciiIdentifier ns = new(nsStr); + AsciiIdentifier localID = new(localIDstr); + AsciiIdentifier processingIdent = new(processingIdentStr); + + try { - Trace.TraceInformation($"successfully added processed ({processingIdent}) photo {imNum} for {ns}/{localID}. UUID: {orig.Uuid}"); - var routeValues = new { ns = ns, localID = localID, imNum = imNum, preferableProcessingsStr = processingIdent }; - return CreatedAtAction(nameof(GetImage), routeValues, orig.Uuid); + Trace.TraceInformation($"Adding processed ({processingIdent}) photo {imNum} for {ns}/{localID}"); + + // TODO: guard against injections here + var orig = await storage.GetOriginalPhotoAsync(ns, localID, imNum); + if (orig == null) { + return NotFound($"Original photo identified by {ns}/{localID}/{imNum} is not found"); + } + + bool created = await this.storage.AddProcessedPetPhotoAsync(orig.Uuid, processingIdent, photo.ToStoragePetPhoto()); + if (created) + { + Trace.TraceInformation($"successfully added processed ({processingIdent}) photo {imNum} for {ns}/{localID}. UUID: {orig.Uuid}"); + var routeValues = new { ns = ns, localID = localID, imNum = imNum, preferableProcessingsStr = processingIdent }; + return CreatedAtAction(nameof(GetImage), routeValues, orig.Uuid); + } + else + { + Trace.TraceError($"Processed ({processingIdent}) photo {imNum} for {ns}/{localID}. UUID: {orig.Uuid} already exists"); + return new ConflictResult(); + } } - else + catch (Exception ex) { - Trace.TraceError($"Processed ({processingIdent}) photo {imNum} for {ns}/{localID}. UUID: {orig.Uuid} already exists"); - return new ConflictResult(); + Trace.TraceError($"Exception during adding a photo {imNum} for {ns}/{localID}: {ex}"); + return StatusCode(StatusCodes.Status500InternalServerError, ex.ToString()); } } - catch (Exception ex) + catch (ArgumentException ae) { - Trace.TraceError($"Exception during adding a photo {imNum} for {ns}/{localID}: {ex}"); - return StatusCode(StatusCodes.Status500InternalServerError, ex.ToString()); + Trace.TraceError(ae.ToString()); + return BadRequest(ae); } } - [HttpPut("{ns}/{localID}/{imNum}/features/{featuresIdent}")] - public async Task PutFeatures(string ns, string localID, int imNum, string featuresIdent, [FromBody] JsonPoco.FeaturesPOCO features) + [HttpPut("{nsStr}/{localIDstr}/{imNum}/features/{featuresIdentStr}")] + public async Task PutFeatures(string nsStr, string localIDstr, int imNum, string featuresIdentStr, [FromBody] JsonPoco.FeaturesPOCO features) { try { - // TODO: guard against injections here - var orig = await storage.GetOriginalPhotoAsync(ns, localID, imNum); - if (orig == null) + // verifying inputs + AsciiIdentifier ns = new(nsStr); + AsciiIdentifier localID = new(localIDstr); + AsciiIdentifier featuresIdent = new(featuresIdentStr); + + try { - return NotFound($"Original photo identified by {ns}/{localID}/{imNum} is not found"); - } + // TODO: guard against injections here + var orig = await storage.GetOriginalPhotoAsync(ns, localID, imNum); + if (orig == null) + { + return NotFound($"Original photo identified by {ns}/{localID}/{imNum} is not found"); + } - Trace.TraceInformation($"Setting features {featuresIdent} for {ns}/{localID}/{imNum} ({orig.Uuid})"); - await this.storage.SetPhotoFeatureVectorAsync(orig.Uuid, featuresIdent, features.Features); - Trace.TraceInformation($"Successfully set features {featuresIdent} for {ns}/{localID}/{imNum} ({orig.Uuid})"); - return Ok(); + Trace.TraceInformation($"Setting features {featuresIdent} for {ns}/{localID}/{imNum} ({orig.Uuid})"); + await this.storage.SetPhotoFeatureVectorAsync(orig.Uuid, featuresIdent, features.Features); + Trace.TraceInformation($"Successfully set features {featuresIdent} for {ns}/{localID}/{imNum} ({orig.Uuid})"); + return Ok(); + } + catch (Exception err) + { + Trace.TraceError($"Except card for {ns}/{localID}: {err}"); + return StatusCode(500, err.ToString()); + } } - catch (Exception err) + catch (ArgumentException ae) { - Trace.TraceError($"Except card for {ns}/{localID}: {err}"); - return StatusCode(500, err.ToString()); + Trace.TraceError(ae.ToString()); + return BadRequest(ae); } } - [HttpDelete("{ns}/{localID}/{photoNum?}")] - public async Task> Delete(string ns, string localID, int photoNum) + [HttpDelete("{nsStr}/{localIDstr}/{photoNum?}")] + public async Task> Delete(string nsStr, string localIDstr, int photoNum) { try { - if (photoNum == 0) + // verifying inputs + AsciiIdentifier ns = new(nsStr); + AsciiIdentifier localID = new(localIDstr); + + try { - // parameter is omitted. thus passing -1 to the storage - photoNum = -1; - } + if (photoNum == 0) + { + // parameter is omitted. thus passing -1 to the storage + photoNum = -1; + } - Trace.TraceInformation($"Deleting photo {photoNum} for {ns}/{localID}"); - bool res = await this.storage.DeleteOriginalPetPhoto(ns, localID, photoNum); - if (res) - Trace.TraceInformation($"Successfully deleted photo {photoNum} for {ns}/{localID}"); - else - Trace.TraceWarning($"Failed to delete photo {photoNum} for {ns}/{localID}"); - return res; + Trace.TraceInformation($"Deleting photo {photoNum} for {ns}/{localID}"); + bool res = await this.storage.DeleteOriginalPetPhoto(ns, localID, photoNum); + if (res) + Trace.TraceInformation($"Successfully deleted photo {photoNum} for {ns}/{localID}"); + else + Trace.TraceWarning($"Failed to delete photo {photoNum} for {ns}/{localID}"); + return res; + } + catch (Exception ex) + { + Trace.TraceError($"Exception during deleting a photo {photoNum} for {ns}/{localID}: {ex}"); + return StatusCode(500, ex.ToString()); + } } - catch (Exception ex) + catch (ArgumentException ae) { - Trace.TraceError($"Exception during deleting a photo {photoNum} for {ns}/{localID}: {ex}"); - return StatusCode(500, ex.ToString()); + Trace.TraceError(ae.ToString()); + return BadRequest(ae); } } } diff --git a/Scripts/create_kashtanka_keyspace.cql b/Scripts/create_kashtanka_keyspace.cql index 6acd92d..c19b2e0 100644 --- a/Scripts/create_kashtanka_keyspace.cql +++ b/Scripts/create_kashtanka_keyspace.cql @@ -1,6 +1,6 @@ CREATE KEYSPACE IF NOT EXISTS kashtanka WITH REPLICATION = { 'class': 'SimpleStrategy', - 'replication_factor': 3 + 'replication_factor': 2 } AND DURABLE_WRITES = true; diff --git a/Security.cs b/Security.cs new file mode 100644 index 0000000..3d433d0 --- /dev/null +++ b/Security.cs @@ -0,0 +1,31 @@ +namespace CardStorageRestAPI +{ + public class AsciiIdentifier + { + private readonly string verifiedString; + public AsciiIdentifier(string str) { + // checking the input for permitted values + + var trimmed = str.Trim(); + // only A-Za-z0-9 and '_' and '-' are permitted + foreach (char c in trimmed) { + if ( + (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '-') || (c == '_') + ) + continue; + throw new ArgumentException($"identifier contains characters that are not permitted. Only A-Za-z0-9 and '_' and '-' are permitted"); + } + verifiedString = trimmed; + } + + public override string ToString() + { + return this.verifiedString; + } + + + } +} diff --git a/Storage/CassandraStorage.cs b/Storage/CassandraStorage.cs index 765e2f9..6e64cb1 100644 --- a/Storage/CassandraStorage.cs +++ b/Storage/CassandraStorage.cs @@ -5,6 +5,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using CardStorageRestAPI; using Cassandra; using Cassandra.Mapping; using ISession = Cassandra.ISession; @@ -245,15 +246,15 @@ public static string DecodeCardType(sbyte code) }; } - public async Task SetPetCardAsync(string ns, string localID, PetCard card) + public async Task SetPetCardAsync(AsciiIdentifier ns, AsciiIdentifier localID, PetCard card) { await EnsureConnectionInitialized(); // See scripts/create_cards_by_id.cql var statement = this.insertPetCardStatement.Bind( - ns, - localID, + ns.ToString(), + localID.ToString(), card.ProvenanceURL, EncodeAnimal(card.Animal), EncodePetSex(card.AnimalSex), @@ -267,15 +268,15 @@ public async Task SetPetCardAsync(string ns, string localID, PetCard card) return checkInsertSuccessful(await session.ExecuteAsync(statement)); } - public async Task<(Guid uuid, bool created)> AddOriginalPetPhotoAsync(string ns, string localID, int imageNum, PetPhoto photo) + public async Task<(Guid uuid, bool created)> AddOriginalPetPhotoAsync(AsciiIdentifier ns, AsciiIdentifier localID, int imageNum, PetPhoto photo) { await EnsureConnectionInitialized(); // see Scripts/create_images_by_card_id.cql var uuid = Guid.NewGuid(); var statement = this.addPetOriginalImageStatement.Bind( - ns, - localID, + ns.ToString(), + localID.ToString(), (sbyte)imageNum, photo.Image, photo.ImageMimeType, @@ -287,7 +288,7 @@ public async Task SetPetCardAsync(string ns, string localID, PetCard card) } else { // fetching existing UUID - var statement2 = this.getParticularOriginalPetImageUuidStatement.Bind(ns, localID, (sbyte)imageNum); + var statement2 = this.getParticularOriginalPetImageUuidStatement.Bind(ns.ToString(), localID.ToString(), (sbyte)imageNum); var res2 = await this.session.ExecuteAsync(statement2); var row = res2.FirstOrDefault(); if (row != null) { @@ -298,33 +299,33 @@ public async Task SetPetCardAsync(string ns, string localID, PetCard card) } } - public async Task AddProcessedPetPhotoAsync(Guid imageUuid, string processingIdent, PetPhoto photo) { + public async Task AddProcessedPetPhotoAsync(Guid imageUuid, AsciiIdentifier processingIdent, PetPhoto photo) { // see Scripts/create_processed_images_by_uuid.cql var statement = this.addPetProcessedImageStatement.Bind( imageUuid, - processingIdent, + processingIdent.ToString(), photo.Image, photo.ImageMimeType ); return checkInsertSuccessful(await this.session.ExecuteAsync(statement)); } - public async Task DeletePetCardAsync(string ns, string localID) + public async Task DeletePetCardAsync(AsciiIdentifier ns, AsciiIdentifier localID) { await EnsureConnectionInitialized(); - var statement = this.deletePetCardStatement.Bind(ns, localID); + var statement = this.deletePetCardStatement.Bind(ns.ToString(), localID.ToString()); await session.ExecuteAsync(statement); return true; } - public async Task GetPetCardAsync(string ns, string localID) + public async Task GetPetCardAsync(AsciiIdentifier ns, AsciiIdentifier localID) { await EnsureConnectionInitialized(); - var statement = this.getPetCardStatement.Bind(ns, localID); + var statement = this.getPetCardStatement.Bind(ns.ToString(), localID.ToString()); var rows = await session.ExecuteAsync(statement); Row extracted = rows.FirstOrDefault(); if (extracted != null) @@ -370,11 +371,11 @@ private static PetPhoto ConvertRowToPetProcessedPhoto(Row row, bool includeBinDa ); } - public async IAsyncEnumerable ListOriginalPhotosAsync(string ns, string localID) + public async IAsyncEnumerable ListOriginalPhotosAsync(AsciiIdentifier ns, AsciiIdentifier localID) { await EnsureConnectionInitialized(); - BoundStatement statement = this.getAllPetImagesStatement.Bind(ns, localID); + BoundStatement statement = this.getAllPetImagesStatement.Bind(ns.ToString(), localID.ToString()); var rows = await this.session.ExecuteAsync(statement); foreach (var row in rows) { @@ -382,30 +383,30 @@ public async IAsyncEnumerable ListOriginalPhotosAsync(string n } } - public async Task DeleteOriginalPetPhoto(string ns, string localID, int photoNum = -1) + public async Task DeleteOriginalPetPhoto(AsciiIdentifier ns, AsciiIdentifier localID, int photoNum = -1) { await EnsureConnectionInitialized(); BoundStatement statement = (photoNum == -1) ? - (this.deleteAllPetImagesStatement.Bind(ns, localID)) : - (this.deleteSpecificPetOriginalImageStatement.Bind(ns, localID, photoNum)); + (this.deleteAllPetImagesStatement.Bind(ns.ToString(), localID.ToString())) : + (this.deleteSpecificPetOriginalImageStatement.Bind(ns.ToString(), localID.ToString(), photoNum)); await this.session.ExecuteAsync(statement); return true; } - public async Task DeleteProcessedPhoto(Guid imageUuid, string processingIdent) { + public async Task DeleteProcessedPhoto(Guid imageUuid, AsciiIdentifier processingIdent) { await EnsureConnectionInitialized(); - BoundStatement statement = this.deletePetProcessedImageStatement.Bind(imageUuid, processingIdent); + BoundStatement statement = this.deletePetProcessedImageStatement.Bind(imageUuid, processingIdent.ToString()); await this.session.ExecuteAsync(statement); return true; } - public async Task GetOriginalPhotoAsync(string ns, string localID, int imageNum) + public async Task GetOriginalPhotoAsync(AsciiIdentifier ns, AsciiIdentifier localID, int imageNum) { await EnsureConnectionInitialized(); - var statement = this.getParticularOriginalPetImageStatement.Bind(ns, localID, (sbyte)imageNum); + var statement = this.getParticularOriginalPetImageStatement.Bind(ns.ToString(), localID.ToString(), (sbyte)imageNum); var row = (await this.session.ExecuteAsync(statement)).FirstOrDefault(); if (row != null) { @@ -418,9 +419,9 @@ public async Task GetOriginalPhotoAsync(string ns, string loca } } - public async Task GetProcessedPetPhotoAsync(Guid imageUuid, string processingIdent) { + public async Task GetProcessedPetPhotoAsync(Guid imageUuid, AsciiIdentifier processingIdent) { await EnsureConnectionInitialized(); - var statement = this.getParticularProcessedPetImageStatement.Bind(imageUuid, processingIdent); + var statement = this.getParticularProcessedPetImageStatement.Bind(imageUuid, processingIdent.ToString()); var row = (await this.session.ExecuteAsync(statement)).FirstOrDefault(); if (row != null) { @@ -434,33 +435,33 @@ public async Task GetProcessedPetPhotoAsync(Guid imageUuid, string pro } - public async Task SetCardFeatureVectorAsync(string ns, string localID, string featuredIdent, double[] features) + public async Task SetCardFeatureVectorAsync(AsciiIdentifier ns, AsciiIdentifier localID, AsciiIdentifier featuredIdent, double[] features) { await EnsureConnectionInitialized(); var newDict = new Dictionary>(); - newDict.Add(featuredIdent, features); + newDict.Add(featuredIdent.ToString(), features); var statement = new SimpleStatement($"UPDATE cards_by_id SET features = features + ? WHERE namespace = ? AND local_id = ?", - newDict, ns, localID); + newDict, ns.ToString(), localID.ToString()); await this.session.ExecuteAsync(statement); return true; } - public async Task SetPhotoFeatureVectorAsync(Guid imageUuid, string featuresIdent, double[] features) + public async Task SetPhotoFeatureVectorAsync(Guid imageUuid, AsciiIdentifier featuresIdent, double[] features) { await EnsureConnectionInitialized(); - var statement = this.addPhotoFeaturesStatement.Bind(imageUuid, featuresIdent, features); + var statement = this.addPhotoFeaturesStatement.Bind(imageUuid, featuresIdent.ToString(), features); await this.session.ExecuteAsync(statement); return true; } - public async Task GetPhotoFeatures(Guid imageUuid, string featuresIdent) + public async Task GetPhotoFeatures(Guid imageUuid, AsciiIdentifier featuresIdent) { await EnsureConnectionInitialized(); - var statement = this.getPhotoFeatureVectorStatement.Bind(imageUuid, featuresIdent); + var statement = this.getPhotoFeatureVectorStatement.Bind(imageUuid, featuresIdent.ToString()); var res = await this.session.ExecuteAsync(statement); return res.FirstOrDefault()?.GetValue("feature_vector"); diff --git a/Storage/ICardStorage.cs b/Storage/ICardStorage.cs index 4efe3f1..3fbc661 100644 --- a/Storage/ICardStorage.cs +++ b/Storage/ICardStorage.cs @@ -1,4 +1,5 @@ -using System; +using CardStorageRestAPI; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -7,9 +8,9 @@ namespace PatCardStorageAPI.Storage { public interface ICardStorage { - Task GetPetCardAsync(string ns, string localID); - Task SetPetCardAsync(string ns, string localID,PetCard card); - Task DeletePetCardAsync(string ns, string localID); - Task SetCardFeatureVectorAsync(string ns, string localID, string featuresIdent, double[] features); + Task GetPetCardAsync(AsciiIdentifier ns, AsciiIdentifier localID); + Task SetPetCardAsync(AsciiIdentifier ns, AsciiIdentifier localID,PetCard card); + Task DeletePetCardAsync(AsciiIdentifier ns, AsciiIdentifier localID); + Task SetCardFeatureVectorAsync(AsciiIdentifier ns, AsciiIdentifier localID, AsciiIdentifier featuresIdent, double[] features); } } diff --git a/Storage/IPhotoStorage.cs b/Storage/IPhotoStorage.cs index 5af839d..e73f271 100644 --- a/Storage/IPhotoStorage.cs +++ b/Storage/IPhotoStorage.cs @@ -1,4 +1,5 @@ -using Cassandra.DataStax.Graph; +using CardStorageRestAPI; +using Cassandra.DataStax.Graph; using System; using System.Collections.Generic; using System.Linq; @@ -8,11 +9,11 @@ namespace PatCardStorageAPI.Storage { public interface IPhotoStorage { - IAsyncEnumerable ListOriginalPhotosAsync(string ns, string localID); - Task GetOriginalPhotoAsync(string ns, string localID, int imageNum); - Task GetProcessedPetPhotoAsync(Guid imageUuid, string processingIdent); + IAsyncEnumerable ListOriginalPhotosAsync(AsciiIdentifier ns, AsciiIdentifier localID); + Task GetOriginalPhotoAsync(AsciiIdentifier ns, AsciiIdentifier localID, int imageNum); + Task GetProcessedPetPhotoAsync(Guid imageUuid, AsciiIdentifier processingIdent); - Task GetPhotoFeatures(Guid imageUuid, string featuresIdent); + Task GetPhotoFeatures(Guid imageUuid, AsciiIdentifier featuresIdent); /// /// photoNum = -1 means: delete all of the photos for the specified pet @@ -21,8 +22,8 @@ public interface IPhotoStorage /// /// /// - Task DeleteOriginalPetPhoto(string ns, string localID, int photoNum = -1); - Task DeleteProcessedPhoto(Guid imageUuid, string processingIdent); + Task DeleteOriginalPetPhoto(AsciiIdentifier ns, AsciiIdentifier localID, int photoNum = -1); + Task DeleteProcessedPhoto(Guid imageUuid, AsciiIdentifier processingIdent); /// /// Returns Guid if the photo is successfully stored. If the photo already exists does NOT replace it, returns null. @@ -32,7 +33,7 @@ public interface IPhotoStorage /// /// /// - Task<(Guid uuid, bool created)> AddOriginalPetPhotoAsync(string ns, string localID, int imageNum, PetPhoto photo); + Task<(Guid uuid, bool created)> AddOriginalPetPhotoAsync(AsciiIdentifier ns, AsciiIdentifier localID, int imageNum, PetPhoto photo); /// /// Returns whether new processed photo is added. @@ -42,8 +43,8 @@ public interface IPhotoStorage /// /// /// - Task AddProcessedPetPhotoAsync(Guid imageUuid, string processingIdent, PetPhoto photo); + Task AddProcessedPetPhotoAsync(Guid imageUuid, AsciiIdentifier processingIdent, PetPhoto photo); - Task SetPhotoFeatureVectorAsync(Guid imageUuid, string featuresIdent, double[] features); + Task SetPhotoFeatureVectorAsync(Guid imageUuid, AsciiIdentifier featuresIdent, double[] features); } } diff --git a/Storage/MemoryTestStorage.cs b/Storage/MemoryTestStorage.cs index f938dce..0dcaafe 100644 --- a/Storage/MemoryTestStorage.cs +++ b/Storage/MemoryTestStorage.cs @@ -1,4 +1,5 @@ -using System; +using CardStorageRestAPI; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -7,20 +8,20 @@ namespace PatCardStorageAPI.Storage { public class MemoryTestStorage : ICardStorage, IPhotoStorage { - public Task SetPetCardAsync(string ns, string localID, PetCard card) + public Task SetPetCardAsync(AsciiIdentifier ns, AsciiIdentifier localID, PetCard card) { throw new NotImplementedException(); } - public Task DeletePetCardAsync(string ns, string localID) + public Task DeletePetCardAsync(AsciiIdentifier ns, AsciiIdentifier localID) { throw new NotImplementedException(); } - public Task GetPetCardAsync(string ns, string localID) + public Task GetPetCardAsync(AsciiIdentifier ns, AsciiIdentifier localID) { // only serves single card - if (ns == "pet911ru" && localID == "rf123") + if (ns.ToString() == "pet911ru" && localID.ToString() == "rf123") { var res = new PetCard { @@ -37,9 +38,9 @@ public Task GetPetCardAsync(string ns, string localID) return Task.FromResult(null); } - public async IAsyncEnumerable ListOriginalPhotosAsync(string ns, string localID) + public async IAsyncEnumerable ListOriginalPhotosAsync(AsciiIdentifier ns, AsciiIdentifier localID) { - if (ns == "pet911ru" && localID == "rf123") + if (ns.ToString() == "pet911ru" && localID.ToString() == "rf123") { yield return new PetOriginalPhoto(Guid.NewGuid(), null, null, 2); @@ -49,47 +50,47 @@ public async IAsyncEnumerable ListOriginalPhotosAsync(string n yield return null; } - public Task GetOriginalPhotoAsync(string ns, string localID, int imageNum) + public Task GetOriginalPhotoAsync(AsciiIdentifier ns, AsciiIdentifier localID, int imageNum) { throw new NotImplementedException(); } - public Task GetProcessedPetPhotoAsync(Guid imageUuid, string processingIdent) + public Task GetProcessedPetPhotoAsync(Guid imageUuid, AsciiIdentifier processingIdent) { throw new NotImplementedException(); } - public Task DeleteOriginalPetPhoto(string ns, string localID, int photoNum = -1) + public Task DeleteOriginalPetPhoto(AsciiIdentifier ns, AsciiIdentifier localID, int photoNum = -1) { throw new NotImplementedException(); } - public Task DeleteProcessedPhoto(Guid imageUuid, string processingIdent) + public Task DeleteProcessedPhoto(Guid imageUuid, AsciiIdentifier processingIdent) { throw new NotImplementedException(); } - public Task AddProcessedPetPhotoAsync(Guid imageUuid, string processingIdent, PetPhoto photo) + public Task AddProcessedPetPhotoAsync(Guid imageUuid, AsciiIdentifier processingIdent, PetPhoto photo) { throw new NotImplementedException(); } - public Task<(Guid,bool)> AddOriginalPetPhotoAsync(string ns, string localID, int imageNum, PetPhoto photo) + public Task<(Guid,bool)> AddOriginalPetPhotoAsync(AsciiIdentifier ns, AsciiIdentifier localID, int imageNum, PetPhoto photo) { throw new NotImplementedException(); } - public Task SetCardFeatureVectorAsync(string ns, string localID, string featuresIdent, double[] features) + public Task SetCardFeatureVectorAsync(AsciiIdentifier ns, AsciiIdentifier localID, AsciiIdentifier featuresIdent, double[] features) { throw new NotImplementedException(); } - public Task SetPhotoFeatureVectorAsync(Guid imageUuid, string featuresIdent, double[] features) + public Task SetPhotoFeatureVectorAsync(Guid imageUuid, AsciiIdentifier featuresIdent, double[] features) { throw new NotImplementedException(); } - public Task GetPhotoFeatures(Guid imageUuid, string featuresIdent) + public Task GetPhotoFeatures(Guid imageUuid, AsciiIdentifier featuresIdent) { throw new NotImplementedException(); }