Skip to content

Commit

Permalink
Initial node
Browse files Browse the repository at this point in the history
  • Loading branch information
NecatiMeral committed Feb 28, 2023
1 parent d72c187 commit f0b0f6c
Show file tree
Hide file tree
Showing 32 changed files with 2,536 additions and 108 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false

# License header
file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.
# file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.

# C++ Files
[*.{cpp,h,in}]
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,4 @@ MigrationBackup/

build/dev/
!build/dev/.gitkeep
dist/
19 changes: 19 additions & 0 deletions build/Get-Cert-Content.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[CmdletBinding()]
param (
[Parameter(Mandatory = $True)]
[string]
$Cert,
[Parameter(Mandatory = $True)]
[string]
$Pass
)

$flag = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
$collection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
$collection.Import($Cert, $Pass, $flag)
$pkcs12ContentType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12
$clearBytes = $collection.Export($pkcs12ContentType)
$fileContentEncoded = [System.Convert]::ToBase64String($clearBytes)
$secret = ConvertTo-SecureString -String $fileContentEncoded -AsPlainText –Force

Write-Host $fileContentEncoded
42 changes: 42 additions & 0 deletions build/sign-node.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[CmdletBinding()]
param (
[Parameter(Mandatory = $True)]
[string]
$ZipFile,
[Parameter(Mandatory = $True)]
[string]
$Cert,
[Parameter(Mandatory = $True)]
[string]
$Pass
)

$Location = Get-Location
Write-Host "Signing logic-node project at ``${ZipFile}``" -ForegroundColor Blue
$RepositoryRoot = Resolve-Path (Join-Path $PSScriptRoot '../')
$ZipFile = Resolve-Path $ZipFile
$Cert = Resolve-Path $Cert
$SdkDir = Join-path $PSScriptRoot 'tools/gira'
$DistDir = Resolve-Path (Join-Path $PSScriptRoot '../dist')

Write-Host "Repository: ``${RepositoryRoot}``"
Write-Host "SDK-Tools: ``${SdkDir}``"
Write-Host "Output: ``${DistDir}``"

$SignToolPath = Join-Path -Path $SdkDir -ChildPath 'SignLogicNodes.exe'

Set-Location $SdkDir

try {
$SignNodeArgs = @(
"${Cert}"
"${Pass}"
"${ZipFile}"
)

Write-Host "${SignToolPath} ${SignNodeArgs}" -ForegroundColor Magenta
. $SignToolPath $SignNodeArgs
}
finally {
Set-Location $Location
}
10 changes: 7 additions & 3 deletions dotnet/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@
</PropertyGroup>

<PropertyGroup>
<RootNamespace>Necati_Meral_Yahoo_De</RootNamespace>
<GiraDeveloperId>necati_meral_yahoo_de</GiraDeveloperId>
</PropertyGroup>

<PropertyGroup Label="Project classification">
<IsTestProject Condition="$(MSBuildProjectFullPath.Contains('test')) and ($(MSBuildProjectName.EndsWith('.Tests')) or $(MSBuildProjectName.EndsWith('.TestBase')))">true</IsTestProject>

<IsNodeProject Condition="$(MSBuildProjectFullPath.Contains('.Logic.')) and (!$(MSBuildProjectName.Contains('.Tests')))">true</IsNodeProject>

<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
</PropertyGroup>

<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Debug' and $(IsNodeProject) == 'true'">
<Exec Command="pwsh -NoProfile -ExecutionPolicy RemoteSigned -File &quot;$(MSBuildThisFileDirectory)..\build\pack-node.ps1&quot; -Project $(MSBuildProjectDirectory)\$(OutputPath) " />
</Target>
Expand Down Expand Up @@ -65,7 +66,7 @@
<Reference Include="LogicModule.Nodes.TestHelper">
<HintPath>$(SolutionDir)src\LogicNodesSDK\LogicModule.Nodes.TestHelper.dll</HintPath>
</Reference>

