This commit is contained in:
Kai Jan Kriegel 2020-11-07 22:08:40 +01:00
parent ac7acc9f72
commit a69d76474f
8 changed files with 377 additions and 97 deletions

View File

@ -15,11 +15,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Borepin.GTK", "Borepin\Bore
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Borepin.macOS", "Borepin\Borepin.macOS\Borepin.macOS.csproj", "{3EC23FE7-395E-4BBC-B56B-9455354BDA34}"
EndProject
Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "NFC_Test", "NFC_Test\NFC_Test.csproj", "{41EC0C17-B456-42AE-89F2-79DDB8ED9858}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NFC_Test", "NFC_Test\NFC_Test.csproj", "{41EC0C17-B456-42AE-89F2-79DDB8ED9858}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NFC", "NFC\NFC.csproj", "{9C2ED2EB-D91C-4D80-9580-6A135C05AF11}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NFC", "NFC\NFC.csproj", "{9C2ED2EB-D91C-4D80-9580-6A135C05AF11}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FabAccessAPI", "FabAccessAPI\FabAccessAPI.csproj", "{3251FCE9-FEA3-4662-8BEB-636BE6732D48}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FabAccessAPI", "FabAccessAPI\FabAccessAPI.csproj", "{3251FCE9-FEA3-4662-8BEB-636BE6732D48}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FabAccessAPI_Test", "FabAccessAPI_Test\FabAccessAPI_Test.csproj", "{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "S22.Sasl", "external\SASL\S22.Sasl.csproj", "{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -301,6 +305,54 @@ Global
{3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|x64.Build.0 = Release|Any CPU
{3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|x86.ActiveCfg = Release|Any CPU
{3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|x86.Build.0 = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|ARM.ActiveCfg = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|ARM.Build.0 = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|iPhone.Build.0 = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|x64.ActiveCfg = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|x64.Build.0 = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|x86.ActiveCfg = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Debug|x86.Build.0 = Debug|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|Any CPU.Build.0 = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|ARM.ActiveCfg = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|ARM.Build.0 = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|iPhone.ActiveCfg = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|iPhone.Build.0 = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|x64.ActiveCfg = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|x64.Build.0 = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|x86.ActiveCfg = Release|Any CPU
{6DD3DE28-BB0B-45BA-9072-CF6325E294CB}.Release|x86.Build.0 = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|ARM.ActiveCfg = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|ARM.Build.0 = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|iPhone.Build.0 = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|x64.ActiveCfg = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|x64.Build.0 = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|x86.ActiveCfg = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Debug|x86.Build.0 = Debug|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|Any CPU.Build.0 = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|ARM.ActiveCfg = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|ARM.Build.0 = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|iPhone.ActiveCfg = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|iPhone.Build.0 = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|x64.ActiveCfg = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|x64.Build.0 = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|x86.ActiveCfg = Release|Any CPU
{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -83,30 +83,78 @@
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\Contents.json">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\App-icon 1024.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneApp_60%402x.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneApp_60%403x.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSpotlight_40%402x.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSpotlight_40%403x.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadSpotlight_40-1.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadSpotlight_40%402x.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadApp_76.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadApp_76%402x.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadProApp_83.5%402x.png" />
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\Contents.json" />
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\AppStore.png" />
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\AppStore%402x.png" />
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\AppStore%403x.png" />
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\AppStore%402x-1.png" />
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\AppStore%403x-1.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadSpotlight_40-3.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSettings_29%402x.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSettings_29%403x.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSettings_29.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSettings_29%402x-1.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadSpotlight_40.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\App-Icon.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\App-Icon_20.png" />
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\App-icon 1024.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneApp_60%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneApp_60%403x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSpotlight_40%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSpotlight_40%403x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadSpotlight_40-1.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadSpotlight_40%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadApp_76.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadApp_76%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadProApp_83.5%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\Contents.json">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\AppStore.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\AppStore%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\AppStore%403x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\AppStore%402x-1.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\LaunchIcon.imageset\AppStore%403x-1.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadSpotlight_40-3.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSettings_29%402x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSettings_29%403x.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSettings_29.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPhoneSettings_29%402x-1.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\AppStore_iPadSpotlight_40.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\App-Icon.png">
<Visible>false</Visible>
</ImageAsset>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\App-Icon_20.png">
<Visible>false</Visible>
</ImageAsset>
</ItemGroup>
<ItemGroup>
<Reference Include="System" />

181
FabAccessAPI/Auth.cs Normal file
View File

@ -0,0 +1,181 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Capnp;
using Capnp.Rpc;
using FabAccessAPI.Schema;
using S22.Sasl;
namespace FabAccessAPI
{
/// Authentication Identity
///
/// Under the hood a string because the form depends heavily on the method
public struct AuthCId {
public string Id { get; private set; }
public AuthCId(string id) : this() { Id = id; }
}
/// Authorization Identity
///
/// This identity is internal to FabAccess and completely independent from the authentication
/// method or source
public struct AuthZId {
/// Main User ID. Generally an user name or similar
public string Uid;
/// Sub user ID.
///
/// Can change scopes for permissions, e.g. having a +admin account with more permissions than
/// the default account and +dashboard et.al. accounts that have restricted permissions for
/// their applications
public string Subuid;
/// Realm this account originates.
///
/// The Realm is usually described by a domain name but local policy may dictate an unrelated
/// mapping
public string Realm;
}
/// Authentication/Authorization user object.
///
/// This struct contains the user as is passed to the actual authentication/authorization
/// subsystems
///
public struct User {
/// Contains the Authentication ID used
///
/// The authentication ID is an identifier for the authentication exchange. This is different
/// than the ID of the user to be authenticated; for example when using x509 the authcid is
/// the dn of the certificate, when using GSSAPI the authcid is of form `<userid>@<REALM>`
public AuthCId Authcid;
/// Contains the Authorization ID
///
/// This is the identifier of the user to *authenticate as*. This in several cases is different
/// to the `authcid`:
/// If somebody wants to authenticate as somebody else, su-style.
/// If a person wants to authenticate as a higher-permissions account, e.g. foo may set authzid foo+admin
/// to split normal user and "admin" accounts.
/// If a method requires a specific authcid that is different from the identifier of the user
/// to authenticate as, e.g. GSSAPI, x509 client certificates, API TOKEN authentication.
public AuthZId Authzid;
/// Contains the authentication method used
///
/// For the most part this is the SASL method
public string AuthMethod;
/// Method-specific key-value pairs
///
/// Each method can use their own key-value pairs.
/// E.g. EXTERNAL encodes the actual method used (x509 client certs, UID/GID for unix sockets,
/// ...)
public Dictionary<string, string> Kvs;
}
// Authentication has two parts: Granting the authentication itself and then performing the
// authentication.
// Granting the authentication checks if
// a) the given authcid fits with the given (authMethod, kvs). In general a failure here indicates
// a programming failure — the authcid come from the same source as that tuple
// b) the given authcid may authenticate as the given authzid. E.g. if a given client certificate
// has been configured for that user, if a GSSAPI user maps to a given user,
public enum AuthError {
/// Authentication ID is bad/unknown/..
BadAuthcid,
/// Authorization ID is unknown/..
BadAuthzid,
/// Authorization ID is not of form user+uid@realm
MalformedAuthzid,
/// User may not use that authorization id
NotAllowedAuthzid,
}
class Auth {
private IAuthentication _authCap;
public Auth(IAuthentication authCap) {
_authCap = authCap;
}
public Task<IReadOnlyList<string>> GetMechanisms() {
return _authCap.Mechanisms();
}
public async Task<bool> Handshake() {
var host = "localhost";
var asm = typeof(Api).Assembly;
var program = $"{asm.FullName}-{asm.GetName().Version}";
var version = (0u, 1u);
var message = new Greeting() {
Host = host,
Major = version.Item1,
Minor = version.Item2,
Program = program
};
var msg = MessageBuilder.Create();
var root = msg.BuildRoot<Schema.Greeting.WRITER>();
message.serialize(root);
var pump = new FramePump(stream);
pump.Send(msg.Frame);
var frame = Framing.ReadSegments(stream);
var deserializer = DeserializerState.CreateRoot(frame);
var reader = new Greeting.READER(deserializer);
var serverInfo = reader;
Console.WriteLine($"Server: {serverInfo.Host}");
Console.WriteLine($"Version: {serverInfo.Program}");
Console.WriteLine($"API-Version: {serverInfo.Major}.{serverInfo.Minor}");
}
public async Task<object> Authenticate(string mech, Dictionary<string, object> properties) {
var m = SaslFactory.Create(mech);
foreach (KeyValuePair<string, object> entry in properties) {
m.Properties.Add(entry.Key, entry.Value);
}
var initialResponse = new Request.initialResponse();
if (m.HasInitial) {
initialResponse.Initial = m.GetResponse(new byte[0]);
}
var req = new Request {
Mechanism = m.Name,
InitialResponse = initialResponse
};
var resp = await _authCap.Start(req);
while (!m.IsCompleted) {
if (resp.which == Response.WHICH.Challence) {
var additional = m.GetResponse(resp.Challence.ToArray());
resp = await _authCap.Step(additional);
}
else {
break;
}
}
return null;
}
}
}

View File

@ -2,82 +2,31 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Capnp;
using Capnp.Rpc;
using FabAccessAPI.Schema;
using S22.Sasl;
namespace FabAccessAPI
{
class Connection
{
public bool Connect(Greeting clientInfo, Stream stream)
{
var message = new Message()
class Connection {
private TcpRpcClient _rpcClient;
private IBootstrap _bootstrapCap;
private User _user;
private Auth _auth;
{
Greet = clientInfo,
which = Message.WHICH.Greet,
};
var msg = MessageBuilder.Create();
var root = msg.BuildRoot<Schema.Message.WRITER>();
message.serialize(root);
var pump = new FramePump(stream);
pump.Send(msg.Frame);
var frame = Framing.ReadSegments(stream);
var deserializer = DeserializerState.CreateRoot(frame);
var reader = new Message.READER(deserializer);
switch (reader.which)
{
case Message.WHICH.Greet:
var serverInfo = reader.Greet;
Console.WriteLine($"Server: {serverInfo.Host}");
Console.WriteLine($"Version: {serverInfo.Program}");
Console.WriteLine($"API-Version: {serverInfo.Major}.{serverInfo.Minor}");
break;
case Message.WHICH.Leave:
var leave_inner = reader.Leave;
switch (leave_inner.TheReason)
{
case Leave.Reason.incompatible:
Console.WriteLine($"Connection aborted due to incompatible API: {leave_inner.Message}");
break;
case Leave.Reason.other:
Console.WriteLine($"Connection aborted: {leave_inner.Message}");
break;
default:
Console.WriteLine($"Got invalid This should never happen: {leave_inner.Message}");
break;
}
return false;
default:
Console.WriteLine($"Got unexpected message: {reader.which}");
break;
}
//FIXME: Replace the hardcoded credentials
SaslMechanism m = SaslFactory.Create("PLAIN");
m.Properties.Add("Username", "test");
m.Properties.Add("Password", "secret");
var authRequest = new Request()
{
Mechanism = m.Name,
};
var auth = new AuthMessage()
{
};
return true;
/// <summary>
///
/// </summary>
/// <param name="rpcClient">Should be an already configured and connected TcpRpcClient</param>
public Connection(TcpRpcClient rpcClient) {
_rpcClient = rpcClient;
_bootstrapCap = _rpcClient.GetMain<IBootstrap>();
}
async Task Auth(string mech, Dictionary<string, string> kvs) {
_auth = new Auth(await _bootstrapCap.Auth());
var mechs = await _auth.GetMechanisms().ConfigureAwait(false);
}
}
}

View File

@ -1,8 +1,11 @@
using System;
using S22.Sasl;
namespace FabAccessAPI
{
public class Class1
public class Api
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using Capnp.Rpc;
namespace FabAccessAPI {
class InjectableTcpRpcClient : TcpRpcClient {
}
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FabAccessAPI\FabAccessAPI.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,18 @@
using NUnit.Framework;
namespace FabAccessAPI_Test
{
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
}
}