diff --git a/.build/BuildToolkit.ps1 b/.build/BuildToolkit.ps1
index 3cfa888..20fcab0 100644
--- a/.build/BuildToolkit.ps1
+++ b/.build/BuildToolkit.ps1
@@ -1,8 +1,8 @@
# Tool Versions
-$NunitVersion = "3.11.1";
+$NunitVersion = "3.12.0";
$OpenCoverVersion = "4.7.922";
$DocFxVersion = "2.56.2";
-$ReportGeneratorVersion = "4.6.7";
+$ReportGeneratorVersion = "4.8.7";
# Folder Pathes
$RootPath = $MyInvocation.PSScriptRoot;
diff --git a/Directory.Build.targets b/Directory.Build.targets
index ed12867..0d17468 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -2,7 +2,7 @@
- 3.0.0
+ 3.2.0
@@ -24,9 +24,9 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/Directory.build.props b/Directory.build.props
index 69a3fff..b8c7941 100644
--- a/Directory.build.props
+++ b/Directory.build.props
@@ -1,6 +1,6 @@
- 8.0
+ 9.0
diff --git a/VERSION b/VERSION
index 13d683c..a0cd9f0 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.0.1
\ No newline at end of file
+3.1.0
\ No newline at end of file
diff --git a/src/Moryx.ClientFramework.Kernel/Configuration/LocalConfigProvider.cs b/src/Moryx.ClientFramework.Kernel/Configuration/LocalConfigProvider.cs
index bc16c94..1cc0b20 100644
--- a/src/Moryx.ClientFramework.Kernel/Configuration/LocalConfigProvider.cs
+++ b/src/Moryx.ClientFramework.Kernel/Configuration/LocalConfigProvider.cs
@@ -24,7 +24,7 @@ public LocalConfigProvider(IConfigManager configManager, ModulesConfiguration mo
_configManager = configManager;
}
- ///
+ ///
public T GetModuleConfiguration(string name) where T : class, IClientModuleConfig, new()
{
var config = GetConfiguration();
diff --git a/src/Moryx.ClientFramework.Kernel/Extensions/ApplicationRuntimeExtensions.cs b/src/Moryx.ClientFramework.Kernel/Extensions/ApplicationRuntimeExtensions.cs
new file mode 100644
index 0000000..0306a24
--- /dev/null
+++ b/src/Moryx.ClientFramework.Kernel/Extensions/ApplicationRuntimeExtensions.cs
@@ -0,0 +1,18 @@
+using Moryx.Identity;
+
+namespace Moryx.ClientFramework.Kernel
+{
+ ///
+ /// Extensions for the
+ ///
+ public static class ApplicationRuntimeExtensions
+ {
+ ///
+ /// Method to register a custom ClaimsAuthorizationManager
+ ///
+ public static void EnableAuthorization(this IApplicationRuntime hol, IAuthorizationContext authorizationContext)
+ {
+ IdentityConfiguration.CurrentContext = authorizationContext;
+ }
+ }
+}
diff --git a/src/Moryx.ClientFramework.Kernel/HeartOfLead.cs b/src/Moryx.ClientFramework.Kernel/HeartOfLead.cs
index b70d221..5da2783 100644
--- a/src/Moryx.ClientFramework.Kernel/HeartOfLead.cs
+++ b/src/Moryx.ClientFramework.Kernel/HeartOfLead.cs
@@ -15,6 +15,7 @@
using Caliburn.Micro;
using CommandLine;
using Moryx.ClientFramework.Localization;
+using Moryx.Configuration;
using Moryx.Container;
using Moryx.Logging;
using Moryx.Threading;
@@ -24,7 +25,7 @@
namespace Moryx.ClientFramework.Kernel
{
///
- /// Main class to create ClientFramwork UI's
+ /// Main class to create ClientFramework UIs
///
public class HeartOfLead : HeartOfLead
{
@@ -35,16 +36,21 @@ public HeartOfLead(string[] args) : base(args)
}
///
- /// Main class to create ClientFramwork UI's
+ /// Main class to create ClientFramework UIs
///
- public class HeartOfLead : ILoggingHost
+ public class HeartOfLead : IApplicationRuntime, ILoggingHost
where TCommandLineArguments : DefaultCommandLineArguments
{
#region Fields and Properties
string ILoggingHost.Name => "ClientKernel";
+
+ ///
IModuleLogger ILoggingHost.Logger { get; set; }
+ ///
+ IContainer IApplicationRuntime.GlobalContainer => _container;
+
///
/// Returns the current
///
@@ -58,7 +64,7 @@ public class HeartOfLead : ILoggingHost
///
/// Flag if the HeartOfLead is initialized
///
- public bool IsInitialied { get; private set; }
+ public bool IsInitialied { get; private set; } // TODO: Rename to IsInitialized in the next major
private GlobalContainer _container;
private IKernelConfigManager _configManager;
@@ -100,7 +106,7 @@ public void Initialize()
if (IsInitialied)
throw new InvalidOperationException("HeartOfLead is already initialized!");
- // Initialize platfrom
+ // Initialize platform
WpfPlatform.SetProduct();
// Attach this Application to the console.
@@ -331,7 +337,8 @@ private void LoadConfiguration()
// Configure config manager
_configManager = new KernelConfigManager { ConfigDirectory = CommandLineOptions.ConfigFolder };
- _container.SetInstance(_configManager);
+ _container.SetInstance(_configManager, "KernelConfigManager");
+ _container.SetInstance(_configManager, "ConfigManager");
// Load global app config
AppConfig = _configManager.GetConfiguration();
diff --git a/src/Moryx.ClientFramework.Kernel/RunMode/LocalRunMode.cs b/src/Moryx.ClientFramework.Kernel/RunMode/LocalRunMode.cs
index 5870424..208069e 100644
--- a/src/Moryx.ClientFramework.Kernel/RunMode/LocalRunMode.cs
+++ b/src/Moryx.ClientFramework.Kernel/RunMode/LocalRunMode.cs
@@ -25,7 +25,7 @@ protected override Predicate TypeLoadFilter
get { return type => type.GetCustomAttribute() == null; }
}
- ///
+ ///
public override void LoadModulesConfiguration()
{
var modulesConfig = ConfigManager.GetConfiguration();
diff --git a/src/Moryx.ClientFramework.Kernel/RunMode/LocalRunModeBase.cs b/src/Moryx.ClientFramework.Kernel/RunMode/LocalRunModeBase.cs
index 37e6b7a..5f8ee46 100644
--- a/src/Moryx.ClientFramework.Kernel/RunMode/LocalRunModeBase.cs
+++ b/src/Moryx.ClientFramework.Kernel/RunMode/LocalRunModeBase.cs
@@ -11,7 +11,7 @@
namespace Moryx.ClientFramework.Kernel
{
///
- /// Base class for local run modes.
+ /// Base class for local run modes.
/// Will load and from the app domain
///
public abstract class LocalRunModeBase : RunModeBase
@@ -21,7 +21,7 @@ public abstract class LocalRunModeBase : RunModeBase
///
/// Config manager to load kernel configurations
///
- public IKernelConfigManager ConfigManager { get; set; }
+ public IKernelConfigManager ConfigManager { get; set; } // TODO: Change type to IConfigManager in future
#endregion
@@ -75,7 +75,7 @@ public override void Initialize()
///
public override void LoadModulesConfiguration()
{
-
+
}
}
}
diff --git a/src/Moryx.ClientFramework/Moryx.ClientFramework.csproj.DotSettings b/src/Moryx.ClientFramework/Moryx.ClientFramework.csproj.DotSettings
index 1dc9c7a..66454ca 100644
--- a/src/Moryx.ClientFramework/Moryx.ClientFramework.csproj.DotSettings
+++ b/src/Moryx.ClientFramework/Moryx.ClientFramework.csproj.DotSettings
@@ -11,6 +11,7 @@
True
True
True
+ True
True
True
True
diff --git a/src/Moryx.ClientFramework/Principals/BooleanPermissionExtension.cs b/src/Moryx.ClientFramework/Principals/BooleanPermissionExtension.cs
new file mode 100644
index 0000000..d5b4471
--- /dev/null
+++ b/src/Moryx.ClientFramework/Principals/BooleanPermissionExtension.cs
@@ -0,0 +1,25 @@
+// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System.Windows.Markup;
+
+namespace Moryx.ClientFramework.Principals
+{
+ ///
+ /// Extension to determine the boolean result depends to the permission
+ ///
+ public class BooleanPermissionExtension : PermissionExtensionBase
+ {
+ ///
+ /// Flag to inverse the boolean result
+ ///
+ [ConstructorArgument("Inverse")]
+ public bool Inverse { get; set; }
+
+ ///
+ protected override object ProvidePermissionBasedValue(bool hasPermission)
+ {
+ return Inverse ? !hasPermission : hasPermission;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Moryx.ClientFramework/Principals/ClaimsPrincipalSync.cs b/src/Moryx.ClientFramework/Principals/ClaimsPrincipalSync.cs
new file mode 100644
index 0000000..acbf7c4
--- /dev/null
+++ b/src/Moryx.ClientFramework/Principals/ClaimsPrincipalSync.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System;
+
+namespace Moryx.ClientFramework.Principals
+{
+ ///
+ /// Helper to inform the UI about an update of the ClaimsPrincipal
+ ///
+ public static class ClaimsPrincipalSync
+ {
+ ///
+ /// Event to get informed about an update of the ClaimsPrincipal
+ ///
+ public static event EventHandler PrincipalChanged;
+
+ ///
+ /// Method to invoke an event after an update of the ClaimsPrincipal
+ ///
+ public static void OnClaimsPrincipalChanged()
+ {
+ PrincipalChanged?.Invoke(typeof(ClaimsPrincipalSync), EventArgs.Empty);
+ }
+ }
+}
diff --git a/src/Moryx.ClientFramework/Principals/GridLengthPermissionExtension.cs b/src/Moryx.ClientFramework/Principals/GridLengthPermissionExtension.cs
new file mode 100644
index 0000000..9b284a5
--- /dev/null
+++ b/src/Moryx.ClientFramework/Principals/GridLengthPermissionExtension.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System.Windows;
+
+namespace Moryx.ClientFramework.Principals
+{
+ ///
+ /// Extension to determine the length of a grid depends to the permission
+ ///
+ public class GridLengthPermissionExtension : PermissionExtensionBase
+ {
+ ///
+ protected override object ProvidePermissionBasedValue(bool hasPermission)
+ {
+ return hasPermission ? new GridLength(1, GridUnitType.Star) : new GridLength(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Moryx.ClientFramework/Principals/PermissionExtensionBase.cs b/src/Moryx.ClientFramework/Principals/PermissionExtensionBase.cs
new file mode 100644
index 0000000..b831436
--- /dev/null
+++ b/src/Moryx.ClientFramework/Principals/PermissionExtensionBase.cs
@@ -0,0 +1,112 @@
+// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Windows;
+using System.Windows.Markup;
+using System.Xaml;
+using Moryx.Identity;
+
+namespace Moryx.ClientFramework.Principals
+{
+ ///
+ /// Base class for permission based value determination
+ ///
+ public abstract class PermissionExtensionBase : MarkupExtension
+ {
+ #region Fields and Properties
+
+ private object _targetObject;
+
+ private object _targetProperty;
+
+ ///
+ /// Resource within the action requires permissions
+ ///
+ public string Resource { get; set; }
+
+ ///
+ /// The requested action which will be validated by the current permissions
+ ///
+ [ConstructorArgument("action")]
+ public string Action { get; set; }
+
+ #endregion
+
+ ///
+ /// Constructor to prepare the extension to get information about changed principals
+ ///
+ protected PermissionExtensionBase()
+ {
+ ClaimsPrincipalSync.PrincipalChanged += OnPrincipalChanged;
+ }
+
+ private void OnPrincipalChanged(object sender, EventArgs args)
+ {
+ if (!(_targetObject is DependencyObject targetObject))
+ return;
+
+ // Current determined value to update
+ var value = ProvidePermissionBasedValue(HasPermission());
+ if (_targetProperty is DependencyProperty targetProperty)
+ {
+ // Update directly if can be accessed otherwise invoke the dispatcher
+ if (targetObject.CheckAccess())
+ targetObject.SetValue(targetProperty, value);
+ else
+ targetObject.Dispatcher.Invoke(() => targetObject.SetValue(targetProperty, value));
+ }
+ else
+ {
+ var propertyInfo = _targetProperty as PropertyInfo;
+ propertyInfo?.SetValue(targetObject, value, null);
+ }
+ }
+
+ ///
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // If resource was not specified, tried to determine from host control
+ if (string.IsNullOrEmpty(Resource) && serviceProvider.GetService(typeof(IRootObjectProvider)) is IRootObjectProvider root)
+ {
+ // Try to read from root attached property
+ var rootElement = root.RootObject as DependencyObject;
+ var defaultResource = rootElement?.GetValue(PermissionProvider.DefaultResourceProperty);
+ if (defaultResource != null)
+ {
+ Resource = (string) defaultResource;
+ }
+
+ if (string.IsNullOrEmpty(Resource))
+ {
+ var regex = new Regex(@"^\w+\.\w+");
+ Resource = regex.Match(root.RootObject?.GetType().Namespace ?? "Moryx").Value;
+ }
+ }
+
+ if (serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget target)
+ {
+ _targetObject = target.TargetObject;
+ _targetProperty = target.TargetProperty;
+ }
+
+ var hasPermission = HasPermission();
+ return ProvidePermissionBasedValue(hasPermission);
+ }
+
+ private bool HasPermission()
+ {
+ if (IdentityConfiguration.CurrentContext != null)
+ return IdentityConfiguration.CurrentContext.CheckAccess(Resource, Action);
+
+ return true;
+ }
+
+ ///
+ /// Get the permission based value
+ ///
+ protected abstract object ProvidePermissionBasedValue(bool hasPermission);
+ }
+}
\ No newline at end of file
diff --git a/src/Moryx.ClientFramework/Principals/PermissionProvider.cs b/src/Moryx.ClientFramework/Principals/PermissionProvider.cs
new file mode 100644
index 0000000..13994ca
--- /dev/null
+++ b/src/Moryx.ClientFramework/Principals/PermissionProvider.cs
@@ -0,0 +1,35 @@
+// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System.Windows;
+
+namespace Moryx.ClientFramework.Principals
+{
+ ///
+ /// Class to provide attached dependency property for permission based authorization
+ ///
+ public class PermissionProvider : DependencyObject
+ {
+ ///
+ /// Property to handle the default resource for the
+ ///
+ public static readonly DependencyProperty DefaultResourceProperty = DependencyProperty.RegisterAttached(
+ "DefaultResource", typeof(string), typeof(PermissionProvider), new PropertyMetadata(default(string)));
+
+ ///
+ /// Sets the default resource
+ ///
+ public static void SetDefaultResource(DependencyObject element, string value)
+ {
+ element.SetValue(DefaultResourceProperty, value);
+ }
+
+ ///
+ /// Returns the default resource
+ ///
+ public static string GetDefaultResource(DependencyObject element)
+ {
+ return (string) element.GetValue(DefaultResourceProperty);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Moryx.ClientFramework/Principals/VisibilityPermissionExtension.cs b/src/Moryx.ClientFramework/Principals/VisibilityPermissionExtension.cs
new file mode 100644
index 0000000..90eb856
--- /dev/null
+++ b/src/Moryx.ClientFramework/Principals/VisibilityPermissionExtension.cs
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System.Windows;
+using System.Windows.Markup;
+
+namespace Moryx.ClientFramework.Principals
+{
+ ///
+ /// Extension to determine the visibility depends to the permission
+ ///
+ public class VisibilityPermissionExtension : PermissionExtensionBase
+ {
+ ///
+ /// Flag to inverse the visibility result
+ ///
+ [ConstructorArgument("Inverse")]
+ public bool Inverse { get; set; }
+
+ ///
+ protected override object ProvidePermissionBasedValue(bool hasPermission)
+ {
+ if (Inverse)
+ return hasPermission ? Visibility.Collapsed : Visibility.Visible;
+
+ return hasPermission ? Visibility.Visible : Visibility.Collapsed;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Moryx.WpfToolkit/Moryx.WpfToolkit.csproj.DotSettings b/src/Moryx.WpfToolkit/Moryx.WpfToolkit.csproj.DotSettings
index c3581f8..a6ba707 100644
--- a/src/Moryx.WpfToolkit/Moryx.WpfToolkit.csproj.DotSettings
+++ b/src/Moryx.WpfToolkit/Moryx.WpfToolkit.csproj.DotSettings
@@ -56,6 +56,7 @@
True
True
True
+ True
True
True
True
diff --git a/src/Moryx.WpfToolkit/PasswordBox/PasswordBoxHelper.cs b/src/Moryx.WpfToolkit/PasswordBox/PasswordBoxHelper.cs
new file mode 100644
index 0000000..777d551
--- /dev/null
+++ b/src/Moryx.WpfToolkit/PasswordBox/PasswordBoxHelper.cs
@@ -0,0 +1,107 @@
+// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Moryx.WpfToolkit
+{
+ ///
+ /// Helper class for the password box to bind the password
+ ///
+ public static class PasswordBoxHelper
+ {
+ ///
+ /// Attached property for the bound password
+ ///
+ public static readonly DependencyProperty BoundPassword =
+ DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxHelper), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));
+
+ ///
+ /// Attached property for the bind password
+ ///
+ public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
+ "BindPassword", typeof(bool), typeof(PasswordBoxHelper), new PropertyMetadata(false, OnBindPasswordChanged));
+
+ private static readonly DependencyProperty UpdatingPassword =
+ DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxHelper), new PropertyMetadata(false));
+
+ ///
+ /// Gets the value
+ ///
+ public static bool GetBindPassword(DependencyObject dp) =>
+ (bool)dp.GetValue(BindPassword);
+
+ ///
+ /// Sets the value
+ ///
+ public static void SetBindPassword(DependencyObject dp, bool value) =>
+ dp.SetValue(BindPassword, value);
+
+ ///
+ /// Gets the value
+ ///
+ public static string GetBoundPassword(DependencyObject dp) =>
+ (string)dp.GetValue(BoundPassword);
+
+ ///
+ /// Sets the value
+ ///
+ public static void SetBoundPassword(DependencyObject dp, string value) =>
+ dp.SetValue(BoundPassword, value);
+
+ private static bool GetUpdatingPassword(DependencyObject dp) =>
+ (bool)dp.GetValue(UpdatingPassword);
+
+ private static void SetUpdatingPassword(DependencyObject dp, bool value) =>
+ dp.SetValue(UpdatingPassword, value);
+
+ private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var box = d as PasswordBox;
+
+ // only handle this event when the property is attached to a PasswordBox
+ // and when the BindPassword attached property has been set to true
+ if (d == null || !GetBindPassword(d))
+ return;
+
+ // avoid recursive updating by ignoring the box's changed event
+ box.PasswordChanged -= HandlePasswordChanged;
+
+ var newPassword = (string)e.NewValue;
+
+ if (!GetUpdatingPassword(box))
+ box.Password = newPassword;
+
+ box.PasswordChanged += HandlePasswordChanged;
+ }
+
+ private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
+ {
+ // When the BindPassword attached property is set on a PasswordBox,
+ // start listening to its PasswordChanged event
+ if (dp is not PasswordBox box)
+ return;
+
+ var wasBound = (bool)e.OldValue;
+ var needToBind = (bool)e.NewValue;
+
+ if (wasBound)
+ box.PasswordChanged -= HandlePasswordChanged;
+
+ if (needToBind)
+ box.PasswordChanged += HandlePasswordChanged;
+ }
+
+ private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
+ {
+ var box = sender as PasswordBox;
+
+ // set a flag to indicate that we're updating the password
+ SetUpdatingPassword(box, true);
+ // push the new password into the BoundPassword property
+ SetBoundPassword(box, box.Password);
+ SetUpdatingPassword(box, false);
+ }
+ }
+}