<PackageReference Include="coverlet.collector" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand All @@ -75,6 +76,7 @@
<PackageReference Include="xunit" Version="$(xUnitPackageVersion)" />
<PackageReference Include="xunit.extensibility.execution" Version="$(xUnitExtensibilityExecutionPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(xUnitRunnerVisualStudioPackageVersion)" />
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
</ItemGroup>

<PropertyGroup Label="Package versions">
Expand All @@ -97,6 +99,8 @@
<!-- xunit.runner.visualstudio https://www.nuget.org/packages/xunit.runner.visualstudio -->
<xUnitRunnerVisualstudioPackageVersion>2.4.1</xUnitRunnerVisualstudioPackageVersion>

<MoqPackageVersion>4.18.4</MoqPackageVersion>

</PropertyGroup>

<ProjectExtensions>
Expand Down
19 changes: 6 additions & 13 deletions dotnet/NecatiMeral.LogicNodes.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1EE7FB73-9
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NecatiMeral.LogicNodes.Tests", "test\NecatiMeral.LogicNodes.Tests\NecatiMeral.LogicNodes.Tests.csproj", "{8A4DFEEE-CC9F-467E-8903-ED93D272D884}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NecatiMeral.Logic.Doorbird", "src\NecatiMeral.Logic.Doorbird\NecatiMeral.Logic.Doorbird.csproj", "{01386FAD-6E3E-408C-9948-D61DA886C584}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Projektmappenelemente", "Projektmappenelemente", "{40954188-75ED-47A1-9E72-5B1B3F7EA544}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
logic-node.props = logic-node.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NecatiMeral.DoorbirdListener.Tests", "test\NecatiMeral.DoorbirdListener.Tests\NecatiMeral.DoorbirdListener.Tests.csproj", "{F3BC8C87-7DEA-432C-A66F-375BA31D1944}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NecatiMeral.Logic.ComfortOnline", "src\NecatiMeral.Logic.ComfortOnline\NecatiMeral.Logic.ComfortOnline.csproj", "{90731595-1712-4F0C-82C9-56408D26064E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -35,23 +33,18 @@ Global
{8A4DFEEE-CC9F-467E-8903-ED93D272D884}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A4DFEEE-CC9F-467E-8903-ED93D272D884}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A4DFEEE-CC9F-467E-8903-ED93D272D884}.Release|Any CPU.Build.0 = Release|Any CPU
{01386FAD-6E3E-408C-9948-D61DA886C584}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01386FAD-6E3E-408C-9948-D61DA886C584}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01386FAD-6E3E-408C-9948-D61DA886C584}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01386FAD-6E3E-408C-9948-D61DA886C584}.Release|Any CPU.Build.0 = Release|Any CPU
{F3BC8C87-7DEA-432C-A66F-375BA31D1944}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3BC8C87-7DEA-432C-A66F-375BA31D1944}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3BC8C87-7DEA-432C-A66F-375BA31D1944}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3BC8C87-7DEA-432C-A66F-375BA31D1944}.Release|Any CPU.Build.0 = Release|Any CPU
{90731595-1712-4F0C-82C9-56408D26064E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{90731595-1712-4F0C-82C9-56408D26064E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90731595-1712-4F0C-82C9-56408D26064E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90731595-1712-4F0C-82C9-56408D26064E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3F1FF768-FD27-46A2-961C-B6EFDF9CB3B3} = {B4F57F38-7B57-4225-9BD4-264ABC47DE33}
{8A4DFEEE-CC9F-467E-8903-ED93D272D884} = {1EE7FB73-9DEA-4D45-9CD0-2F4BFE480FF6}
{01386FAD-6E3E-408C-9948-D61DA886C584} = {B4F57F38-7B57-4225-9BD4-264ABC47DE33}
{F3BC8C87-7DEA-432C-A66F-375BA31D1944} = {1EE7FB73-9DEA-4D45-9CD0-2F4BFE480FF6}
{90731595-1712-4F0C-82C9-56408D26064E} = {B4F57F38-7B57-4225-9BD4-264ABC47DE33}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {471310B7-D205-4EEC-A063-0A534164AB9A}
Expand Down
15 changes: 15 additions & 0 deletions dotnet/src/NecatiMeral.Logic.ComfortOnline/ComfortOnlineConsts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Necati_Meral_Yahoo_De.Logic.ComfortOnline;
public static class ComfortOnlineConsts
{
public const string ComfortOnlineBaseAddress = "https://www.comfort-online.com/";

public static class ErrorCodes
{
public const string Ok = "Ok";
public const string InitialRequestFailed = "InitialRequestFailed";
public const string MissingRequestVerificationToken = "MissingRequestVerificationToken";
public const string InvalidCredentials = "InvalidCredentials";
public const string LoginFailed = "LoginFailed";
public const string UnexpectedError = "UnexpectedError: ";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Text.RegularExpressions;

namespace Necati_Meral_Yahoo_De.Logic.ComfortOnline;
public class ComfortOnlinePageParser
{
readonly Regex _spanRegex = new Regex("<span.*?(?:id=\\\"val_([\\d_]*)\\\")>(.*)<\\/span>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
readonly Regex _inputRegex = new Regex("<input.*?(?:id=\\\"slider_([\\d_]*)\\\".*?value=\\\"([^\\\"]+)|value=\\\"([^\\\"]+).*?id=\\\"slider_([\\d_]*)\\\")[^>]*>", RegexOptions.Compiled | RegexOptions.IgnoreCase);

public ComfortOnlinePlantSectionInfo Parse(string body)
{
var info = new ComfortOnlinePlantSectionInfo();

var spanMatches = _spanRegex.Matches(body);
foreach (Match match in spanMatches)
{
if(match.Groups.Count == 3)
{
info[match.Groups[1].Value] = match.Groups[2].Value;
}
}

var inputMatches = _inputRegex.Matches(body);
foreach (Match match in inputMatches)
{
if (match.Groups.Count == 5)
{
if (string.IsNullOrEmpty(match.Groups[1].Value))
{
info[match.Groups[3].Value] = match.Groups[4].Value;
}
else
{
info[match.Groups[1].Value] = match.Groups[2].Value;
}
}
}

return info;
}

public class ComfortOnlinePlantSectionInfo
{
Dictionary<string, string> _dict;

public IReadOnlyDictionary<string, string> Values => _dict;

public string this[string key]
{
get => _dict[key];
set => _dict[key] = value;
}

public ComfortOnlinePlantSectionInfo()
{
_dict = new Dictionary<string, string>();
}
}
}
153 changes: 153 additions & 0 deletions dotnet/src/NecatiMeral.Logic.ComfortOnline/ComfortOnlineRequestNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
using System.CodeDom;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using LogicModule.ObjectModel.TypeSystem;
using Necati_Meral_Yahoo_De.Helpers;
using Necati_Meral_Yahoo_De.Http;
using Necati_Meral_Yahoo_De.LogicNodes;

namespace Necati_Meral_Yahoo_De.Logic.ComfortOnline;
public class ComfortOnlineRequestNode : LocalizablePrefixLogicNodeBase
{
protected ITypeService TypeService { get; }
protected ComfortOnlinePageParser Parser { get; }
protected IHttpClient HttpClient { get; }

[Input(DisplayOrder = 1, IsRequired = true)]
public BoolValueObject Trigger { get; private set; }

[Input(IsDefaultShown = true, DisplayOrder = 2)]
public StringValueObject PlantId { get; }

[Input(IsDefaultShown = true, DisplayOrder = 3)]
public StringValueObject PlantSection { get; }

[Input(IsDefaultShown = false, DisplayOrder = 4)]
public StringValueObject UserName { get; }

[Input(IsDefaultShown = false, DisplayOrder = 5)]
public StringValueObject Password { get; }

[Output(IsDefaultShown = true, DisplayOrder = 1)]
public StringValueObject Data { get; private set; }

[Output(IsDefaultShown = false, DisplayOrder = 99)]
public StringValueObject Diagnostics { get; private set; }

public ComfortOnlineRequestNode(INodeContext context)
: base(context, LogicNodeConsts.InputPrefix)
{
context.ThrowIfNull("context");

TypeService = context.GetService<ITypeService>();
Parser = new ComfortOnlinePageParser();

Trigger = TypeService.CreateBool("BINARY", "Trigger", false);
PlantId = TypeService.CreateString("STRING", "PlantId", string.Empty);
PlantSection = TypeService.CreateString("STRING", "PlantSection", string.Empty);
UserName = TypeService.CreateString("STRING", "UserName", string.Empty);
Password = TypeService.CreateString("STRING", "Password", string.Empty);
Data = TypeService.CreateString("STRING", "Data", string.Empty);
Diagnostics = TypeService.CreateString("STRING", "Diagnostics", string.Empty);

HttpClient = CreateHttpClient();
}

public override void Execute()
{
if(Trigger.HasValue && Trigger.WasSet)
{
try
{
AsyncHelper.RunSync(ExecuteAsync);
}
catch (AggregateException ex)
{
Diagnostics.Value = $"{ComfortOnlineConsts.ErrorCodes.UnexpectedError} {ex.Message}";
throw ex.InnerException;
}
}
}

protected virtual async Task ExecuteAsync()
{
var loginSucceeded = await LoginAsync();
if (loginSucceeded)
{
var plantSectionResponse = await HttpClient.GetStringAsync($"/Measurand/Values?plant={PlantId.Value}&name={PlantSection.Value}");
var parsed = Parser.Parse(plantSectionResponse);

var entries = parsed.Values.Select(p => string.Format("\"{0}\": \"{1}\"", p.Key, string.Join(",", p.Value)));

Data.Value = $"{{{string.Join(", ", entries)}}}";
Diagnostics.Value = ComfortOnlineConsts.ErrorCodes.Ok;
}
}

protected virtual async Task<bool> LoginAsync()
{
string initialRequestBody;
try
{
initialRequestBody = await HttpClient.GetStringAsync("/Account/Login");
}
catch (Exception)
{
Diagnostics.Value = ComfortOnlineConsts.ErrorCodes.InitialRequestFailed;
return false;
}

var requestVerificationToken = GetRequestVerificationToken(initialRequestBody);
if (string.IsNullOrEmpty(requestVerificationToken))
{
Diagnostics.Value = ComfortOnlineConsts.ErrorCodes.MissingRequestVerificationToken;
return false;
}

try
{
var loginPage = await HttpClient.PostAsync("/Account/Login", new Dictionary<string, string>
{
{ "UserName", UserName.Value },
{ "Password", Password.Value },
{ "__RequestVerificationToken", requestVerificationToken }
});

// Basic detection if we got redirected to login page = login failed
if (loginPage.Contains("<form action=\"/account/login\""))
{
Diagnostics.Value = ComfortOnlineConsts.ErrorCodes.InvalidCredentials;
return false;
}
}
catch (Exception)
{
Diagnostics.Value = ComfortOnlineConsts.ErrorCodes.LoginFailed;
return false;
}

return true;
}

protected virtual IHttpClient CreateHttpClient()
{
var cookies = new CookieContainer();
var httpClient = new HttpClient(new HttpClientHandler { CookieContainer = cookies })
{
BaseAddress = new Uri(ComfortOnlineConsts.ComfortOnlineBaseAddress)
};

return new NetHttpClient(httpClient);
}

protected virtual string GetRequestVerificationToken(string body)
{
var matches = Regex.Match(body, "<input.*?(?:name=\\\"__RequestVerificationToken\\\".*?value=\\\"([^\"]+)|value=\\\"([^\"]+).*?name=\\\"__RequestVerificationToken\\\")[^>]*>");
if (matches.Success)
{
return matches.Groups[1].Value;
}
return string.Empty;
}
}
Loading

0 comments on commit f0b0f6c

Please sign in to comment.