Commit 80a39700 authored by Jeremy Bokobza's avatar Jeremy Bokobza

First add:

- Added Web api wrapping the Safe functionality offered by HBitcoin
- Added basic unit tests project for the controllers
parent 08c1b8a4
################################################################################
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################
/Breeze.Api/.vs
/Breeze.Api/src/Breeze.Api.Tests/bin
/Breeze.Api/src/Breeze.Api.Tests/obj
/Breeze.Api/src/Breeze.Api/bin
/Breeze.Api/src/Breeze.Api/obj
*.user

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{807563C4-7259-434D-B604-A14C3DCF8E30}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2FF3130A-E8F3-4E0B-8733-B34C4A19910C}"
ProjectSection(SolutionItems) = preProject
global.json = global.json
..\global.json = ..\global.json
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Breeze.Api", "src\Breeze.Api\Breeze.Api.xproj", "{E7B3E9EB-34E8-4B10-B296-4D5270E314A4}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Breeze.Api.Tests", "src\Breeze.Api.Tests\Breeze.Api.Tests.xproj", "{BD5174B4-DCE8-4594-9A16-B83E56767770}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E7B3E9EB-34E8-4B10-B296-4D5270E314A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7B3E9EB-34E8-4B10-B296-4D5270E314A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7B3E9EB-34E8-4B10-B296-4D5270E314A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7B3E9EB-34E8-4B10-B296-4D5270E314A4}.Release|Any CPU.Build.0 = Release|Any CPU
{BD5174B4-DCE8-4594-9A16-B83E56767770}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD5174B4-DCE8-4594-9A16-B83E56767770}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD5174B4-DCE8-4594-9A16-B83E56767770}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD5174B4-DCE8-4594-9A16-B83E56767770}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E7B3E9EB-34E8-4B10-B296-4D5270E314A4} = {807563C4-7259-434D-B604-A14C3DCF8E30}
{BD5174B4-DCE8-4594-9A16-B83E56767770} = {807563C4-7259-434D-B604-A14C3DCF8E30}
EndGlobalSection
EndGlobal
{
"projects": [ "src", "test" ],
"sdk": {
"version": "1.0.0-preview2-003131"
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.25420" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.25420</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>bd5174b4-dce8-4594-9a16-b83e56767770</ProjectGuid>
<RootNamespace>Breeze.Api.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
\ No newline at end of file
using Microsoft.AspNetCore.Mvc;
using Xunit;
using Moq;
using Breeze.Api.Controllers;
using Breeze.Api.Models;
using Breeze.Api.Wrappers;
namespace Breeze.Api.Tests
{
public class ControllersTests
{
[Fact]
public void CreateSafesuccessfullyReturnsMnemonic()
{
var mockSafeCreate = new Mock<ISafeWrapper>();
mockSafeCreate.Setup(safe => safe.Create(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns("mnemonic");
var controller = new SafeController(mockSafeCreate.Object);
// Act
var result = controller.Create(new SafeCreationModel
{
Name = "myName",
FolderPath = "",
Password = "",
Network = ""
});
// Assert
var viewResult = Assert.IsType<JsonResult>(result);
Assert.Equal("mnemonic", viewResult.Value);
Assert.NotNull(result);
}
}
}
{
"version": "1.0.0-*",
"dependencies": {
"System.Runtime.Serialization.Primitives": "4.3.0",
"xunit": "2.2.0",
"dotnet-test-xunit": "1.0.0-rc2-build10025",
"Breeze.Api": "1.0.0-*",
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
"Moq": "4.7.1"
},
"testRunner": "xunit",
"frameworks": {
"netcoreapp1.0": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
}
},
"imports": [ "netcore50" ]
}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>e7b3e9eb-34e8-4b10-b296-4d5270e314a4</ProjectGuid>
<RootNamespace>Breeze.Api</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Security;
using Microsoft.AspNetCore.Mvc;
using Breeze.Api.Models;
using Breeze.Api.Wrappers;
namespace Breeze.Api.Controllers
{
[Route("api/[controller]")]
public class SafeController : Controller
{
private readonly ISafeWrapper safeWrapper;
public SafeController(ISafeWrapper safeWrapper)
{
this.safeWrapper = safeWrapper;
}
/// <summary>
/// Creates a new safe on the local machine.
/// </summary>
/// <param name="safeCreation">The object containing the parameters used to create the wallet.</param>
/// <returns>A JSON object contaibibg the mnemonic created for the new wallet.</returns>
[HttpPost]
public IActionResult Create([FromBody]SafeCreationModel safeCreation)
{
// checks the request is valid
if (!this.ModelState.IsValid)
{
var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
return this.BadRequest(string.Join(Environment.NewLine, errors));
}
try
{
var mnemonic = this.safeWrapper.Create(safeCreation.Password, safeCreation.FolderPath, safeCreation.Name, safeCreation.Network);
return this.Json(mnemonic);
}
catch (NotSupportedException e)
{
Console.WriteLine(e);
// indicates that this wallet already exists
return this.StatusCode((int) HttpStatusCode.Conflict, "This wallet already exists.");
}
}
public IActionResult Load(SafeLoadModel safeLoad)
{
// checks the request is valid
if (!this.ModelState.IsValid)
{
var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
return this.BadRequest(string.Join(Environment.NewLine, errors));
}
try
{
var safe = this.safeWrapper.Load(safeLoad.Password, safeLoad.FolderPath, safeLoad.Name);
return this.Json(safe);
}
catch (FileNotFoundException e)
{
Console.WriteLine(e);
// indicates that this wallet does not exist
return this.StatusCode((int)HttpStatusCode.NotFound, "Wallet not found.");
}
catch (SecurityException e)
{
Console.WriteLine(e);
// indicates that the password is wrong
return this.StatusCode((int)HttpStatusCode.Forbidden, "Wrong password, please try again.");
}
catch (Exception e)
{
Console.WriteLine(e);
return this.StatusCode((int)HttpStatusCode.BadRequest, e.Message);
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Breeze.Api.Models
{
/// <summary>
/// Object used to create a new wallet
/// </summary>
public class SafeCreationModel
{
[Required(ErrorMessage = "A password is required.")]
public string Password { get; set; }
public string Network { get; set; }
[Required(ErrorMessage = "The folder path where the wallet will be created is required.")]
public string FolderPath { get; set; }
[Required(ErrorMessage = "The name of the wallet to create is missing.")]
public string Name { get; set; }
}
public class SafeLoadModel
{
[Required(ErrorMessage = "A password is required.")]
public string Password { get; set; }
[Required(ErrorMessage = "The folder path is required.")]
public string FolderPath { get; set; }
[Required(ErrorMessage = "The name of the wallet is missing.")]
public string Name { get; set; }
}
public class SafeModel
{
public string Network { get; set; }
public string FileName { get; set; }
public IEnumerable<string> Addresses { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
namespace Breeze.Api
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:43190/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Breeze.Api": {
"commandName": "Project",
"launchUrl": "http://localhost:5000/api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Breeze.Api.Wrappers;
namespace Breeze.Api
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc()
// add serializers for NBitcoin objects
.AddJsonOptions(options => NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(options.SerializerSettings));
// add DI classes for controllers.
services.AddTransient<ISafeWrapper, SafeWrapper>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMvc();
}
}
}
using Breeze.Api.Models;
namespace Breeze.Api.Wrappers
{
/// <summary>
/// An interface enabling wallet operations.
/// </summary>
public interface ISafeWrapper
{
string Create(string password, string folderPath, string name, string network);
SafeModel Load(string password, string folderPath, string name);
}
}
using System.IO;
using System.Linq;
using HBitcoin.KeyManagement;
using NBitcoin;
using Breeze.Api.Models;
namespace Breeze.Api.Wrappers
{
/// <summary>
/// An implementation of the <see cref="ISafeWrapper"/> interface.
/// </summary>
public class SafeWrapper : ISafeWrapper
{
/// <summary>
/// Creates a safe on the local device.
/// </summary>
/// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the safe will be saved.</param>
/// <param name="name">The name of the safe.</param>
/// <param name="network">The network for which to create a safe.</param>
/// <returns>A mnemonic allowing recovery of the safe.</returns>
public string Create(string password, string folderPath, string name, string network)
{
// any network different than MainNet will default to TestNet
Network net;
switch (network.ToLowerInvariant())
{
case "main":
case "mainnet":
net = Network.Main;
break;
default:
net = Network.TestNet;
break;
}
Mnemonic mnemonic;
Safe safe = Safe.Create(out mnemonic, password, Path.Combine(folderPath, $"{name}.json"), net);
return mnemonic.ToString();
}
/// <summary>
/// Loads a safe from the local device.
/// </summary>
/// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the safe will be loaded.</param>
/// <param name="name">The name of the safe.</param>
/// <returns>The safe loaded from the local device</returns>
public SafeModel Load(string password, string folderPath, string name)
{
Safe safe = Safe.Load(password, Path.Combine(folderPath, $"{name}.json"));
//TODO review here which data should be returned
return new SafeModel
{
Network = safe.Network.Name,
Addresses = safe.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = safe.WalletFilePath
};
}
}
}
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
{
"dependencies": {
"HBitcoin": "0.1.4",
"Microsoft.AspNetCore.Mvc": "1.1.2",
"Microsoft.AspNetCore.Routing": "1.1.1",
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.1",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.1",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.1",
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.1",
"Microsoft.Extensions.Configuration.Json": "1.1.1",
"Microsoft.Extensions.Logging": "1.1.1",
"Microsoft.Extensions.Logging.Console": "1.1.1",
"Microsoft.Extensions.Logging.Debug": "1.1.1",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.1",
"Microsoft.NETCore.App": "1.1.1"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"**/*.cshtml",
"appsettings.json",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
},
"runtimes": {
"win10-x64": {}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!--
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
-->
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
</system.webServer>
</configuration>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment