diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..c2641fa --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,139 @@ +# This file is a template, and might need editing before it works on your project. +# The following script will work for any project that can be built from command line by msbuild +# It uses powershell shell executor, so you need to add the following line to your config.toml file +# (located in gitlab-runner.exe directory): +# shell = "powershell" +# +# The script is composed of 3 stages: build, test and deploy. +# +# The build stage restores NuGet packages and uses msbuild to build the exe and msi +# One major issue you'll find is that you can't build msi projects from command line +# if you use vdproj. There are workarounds building msi via devenv, but they rarely work +# The best solution is migrating your vdproj projects to WiX, as it can be build directly +# by msbuild. +# +# The test stage runs nunit from command line against Test project inside your solution +# It also saves the resulting TestResult.xml file +# +# The deploy stage copies the exe and msi from build stage to a network drive +# You need to have the network drive mapped as Local System user for gitlab-runner service to see it +# The best way to persist the mapping is via a scheduled task (see: https://stackoverflow.com/a/7867064/1288473), +# running the following batch command: net use P: \\x.x.x.x\Projects /u:your_user your_pass /persistent:yes + + +# place project specific paths in variables to make the rest of the script more generic +variables: + LIB_RELEASE_FOLDER: 'Borepin\Borepin\bin\Release' + UWP_RELEASE_FOLDER: 'Borepin\Borepin.UWP\bin\x86\Release' + TEST_FOLDER: 'Tests\bin\Release' +# DEPLOY_FOLDER: 'P:\Projects\YourApp\Builds' + NUGET_PATH: 'C:\NuGet\nuget.exe' + DOTNET_PATH: 'C:\Program Files\dotnet\dotnet.exe' + MSBUILD_PATH: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\msbuild.exe' + NUNIT_PATH: 'C:\Program Files (x86)\NUnit.org\nunit-console\nunit3-console.exe' + +stages: + - build + - test + - deploy + +build_base: + stage: build + tags: + - xamarin + - windows +# only: +# - tags # the build process will only be started by git tag commits + script: + - '& "$env:NUGET_PATH" restore' # restore Nuget dependencies + - '& "$env:MSBUILD_PATH" /p:Configuration=Release /target:Borepin' # build the project + artifacts: + expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on + paths: + - '$env:LIB_RELEASE_FOLDER' # saving exe to copy to deploy folder + - '$env:TEST_FOLDER\' # saving entire Test project so NUnit can run tests + +build_UWP: + stage: build + tags: + - xamarin + - windows +# only: +# - tags # the build process will only be started by git tag commits + script: + - '& "$env:NUGET_PATH" restore' # restore Nuget dependencies + - '& "$env:MSBUILD_PATH" /p:Configuration=Release /target:Borepin_UWP' # build the project + artifacts: + expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on + paths: + - '$env:UWP_RELEASE_FOLDER' # saving exe to copy to deploy folder + - '$env:TEST_FOLDER\' # saving entire Test project so NUnit can run tests + +build_Android: + stage: build + tags: + - xamarin + - windows +# only: +# - tags # the build process will only be started by git tag commits + script: + - '& "$env:NUGET_PATH" restore' # restore Nuget dependencies + - '& "$env:MSBUILD_PATH" /p:Configuration=Release /target:Borepin_Android:PackageForAndroid /target:Borepin_Android:SignAndroidPackage' # build the project /p:AndroidKeyStore=True + artifacts: + expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on + paths: + - Borepin/Borepin.Android/bin/Release/com.companyname.borepin-Signed.apk # saving apk to copy to deploy folder + - '$env:TEST_FOLDER\' # saving entire Test project so NUnit can run tests + +build_iOS: + stage: build + tags: + - macos +# only: +# - tags # the build process will only be started by git tag commits + script: + - 'nuget restore' # restore Nuget dependencies + - 'msbuild /t:Borepin_iOS /p:Configuration=Debug /p:Platform=iPhone /p:ArchiveOnBuild=true' # build the project /p:AndroidKeyStore=True + artifacts: + expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on + paths: + - Borepin/Borepin.iOS/bin/iPhone/Debug/Borepin.ipa + - Borepin/Borepin.iOS/bin/iPhone/Debug/Borepin.app.dSYM + - '$env:TEST_FOLDER\' # saving entire Test project so NUnit can run tests + +# test_job: +# stage: test +# tags: +# - xamarin +# - windows +# # only: +# # - tags +# script: +# - '& "$env:NUNIT_PATH" ".\$env:TEST_FOLDER\Tests.dll"' # running NUnit tests +# artifacts: +# when: always # save test results even when the task fails +# expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on +# paths: +# - '.\TestResult.xml' # saving NUnit results to copy to deploy folder +# dependencies: +# - build_job + +# deploy_job: +# stage: deploy +# only: +# - tags +# script: +# # Compose a folder for each release based on commit tag. +# # Assuming your tag is Rev1.0.0.1, and your last commit message is 'First commit' +# # the artifact files will be copied to: +# # P:\Projects\YourApp\Builds\Rev1.0.0.1 - First commit\ +# - '$commitSubject = git log -1 --pretty=%s' +# - '$deployFolder = $($env:DEPLOY_FOLDER) + "\" + $($env:CI_BUILD_TAG) + " - " + $commitSubject + "\"' + +# # xcopy takes care of recursively creating required folders +# - 'xcopy /y ".\$env:EXE_RELEASE_FOLDER\YourApp.exe" "$deployFolder"' +# - 'xcopy /y ".\$env:MSI_RELEASE_FOLDER\YourApp Setup.msi" "$deployFolder"' +# - 'xcopy /y ".\TestResult.xml" "$deployFolder"' +# dependencies: +# - build_job +# - test_job diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f04abcd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "FabAccessAPI/schema"] + path = FabAccessAPI/schema + url = https://gitlab.com/fabinfra/fabaccess/fabaccess-api.git +[submodule "external/SASL"] + path = external/SASL + url = https://github.com/kjkriegel/S22.Sasl.git diff --git a/Borepin.sln b/Borepin.sln index f2e2b0c..409eea6 100644 --- a/Borepin.sln +++ b/Borepin.sln @@ -15,9 +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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FabAccessAPI", "FabAccessAPI\FabAccessAPI.csproj", "{3251FCE9-FEA3-4662-8BEB-636BE6732D48}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "S22.Sasl", "external\SASL\S22.Sasl.csproj", "{7FEC3D5E-C240-41B6-BBFA-895C4F4D45CA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FabAccessAPI_Test", "FabAccessAPI_Test\FabAccessAPI_Test.csproj", "{1C85978A-9FC0-4064-8399-FA2455C5EC2A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -275,6 +281,78 @@ Global {9C2ED2EB-D91C-4D80-9580-6A135C05AF11}.Release|x64.Build.0 = Release|Any CPU {9C2ED2EB-D91C-4D80-9580-6A135C05AF11}.Release|x86.ActiveCfg = Release|Any CPU {9C2ED2EB-D91C-4D80-9580-6A135C05AF11}.Release|x86.Build.0 = Release|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|ARM.ActiveCfg = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|ARM.Build.0 = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|iPhone.Build.0 = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|x64.ActiveCfg = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|x64.Build.0 = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|x86.ActiveCfg = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Debug|x86.Build.0 = Debug|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|Any CPU.Build.0 = Release|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|ARM.ActiveCfg = Release|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|ARM.Build.0 = Release|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|iPhone.ActiveCfg = Release|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|iPhone.Build.0 = Release|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {3251FCE9-FEA3-4662-8BEB-636BE6732D48}.Release|x64.ActiveCfg = Release|Any CPU + {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 + {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 + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|ARM.ActiveCfg = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|ARM.Build.0 = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|iPhone.Build.0 = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|x64.ActiveCfg = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|x64.Build.0 = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|x86.ActiveCfg = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Debug|x86.Build.0 = Debug|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|Any CPU.Build.0 = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|ARM.ActiveCfg = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|ARM.Build.0 = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|iPhone.ActiveCfg = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|iPhone.Build.0 = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|x64.ActiveCfg = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|x64.Build.0 = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|x86.ActiveCfg = Release|Any CPU + {1C85978A-9FC0-4064-8399-FA2455C5EC2A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Borepin/Borepin.iOS/Borepin.iOS.csproj b/Borepin/Borepin.iOS/Borepin.iOS.csproj index 65a106b..ee89ac0 100644 --- a/Borepin/Borepin.iOS/Borepin.iOS.csproj +++ b/Borepin/Borepin.iOS/Borepin.iOS.csproj @@ -15,7 +15,7 @@ true NSUrlSessionHandler automatic - true + 13.2 true @@ -51,9 +51,10 @@ iPhone Developer true Entitlements.plist - Full + SdkOnly -all --optimize=experimental-xforms-product-type + iOS Team Provisioning Profile: org.fab-access.borepin none @@ -64,8 +65,8 @@ ARM64 iPhone Developer Entitlements.plist + Automatic:AppStore --optimize=experimental-xforms-product-type - Full diff --git a/Borepin/Borepin/Borepin.csproj b/Borepin/Borepin/Borepin.csproj index 7c81734..477ce23 100644 --- a/Borepin/Borepin/Borepin.csproj +++ b/Borepin/Borepin/Borepin.csproj @@ -1,4 +1,4 @@ - + 4.0 @@ -22,6 +22,7 @@ + @@ -51,4 +52,4 @@ MSBuild:UpdateDesignTimeXaml - \ No newline at end of file + diff --git a/FabAccessAPI/Auth.cs b/FabAccessAPI/Auth.cs new file mode 100644 index 0000000..ab83078 --- /dev/null +++ b/FabAccessAPI/Auth.cs @@ -0,0 +1,165 @@ +using Capnp; +using FabAccessAPI.Schema; +using S22.Sasl; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Exception = System.Exception; + +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 AuthUser { + /// 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 `@` + 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 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, + + } + + public class UnauthorizedException : Exception{} + public class UnsupportedMechanismException : Exception{} + + /// + /// THIS IS VERY INCOMPLETE! + /// + public class Auth { + #region Log + private static readonly log4net.ILog _Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + #endregion + + private IAuthentication _authCap; + public Auth(IAuthentication authCap) { + _authCap = authCap; + } + + public Task> GetMechanisms() { + return _authCap.Mechanisms(); + } + + public async Task Authenticate(string mech, Dictionary properties) { + + var m = SaslFactory.Create(mech); + foreach (KeyValuePair 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; + } + } + + if (resp.which == Response.WHICH.Outcome) { + if (resp.Outcome.Result == Response.Result.successful) { + return true; + } + else { + //TODO: Provide meaningful info about auth failure + return false; + } + } + + return false; + } + + + + } + +} diff --git a/FabAccessAPI/Connection.cs b/FabAccessAPI/Connection.cs new file mode 100644 index 0000000..84386ab --- /dev/null +++ b/FabAccessAPI/Connection.cs @@ -0,0 +1,64 @@ +using Capnp.Rpc; +using FabAccessAPI.Schema; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace FabAccessAPI { + public class Connection { + #region private variables + private TcpRpcClient? _rpcClient = null; + private IBootstrap? _bootstrapCap = null; + private AuthUser? _authUser = null; + private Auth? _auth = null; + private Machines? _machines = null; + #endregion + + public TcpRpcClient? RpcClient => _rpcClient; + + #region Log + private static readonly log4net.ILog _Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + #endregion + + /// + /// + /// + /// Should be an already configured and connected TcpRpcClient + public Connection(TcpRpcClient rpcClient) { + _rpcClient = rpcClient; + _bootstrapCap = _rpcClient.GetMain(); + _Log.Debug($"Done bootstraping API connection."); + } + + /// + /// Authenticate this connection. + /// Calling this more then once is UB + /// + /// The desired authentication mechanism + /// Key-Value data specific to the mechanism + /// + public async Task Auth(string mech, Dictionary kvs) { + // _bootstrapCap = await _bootstrapCap.Unwrap(); + var authCap = await _bootstrapCap.Auth(); + _auth = new Auth(authCap); + var mechs = await _auth.GetMechanisms().ConfigureAwait(false); + _Log.Debug($"The Server supports the following auth mechs: {string.Join(", ", mechs)}"); + + if (!mechs.Contains(mech)) { + throw new UnsupportedMechanismException(); + } + + await _auth.Authenticate(mech, kvs).ConfigureAwait(false); + } + + /// + /// Get a wrapped capability to interact with machines + /// + /// A wrapped capability to interact with machines + public async Task AccessMachines() { + _machines ??= new Machines((await _bootstrapCap.Machines().ConfigureAwait(false))); + return _machines; + } + } +} diff --git a/FabAccessAPI/FabAccessAPI.csproj b/FabAccessAPI/FabAccessAPI.csproj new file mode 100644 index 0000000..b616b61 --- /dev/null +++ b/FabAccessAPI/FabAccessAPI.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.1 + 8 + enable + + + + + + + + + + + + + + diff --git a/FabAccessAPI/Machines.cs b/FabAccessAPI/Machines.cs new file mode 100644 index 0000000..1a3212c --- /dev/null +++ b/FabAccessAPI/Machines.cs @@ -0,0 +1,204 @@ +using FabAccessAPI.Schema; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace FabAccessAPI { + + public class MachineException : Exception { } + + /// + /// Wraps a capability for accessing the Machines subsystem of BFFH + /// + public class Machines { + #region Log + private static readonly log4net.ILog _Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + #endregion + + + private IMachines _machinesCap; + + /// + /// Constructs the Wrapper Class from a given capability. + /// + /// The capability that should be wrapped. + public Machines(IMachines machinesCap) { + _machinesCap = machinesCap; + } + + /// + /// List of all machines that BFFH knows about the user has been granted at least read access on + /// + /// ReadOnlyList of available Machines + public async Task?> ListMachines() { + return (await _machinesCap.ListMachines().ConfigureAwait(false)).Select(x => new Machine(x)) as IReadOnlyList; + } + + /// + /// Access a particular machine by known name. This may fail for two reasons: + /// The user has not been granted access to know the machine exists or the machine does in fact not exist. + /// In both cases the `machine` result will be a NULL-pointer + /// + /// Name of the Machine + /// The Machine we requested + public async Task GetMachine(string name) { + var mach = (await _machinesCap.GetMachine(name).ConfigureAwait(false)).Item1; + if (mach == null) { + //TODO: Throw a more specific exception! + throw new MachineException(); + } + return new Machine(mach); + } + } + + /// + /// A machine. This represents a machine as BFFH thinks about it which may mean + ///several machines or just part of a machine in the real world. + ///By itself this struct is completely useless since it contains only the information + ///that the machine exists the user is allowed to know about that fact. For all further + ///information the user has to call the contained capabilities which depending on the + ///access level may not be set. For example an admin will have every capability here + ///set but a simple user may only have `read` and `write` set while some users may not + /// even have `read` set and are unable to even see if the machine is currently in use. + /// + public class Machine { + private Schema.Machine _machine; + + /// + /// Constructs the Wrapper Class from a given capability + /// + /// The capability that should be wrapped. + public Machine(Schema.Machine machine) { + _machine = machine; + } + + // read operations + + /// + /// Get the MInfo Struct for the Machine. + /// This contains everything BFFH knows about the Machine. + /// + /// + /// The MInfo Struct describing the Machine + public async Task GetMInfo() { + var readCap = _machine.Read; + if (readCap == null) { + throw new UnauthorizedException(); + } + + return (await _machine.Read.Info().ConfigureAwait(false)).Item1; + } + + //write operations + + /// + /// Try to use a machine. Throws a UnauthorizedException if the user does not have the required + /// permissions to use this machine. + /// + /// Use the Ret() Method of the returned Object to return the machine + /// + /// + /// Capability to give back the machine + public Task Use() { + var writeCap = _machine.Write; + if (writeCap == null) { + throw new UnauthorizedException(); + } + + return writeCap.Use(); + } + + /// + /// Try to reserve a machine. Throws a UnauthorizedException if the user does not have the required + /// permissions to use this machine. + /// + /// Use the Ret() Method of the returned Object to return the machine + /// Use the Use() Nethod of the Machine to use your reserved machine. + /// + /// + /// Capability to give back the machine + public Task Reserve() + { + var writeCap = _machine.Write; + if (writeCap == null) + { + throw new UnauthorizedException(); + } + + return writeCap.Reserve(); + } + + + // public void GiveBack(Schema.Machine.WriteInterface.IGiveBack cap) { + // cap.Ret(); + // } + + //manage operations + + /// + /// After a machine has been used by an user with low enough permissions it's + /// in the 'toCheck' state. This call then allows more priviledged users to + /// "check" the machine and move it to the `free` state. + /// + /// Calling this method signifies that the machine was checked and in an acceptable state. + /// + public async void MarkOk() { + var manageCap = _machine.Manage; + if (manageCap == null) { + throw new UnauthorizedException(); + } + // TODO: Do we really want to check this here? + if ((await GetMInfo().ConfigureAwait(false)).State == State.toCheck) { + await _machine.Manage.Ok().ConfigureAwait(false); + } + } + + /// + /// After a machine has been used by an user with low enough permissions it's + /// in the 'toCheck' state. This call then allows more priviledged users to + /// "check" the machine and move it to the `free` state. + /// + /// Calling this method signifies that the machine was checked and in an unacceptable state. + /// It will most likely be marked as `blocked` and the previous user will somehow be informed. + /// + public async void MarkNotOk() { + var manageCap = _machine.Manage; + if (manageCap == null) { + throw new UnauthorizedException(); + } + // TODO: Do we really want to check this here? + if ((await GetMInfo().ConfigureAwait(false)).State == State.toCheck) { + await _machine.Manage.NotOk().ConfigureAwait(false); + } + } + + //administrative operations + + /// + /// Forcefully set a machine state. + /// + /// The desired machine state. + public async void ForceSetState(State state) { + var adminCap = _machine.Admin; + if (adminCap == null) { + throw new UnauthorizedException(); + } + + await adminCap.ForceSetState(state).ConfigureAwait(false); + } + + /// + /// Set the given user as current responsible + /// + /// The user + public async void ForceSetUser(String user) { + var adminCap = _machine.Admin; + if (adminCap == null) { + throw new UnauthorizedException(); + } + + await adminCap.ForceSetUser(user).ConfigureAwait(false); + } + } +} diff --git a/FabAccessAPI/Permissions.cs b/FabAccessAPI/Permissions.cs new file mode 100644 index 0000000..121aa3e --- /dev/null +++ b/FabAccessAPI/Permissions.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + + +//This is where the permissions subsystem will live +namespace FabAccessAPI { + public class Permissions { + #region Log + private static readonly log4net.ILog _Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + #endregion + } +} diff --git a/FabAccessAPI/schema b/FabAccessAPI/schema new file mode 160000 index 0000000..4adb053 --- /dev/null +++ b/FabAccessAPI/schema @@ -0,0 +1 @@ +Subproject commit 4adb05341763b96a43440a6a96e0d9959ba71e89 diff --git a/FabAccessAPI_Test/FabAccessAPITests.cs b/FabAccessAPI_Test/FabAccessAPITests.cs new file mode 100644 index 0000000..a20f35a --- /dev/null +++ b/FabAccessAPI_Test/FabAccessAPITests.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; +using FabAccessAPI; +using Capnp; +using Capnp.Rpc; +using log4net.Config; +using Microsoft.Extensions.Logging; + +namespace FabAccessAPI_Test { + public class Tests { + #region Log + private static readonly log4net.ILog _Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + #endregion + + private static ILoggerFactory _loggerFactory; + private Connection _connection; + + [OneTimeSetUp] + public void InitialSetup() { + XmlConfigurator.Configure(new System.IO.FileInfo("log4net.config")); + _loggerFactory = LoggerFactory.Create(builder => { + builder + .AddFilter("Microsoft", LogLevel.Warning) + .AddFilter("System", LogLevel.Warning); + }); + _loggerFactory.AddLog4Net(); + Logging.LoggerFactory = _loggerFactory; + } + + [SetUp] + public void Setup() { + var rpcClient = new TcpRpcClient(); + rpcClient.Connect("127.0.0.1", 59661); + _connection = new Connection(rpcClient); + } + + [TearDown] + public void Teardown() { + _connection.RpcClient?.Dispose(); + _connection = null; + } + + [Test] + public void Connect() { + Assert.AreEqual(ConnectionState.Active, _connection.RpcClient.State); + } + + [Test] + public async Task Authenticate() { + await _connection.Auth("PLAIN", new Dictionary{{"Username", "Testuser"}, {"Password", "secret"}}); + } + + [Test] + public async Task Machines() { + await _connection.Auth("PLAIN", new Dictionary{{"Username", "Testuser"}, {"Password", "secret"}}); + var machines = await _connection.AccessMachines(); + + var testmachine = await machines.GetMachine("Testmachine"); + Assert.NotNull(testmachine); + var minfo = await testmachine.GetMInfo(); + Assert.NotNull(minfo); + _Log.Info($"Name: {minfo.Name}, Description: {minfo.Description}, State: {minfo.State.ToString()}"); + } + } +} \ No newline at end of file diff --git a/FabAccessAPI_Test/FabAccessAPI_Test.csproj b/FabAccessAPI_Test/FabAccessAPI_Test.csproj new file mode 100644 index 0000000..7682803 --- /dev/null +++ b/FabAccessAPI_Test/FabAccessAPI_Test.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/FabAccessAPI_Test/log4net.config b/FabAccessAPI_Test/log4net.config new file mode 100644 index 0000000..3781ff0 --- /dev/null +++ b/FabAccessAPI_Test/log4net.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 972e79e..6a792c0 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,14 @@ Borepin * with .NET Desktop 2. Install GTKSharp for Windows [download GTKSharp](https://www.mono-project.com/download/stable/#download-win) +3. Install capnproto + + 3.1 If you have Chocolatey installed + ```shell + $ choco install capnproto + ``` + 3.2 else you can download it from [here](https://capnproto.org/install.html) and add it to your PATH + 4. Clone Borepin [download Borepin](https://gitlab.com/fabinfra/fabaccess/client) 6. Load Borepin @@ -22,11 +30,11 @@ If Step 5. Build Borepin is failing because of GTKSharp, it could help to restar 1. Install mono, gtk-sharp, msbuild, nuget 1.1 Debian based ```shell - $ apt install mono-complete, gtk-sharp2, nuget + $ apt install mono-complete, gtk-sharp2, nuget, capnproto ``` 1.2 ArchLinux based ```shell - $ pacman -S mono, mono-msbuild, gtk-sharp-2, nuget + $ pacman -S mono, mono-msbuild, gtk-sharp-2, nuget, capnproto ``` 2. Clone Borepin ```shell diff --git a/external/SASL b/external/SASL new file mode 160000 index 0000000..cb9a991 --- /dev/null +++ b/external/SASL @@ -0,0 +1 @@ +Subproject commit cb9a9919c971e00c52732fba983e82d795fbe4ad