diff --git a/.github/workflows/test-install.yml b/.github/workflows/test-install.yml
index 9eb0b10d4..2165aa47b 100644
--- a/.github/workflows/test-install.yml
+++ b/.github/workflows/test-install.yml
@@ -21,23 +21,16 @@ on:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
- # ubuntu18 was unstable at github (2022-07-06 - 2022-07-11)
- # test_ubuntu_18:
- # name: test build on ubuntu_18
- # runs-on: ubuntu-18.04
+ # # ubuntu18 was unstable at github (2022-07-06 - 2022-07-11)
+ # # does not seem to be supported by hithub anymore (2024-05-01)
+
+ # test_ubuntu_20:
+ # name: test build on ubuntu_20
+ # runs-on: ubuntu-20.04
# steps:
# - uses: actions/checkout@v3
# - name: do test install in case of merged pull request
- # run: cd /home/runner/work/firewall-orchestrator/firewall-orchestrator && ansible-playbook -e run_on_github=yes --skip-tags test site.yml -K
-
- test_ubuntu_20:
- name: test build on ubuntu_20
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v3
- - name: do test install in case of merged pull request
- run: cd /home/runner/work/firewall-orchestrator/firewall-orchestrator && ansible-playbook -e run_on_github=yes site.yml -K
-# run: cd /home/runner/work/firewall-orchestrator/firewall-orchestrator && ansible-playbook -e run_on_github=yes --skip-tags test site.yml -K
+ # run: cd /home/runner/work/firewall-orchestrator/firewall-orchestrator && ansible-playbook -e force_install=true site.yml -K
# test_ubuntu_22:
# name: test build on ubuntu_22
@@ -45,5 +38,13 @@ jobs:
# steps:
# - uses: actions/checkout@v3
# - name: do test install in case of merged pull request
- # run: cd /home/runner/work/firewall-orchestrator/firewall-orchestrator && ansible-playbook -e run_on_github=yes site.yml -K
- # run: cd /home/runner/work/firewall-orchestrator/firewall-orchestrator && ansible-playbook -e run_on_github=yes --skip-tags test site.yml -K
+ # run: cd /home/runner/work/firewall-orchestrator/firewall-orchestrator && ansible-playbook -e force_install=true site.yml -K
+
+ test_ubuntu_latest:
+ name: test build on ubuntu latest
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: do test install in case of merged pull request
+ run: cd /home/runner/work/firewall-orchestrator/firewall-orchestrator && ansible-playbook -e force_install=true site.yml -K
+
diff --git a/documentation/revision-history-develop.md b/documentation/revision-history-develop.md
index 0bb70508e..df39a94e6 100644
--- a/documentation/revision-history-develop.md
+++ b/documentation/revision-history-develop.md
@@ -202,3 +202,6 @@ bugfix release:
- fix demo managements (change import from deactivated to activated - does not affect test managements)
- upgrade to dotnet 8.0
- adding all imported modelling users to uiuser
+
+# 8.2.1 - xx.05.2024 DEVELOP
+- fix misleading login error message when authorisation is missing
diff --git a/inventory/group_vars/all.yml b/inventory/group_vars/all.yml
index 80fd8cfe8..19c999f6b 100644
--- a/inventory/group_vars/all.yml
+++ b/inventory/group_vars/all.yml
@@ -1,5 +1,5 @@
### general settings
-product_version: "8.2"
+product_version: "8.2.1"
ansible_user: "{{ lookup('env', 'USER') }}"
ansible_become_method: sudo
ansible_python_interpreter: /usr/bin/python3
@@ -22,7 +22,6 @@ sample_hostname: "{{ groups['sampleserver'].0 }}"
# upgrade - installs on top of an existing system preserving any existing data in ldap, database, api
installation_mode: new
install_syslog: true
-run_on_github: false
add_demo_data: true
api_docu: false
force_install: false
diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml
index 72f6eacad..5ec90ac8b 100644
--- a/roles/common/tasks/main.yml
+++ b/roles/common/tasks/main.yml
@@ -1,11 +1,10 @@
- block:
- - name: assert ansible version gt 2.13
+ - name: assert ansible version gt 2.12
fail:
msg: Ansible 2.13 or above is required
when: ansible_version.full is version('2.13', '<')
-
- name: check for existing main config file {{ fworch_conf_file }}
stat:
path: "{{ fworch_conf_file }}"
@@ -93,23 +92,10 @@
- There are upgradable OS packages available, please run OS upgrade before running FWORCH installer.
- Use "-e force_install=true" to overwrite this check and install anyway at your own risk.
when: |
- not force_install|bool and not run_on_github|bool and
+ not force_install|bool and
(ansible_facts['distribution'] == "Ubuntu" or ansible_facts['distribution'] == "Debian") and
upgradable_packages.stdout_lines|length > 1
-
- # - name: fix grub-efi (for github actions)
- # apt:
- # upgrade: dist
- # update_cache: true
- # when: ansible_facts['distribution'] == "Ubuntu" or ansible_facts['distribution'] == "Debian" and run_on_github|bool
-
- # - name: update operating system packages .deb based (for github actions)
- # apt:
- # upgrade: dist
- # update_cache: true
- # when: ansible_facts['distribution'] == "Ubuntu" or ansible_facts['distribution'] == "Debian" and run_on_github|bool
-
- name: update operating system packages .rpm based (untested)
yum:
upgrade: dist
diff --git a/roles/database/files/sql/idempotent/fworch-texts.sql b/roles/database/files/sql/idempotent/fworch-texts.sql
index 47eaf9131..ff371ded6 100644
--- a/roles/database/files/sql/idempotent/fworch-texts.sql
+++ b/roles/database/files/sql/idempotent/fworch-texts.sql
@@ -273,6 +273,8 @@ INSERT INTO txt VALUES ('permissions_text', 'German', 'Ihre Berechtigungen wur
INSERT INTO txt VALUES ('permissions_text', 'English', 'Your permissions have been changed. Re-login to update your permissions.');
INSERT INTO txt VALUES ('login_importer_error', 'German', 'Nutzer mit der Rolle "Importer" dürfen sich nicht an der Benutzeroberfläche anmelden. Diese Rolle dient einzig dem Importieren von eingebundenen Geräten.');
INSERT INTO txt VALUES ('login_importer_error', 'English', 'Users with role "importer" are not allowed to log into the user interface. The only purpose of this role is to import included devices.');
+INSERT INTO txt VALUES ('not_authorized', 'German', 'Authentisierung OK, aber keine Berechtigung/Authorisierung vorhanden.');
+INSERT INTO txt VALUES ('not_authorized', 'English', 'Authentication succeeded, but not authorized.');
-- navigation
INSERT INTO txt VALUES ('reporting', 'German', 'Reporting');
@@ -5747,11 +5749,11 @@ INSERT INTO txt VALUES ('H9011', 'English', 'An application is - from the perspe
INSERT INTO txt VALUES ('H9021', 'German', 'Verbindungen sind die Hauptbestandteile des Kommunikationsprofils. Es wird zwischen verschiedenen Arten von Verbindungen unterschieden:');
INSERT INTO txt VALUES ('H9021', 'English', 'Connections are the main components of the communication profile. There are different types of connections:');
INSERT INTO txt VALUES ('H9022', 'German', 'Schnittstellen: Sie dienen in erster Linie der Modellierung von (aus Sicht der Applikation) externen Verbindungen oder der Bündelung interner Objekte.
- Es müssen in der Applikation neben dem Dienst entweder Quelle oder Ziel definiert werden. Die Schnittstellen werden in den anderen Applikationen
- zur Auswahl angeboten und können dort in der Definition von eigenen Verbindungen verwendet werden.
+ Es müssen in der Applikation neben dem Dienst entweder Quelle oder Ziel definiert werden. Die Schnittstellen können durch Setzen des entsprechendenn Häkchens veröffentlicht und dadurch in den anderen Applikationen
+ zur Auswahl angeboten werden. Sie können dann dort in der Definition von eigenen Verbindungen verwendet werden.
');
INSERT INTO txt VALUES ('H9022', 'English', 'Interfaces: They serve primarily the modelling of (relative to the application) external connections or the bundling of internal objects.
- Besides the service either source or destination have to be defined in the application. The interfaces are offered to other applications to use
+ Besides the service either source or destination have to be defined in the application. The interfaces can be published by setting the respective flag and are then offered to other applications to use
them in the definition of own connections.
');
INSERT INTO txt VALUES ('H9023', 'German', 'Standard: Zentrale Objekte zur Modellierung der Kommunikationsverbindungen. Dabei müssen Quelle, Dienst und Ziel aus den in der Bibliothek
@@ -5810,3 +5812,31 @@ INSERT INTO txt VALUES ('H9043', 'German', 'Dienstgruppen: In Dienstgruppen k&o
INSERT INTO txt VALUES ('H9043', 'English', 'Service Groups: Simple services can be bundled in Service Groups. A name has to be given to them, comments can be added.
Again definition can be done by the modeller, but also Service Groups predefined by the administrator can be used.
');
+INSERT INTO txt VALUES ('H9051', 'German', 'Beantragung neuer Schnittstellen: Wenn externe Schnittstellen von anderen Applikationen benötigt werden, können diese über die entsprechende Schaltfläche in der Bibliothek beantragt werden.
+
+
Es erscheint ein Dialog, in dem die externe Applikation ausgewählt und eine Begründung eingetragen werden müssen, sowie das Häkchen, ob die Schnittstelle als Quelle oder Ziel genutzt werden soll.
+
Beim Abschicken der Anforderung wird
+
+
bei der externen Applikation automatisch eine Dummy-Schnittstelle angelegt, die dann in der eigenen Schnittstellen-Auswahl erscheint und direkt zur Erstellung eigener Verbindungen genutzt werden kann.
+ Sie wird in der Liste der eigenen Verbindungen mit Einträgen "Schnittstelle angefordert" in Quelle/Ziel und Dienst als solche gekennzeichnet.
+
der oder die für die externe Applikation Verantwortlichen per Email über den Antrag informiert.
+
im Workflow-Modul ein Ticket mit dem Antrag erstellt. Je nach Konfiguration des Workflows kann hier der Auftrag abgelehnt, an andere Applikationen weitergeleitet, einzelnen Bearbeitern zugewiesen oder mit Kommentaren versehen werden.
+
+
+
Wird die Schnittstelle auf der Gegenseite modelliert und veröffentlicht, wandelt sich auch die eigene nutzende Verbindung automatisch in eine "normale" Verbindung um, eine weiteres Eingreifen des Antragstellers ist nicht mehr notwendig.
+
+');
+INSERT INTO txt VALUES ('H9051', 'English', 'Request new interface: If external interfaces from other applications are needed, they can be requested via a button in the library.
+
+
A dialogue is displayed to select the external Application. A reason field has to be filled as well as the checkbox, if the interface should be used as source or destination.
+
If the request is submitted
+
+
a dummy interface is created automatically at the target application, which then appears in the own interface selection in the library and can be used for the definition of the own connection.
+ It is marked as such by the text "Interface requested" in Source/Destination and Service in the list of own the connections.
+
the responsible(s) of the external Application is informed about the request by email.
+
a ticket in the Workflow module is created. Depending on the configuration of the workflow, the request can be rejected, forwarded to other applications, assigned to aperson in charge or commented.
+
+
+
When the requested interface is modelled and published on the other side, the own using connection is changed to a "regular" connection automatically, further action is not necessary.
+
+');
diff --git a/roles/lib/files/FWO.Api.Client/APIcalls/modelling/updateConnectionOwner.graphql b/roles/lib/files/FWO.Api.Client/APIcalls/modelling/updateConnectionOwner.graphql
new file mode 100644
index 000000000..fdf9b53b6
--- /dev/null
+++ b/roles/lib/files/FWO.Api.Client/APIcalls/modelling/updateConnectionOwner.graphql
@@ -0,0 +1,12 @@
+mutation updateConnectionOwner(
+ $id: Int!
+ $appId: Int
+ ) {
+ update_modelling_connection_by_pk(
+ pk_columns: { id: $id }
+ _set: {
+ app_id: $appId
+ }) {
+ UpdatedId: id
+ }
+}
diff --git a/roles/lib/files/FWO.Api.Client/APIcalls/modelling/updateConnectionPublish.graphql b/roles/lib/files/FWO.Api.Client/APIcalls/modelling/updateConnectionPublish.graphql
new file mode 100644
index 000000000..9f5589d8d
--- /dev/null
+++ b/roles/lib/files/FWO.Api.Client/APIcalls/modelling/updateConnectionPublish.graphql
@@ -0,0 +1,14 @@
+mutation updateConnectionPublish(
+ $id: Int!
+ $isPublished: Boolean
+ $isRequested: Boolean
+ ) {
+ update_modelling_connection_by_pk(
+ pk_columns: { id: $id }
+ _set: {
+ is_requested: $isRequested
+ is_published: $isPublished
+ }) {
+ UpdatedId: id
+ }
+}
diff --git a/roles/lib/files/FWO.Api.Client/Data/StateMatrix.cs b/roles/lib/files/FWO.Api.Client/Data/StateMatrix.cs
index 097279f46..6a6f5e518 100644
--- a/roles/lib/files/FWO.Api.Client/Data/StateMatrix.cs
+++ b/roles/lib/files/FWO.Api.Client/Data/StateMatrix.cs
@@ -1,5 +1,4 @@
using FWO.Api.Client.Queries;
-using FWO.GlobalConstants;
using FWO.Api.Data;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
@@ -20,10 +19,10 @@ public enum WorkflowPhases
public class StateMatrix
{
[JsonProperty("matrix"), JsonPropertyName("matrix")]
- public Dictionary> Matrix { get; set; } = new ();
+ public Dictionary> Matrix { get; set; } = [];
[JsonProperty("derived_states"), JsonPropertyName("derived_states")]
- public Dictionary DerivedStates { get; set; } = new ();
+ public Dictionary DerivedStates { get; set; } = [];
[JsonProperty("lowest_input_state"), JsonPropertyName("lowest_input_state")]
public int LowestInputState { get; set; }
@@ -37,7 +36,7 @@ public class StateMatrix
[JsonProperty("active"), JsonPropertyName("active")]
public bool Active { get; set; }
- public Dictionary PhaseActive = new Dictionary();
+ public Dictionary PhaseActive = [];
public bool IsLastActivePhase = true;
public int MinImplTasksNeeded;
@@ -77,7 +76,7 @@ public bool getNextActivePhase(ref WorkflowPhases phase)
public List getAllowedTransitions(int stateIn)
{
- return Matrix.ContainsKey(stateIn) ? Matrix[stateIn] : new ();
+ return Matrix.ContainsKey(stateIn) ? Matrix[stateIn] : [];
}
public int getDerivedStateFromSubStates(List statesIn)
@@ -86,7 +85,7 @@ public int getDerivedStateFromSubStates(List statesIn)
{
return 0;
}
- int stateOut = 0;
+ int stateOut;
int backAssignedState = LowestInputState;
int initState = 0;
int inWorkState = LowestEndState;
@@ -160,7 +159,7 @@ public int getDerivedStateFromSubStates(List statesIn)
public class GlobalStateMatrix
{
[JsonProperty("config_value"), JsonPropertyName("config_value")]
- public Dictionary GlobalMatrix { get; set; } = new ();
+ public Dictionary GlobalMatrix { get; set; } = [];
public async Task Init(ApiConnection apiConnection, TaskType taskType = TaskType.master, bool reset = false)
@@ -198,11 +197,11 @@ public class GlobalStateMatrixHelper
public class StateMatrixDict
{
- public Dictionary Matrices { get; set; } = new Dictionary();
+ public Dictionary Matrices { get; set; } = [];
public async Task Init(WorkflowPhases phase, ApiConnection apiConnection)
{
- Matrices = new Dictionary();
+ Matrices = [];
foreach(TaskType taskType in Enum.GetValues(typeof(TaskType)))
{
Matrices.Add(taskType.ToString(), new StateMatrix());
diff --git a/roles/lib/files/FWO.Api.Client/FWO.Api.Client.csproj b/roles/lib/files/FWO.Api.Client/FWO.Api.Client.csproj
index 8ab351dda..9a5a1aefa 100644
--- a/roles/lib/files/FWO.Api.Client/FWO.Api.Client.csproj
+++ b/roles/lib/files/FWO.Api.Client/FWO.Api.Client.csproj
@@ -8,9 +8,9 @@
-
-
-
+
+
+
diff --git a/roles/lib/files/FWO.Api.Client/Queries/ModellingQueries.cs b/roles/lib/files/FWO.Api.Client/Queries/ModellingQueries.cs
index 1b6458b46..1cced1946 100644
--- a/roles/lib/files/FWO.Api.Client/Queries/ModellingQueries.cs
+++ b/roles/lib/files/FWO.Api.Client/Queries/ModellingQueries.cs
@@ -34,6 +34,8 @@ public class ModellingQueries : Queries
public static readonly string getCommonServices;
public static readonly string newConnection;
public static readonly string updateConnection;
+ public static readonly string updateConnectionOwner;
+ public static readonly string updateConnectionPublish;
public static readonly string deleteConnection;
public static readonly string addAppServerToConnection;
public static readonly string removeAppServerFromConnection;
@@ -122,6 +124,8 @@ static ModellingQueries()
getCommonServices = connectionDetailsFragment + File.ReadAllText(QueryPath + "modelling/getCommonServices.graphql");
newConnection = File.ReadAllText(QueryPath + "modelling/newConnection.graphql");
updateConnection = File.ReadAllText(QueryPath + "modelling/updateConnection.graphql");
+ updateConnectionOwner = File.ReadAllText(QueryPath + "modelling/updateConnectionOwner.graphql");
+ updateConnectionPublish = File.ReadAllText(QueryPath + "modelling/updateConnectionPublish.graphql");
deleteConnection = File.ReadAllText(QueryPath + "modelling/deleteConnection.graphql");
addAppServerToConnection = File.ReadAllText(QueryPath + "modelling/addAppServerToConnection.graphql");
removeAppServerFromConnection = File.ReadAllText(QueryPath + "modelling/removeAppServerFromConnection.graphql");
diff --git a/roles/lib/files/FWO.Report/FWO.Report.csproj b/roles/lib/files/FWO.Report/FWO.Report.csproj
index c2cdf7173..9a3a64891 100644
--- a/roles/lib/files/FWO.Report/FWO.Report.csproj
+++ b/roles/lib/files/FWO.Report/FWO.Report.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/roles/middleware/files/FWO.Middleware.Server/AppDataImport.cs b/roles/middleware/files/FWO.Middleware.Server/AppDataImport.cs
index 38bd17291..681fbc55d 100644
--- a/roles/middleware/files/FWO.Middleware.Server/AppDataImport.cs
+++ b/roles/middleware/files/FWO.Middleware.Server/AppDataImport.cs
@@ -263,29 +263,31 @@ private async Task AddAllGroupMembersToUiUser(string userGroupDn)
{
foreach (string memberDn in ldap.GetGroupMembers(userGroupDn))
{
- await UiUserHandler.UpsertUiUser(apiConnection, await ConvertLdapToUiUser(apiConnection, memberDn), false);
+ UiUser? uiUser = await ConvertLdapToUiUser(apiConnection, memberDn);
+ if(uiUser != null)
+ {
+ await UiUserHandler.UpsertUiUser(apiConnection, uiUser, false);
+ }
}
- }
+ }
}
- private async Task ConvertLdapToUiUser(ApiConnection apiConnection, string userDn)
+ private async Task ConvertLdapToUiUser(ApiConnection apiConnection, string userDn)
{
// add the modelling user to local uiuser table for later ref to email address
- UiUser uiUser = new();
-
// find the user in all connected ldaps
foreach (Ldap ldap in connectedLdaps)
{
- if (!ldap.UserSearchPath.IsNullOrEmpty() && userDn.ToLower().Contains(ldap.UserSearchPath.ToLower()))
+ if (!ldap.UserSearchPath.IsNullOrEmpty() && userDn.ToLower().Contains(ldap.UserSearchPath!.ToLower()))
{
- LdapEntry ldapUser = ldap.GetUserDetailsFromLdap(userDn);
+ LdapEntry? ldapUser = ldap.GetUserDetailsFromLdap(userDn);
if (ldapUser != null)
{
// add data from ldap entry to uiUser
- uiUser = new()
+ return new()
{
- LdapConnection = new UiLdapConnection(),
+ LdapConnection = new UiLdapConnection(){ Id = ldap.Id },
Dn = ldapUser.Dn,
Name = ldap.GetName(ldapUser),
Firstname = ldap.GetFirstName(ldapUser),
@@ -293,19 +295,15 @@ private async Task ConvertLdapToUiUser(ApiConnection apiConnection, stri
Email = ldap.GetEmail(ldapUser),
Tenant = await DeriveTenantFromLdap(ldap, ldapUser)
};
- uiUser.LdapConnection.Id = ldap.Id;
- return uiUser;
}
}
}
- return uiUser;
-
+ return null;
}
private async Task DeriveTenantFromLdap(Ldap ldap, LdapEntry ldapUser)
{
// try to derive the the user's tenant from the ldap settings
-
Tenant tenant = new()
{
Id = GlobalConst.kTenant0Id // default: tenant0 (id=1)
@@ -325,7 +323,7 @@ private async Task DeriveTenantFromLdap(Ldap ldap, LdapEntry ldapUser)
{
if (!ldap.GlobalTenantName.IsNullOrEmpty())
{
- tenantName = ldap.GlobalTenantName;
+ tenantName = ldap.GlobalTenantName ?? "";
}
}
@@ -336,9 +334,7 @@ private async Task DeriveTenantFromLdap(Ldap ldap, LdapEntry ldapUser)
tenant.Id = tenants[0].Id;
}
}
-
return tenant;
-
}
private string CreateUserGroup(ModellingImportAppData incomingApp)
@@ -403,9 +399,31 @@ private string UpdateUserGroup(ModellingImportAppData incomingApp, string groupD
internalLdap.RemoveUserFromEntry(member, groupDn);
}
}
+ UpdateRoles(groupDn);
return groupDn;
}
+ private void UpdateRoles(string groupDn)
+ {
+ List roles = internalLdap.GetRoles([groupDn]);
+ if(!roles.Contains(Roles.Modeller))
+ {
+ internalLdap.AddUserToEntry(groupDn, modellerRoleDn);
+ }
+ if(!roles.Contains(Roles.Requester))
+ {
+ internalLdap.AddUserToEntry(groupDn, requesterRoleDn);
+ }
+ if(!roles.Contains(Roles.Implementer))
+ {
+ internalLdap.AddUserToEntry(groupDn, implementerRoleDn);
+ }
+ if(!roles.Contains(Roles.Reviewer))
+ {
+ internalLdap.AddUserToEntry(groupDn, reviewerRoleDn);
+ }
+ }
+
private async Task ImportAppServers(ModellingImportAppData incomingApp, int applId)
{
int successCounter = 0;
diff --git a/roles/middleware/files/FWO.Middleware.Server/Ldap.cs b/roles/middleware/files/FWO.Middleware.Server/Ldap.cs
index c5779e5f9..376e1ab15 100644
--- a/roles/middleware/files/FWO.Middleware.Server/Ldap.cs
+++ b/roles/middleware/files/FWO.Middleware.Server/Ldap.cs
@@ -38,9 +38,9 @@ private LdapConnection Connect()
{
try
{
- LdapConnectionOptions ldapOptions = new LdapConnectionOptions();
+ LdapConnectionOptions ldapOptions = new ();
if (Tls) ldapOptions.ConfigureRemoteCertificateValidationCallback((object sen, X509Certificate? cer, X509Chain? cha, SslPolicyErrors err) => true); // todo: allow real cert validation
- LdapConnection connection = new LdapConnection(ldapOptions) { SecureSocketLayer = Tls, ConnectionTimeout = timeOutInMs };
+ LdapConnection connection = new (ldapOptions) { SecureSocketLayer = Tls, ConnectionTimeout = timeOutInMs };
connection.Connect(Address, Port);
return connection;
@@ -79,18 +79,16 @@ private static bool TryBind(LdapConnection connection, string user, string passw
///
public void TestConnection()
{
- using (LdapConnection connection = Connect())
- {
- if (!string.IsNullOrEmpty(SearchUser))
- {
- if (!TryBind(connection, SearchUser, SearchUserPwd)) throw new Exception("Binding failed for search user");
- }
- if (!string.IsNullOrEmpty(WriteUser))
- {
- if (!TryBind(connection, WriteUser, WriteUserPwd)) throw new Exception("Binding failed for write user");
- }
- }
- }
+ using LdapConnection connection = Connect();
+ if (!string.IsNullOrEmpty(SearchUser))
+ {
+ if (!TryBind(connection, SearchUser, SearchUserPwd)) throw new Exception("Binding failed for search user");
+ }
+ if (!string.IsNullOrEmpty(WriteUser))
+ {
+ if (!TryBind(connection, WriteUser, WriteUserPwd)) throw new Exception("Binding failed for write user");
+ }
+ }
private string GetUserSearchFilter(string searchPattern)
{
@@ -111,7 +109,7 @@ private string GetUserSearchFilter(string searchPattern)
userFilter = "(&(|(objectclass=user)(objectclass=person)(objectclass=inetOrgPerson)(objectclass=organizationalPerson))(!(objectclass=computer)))";
searchFilter = $"(|(cn={searchPattern})(uid={searchPattern})(userPrincipalName={searchPattern})(mail={searchPattern}))";
}
- return ((searchPattern == null || searchPattern == "") ? userFilter : $"(&{userFilter}{searchFilter})");
+ return (searchPattern == null || searchPattern == "") ? userFilter : $"(&{userFilter}{searchFilter})";
}
private string GetGroupSearchFilter(string searchPattern)
@@ -145,63 +143,60 @@ private string GetGroupSearchFilter(string searchPattern)
Log.WriteDebug("User Validation", $"Validating User: \"{user.Name}\" ...");
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- TryBind(connection, SearchUser, SearchUserPwd);
-
- LdapSearchConstraints cons = connection.SearchConstraints;
- cons.ReferralFollowing = true;
- connection.Constraints = cons;
-
- List possibleUserEntries = new List();
-
- // If dn was already provided
- if (!user.Dn.IsNullOrEmpty())
- {
- // Try to read user entry directly
- LdapEntry? userEntry = connection.Read(user.Dn);
- if (userEntry != null)
- {
- possibleUserEntries.Add(userEntry);
- }
- }
- else // Dn was not provided, search for user name
- {
- string[] attrList = new string[] { "*", "memberof" };
- string userSearchFilter = GetUserSearchFilter(user.Name);
-
- // Search for users in ldap with same name as user to validate
- possibleUserEntries = ((LdapSearchResults)connection.Search(
- UserSearchPath, // top-level path under which to search for user
- LdapConnection.ScopeSub, // search all levels beneath
- userSearchFilter,
- attrList,
- typesOnly: false
- )).ToList();
- }
-
- // If credentials are not checked return user that was found first
- // It could happen that multiple users with the same name were found (impossible if dn was provided)
- if (!validateCredentials && possibleUserEntries.Count > 0)
- {
- return possibleUserEntries.First();
- }
- // If credentials should be checked
- else if (validateCredentials)
- {
- // Multiple users with the same name could have been found (impossible if dn was provided)
- foreach (LdapEntry possibleUserEntry in possibleUserEntries)
- {
- // Check credentials - if multiple users were found and the credentials are valid this is most definitely the correct user
- if (CredentialsValid(connection, possibleUserEntry.Dn, user.Password))
- {
- return possibleUserEntry;
- }
- }
- }
- }
- }
+ using LdapConnection connection = Connect();
+ TryBind(connection, SearchUser, SearchUserPwd);
+
+ LdapSearchConstraints cons = connection.SearchConstraints;
+ cons.ReferralFollowing = true;
+ connection.Constraints = cons;
+
+ List possibleUserEntries = [];
+
+ // If dn was already provided
+ if (!user.Dn.IsNullOrEmpty())
+ {
+ // Try to read user entry directly
+ LdapEntry? userEntry = connection.Read(user.Dn);
+ if (userEntry != null)
+ {
+ possibleUserEntries.Add(userEntry);
+ }
+ }
+ else // Dn was not provided, search for user name
+ {
+ string[] attrList = ["*", "memberof"];
+ string userSearchFilter = GetUserSearchFilter(user.Name);
+
+ // Search for users in ldap with same name as user to validate
+ possibleUserEntries = ((LdapSearchResults)connection.Search(
+ UserSearchPath, // top-level path under which to search for user
+ LdapConnection.ScopeSub, // search all levels beneath
+ userSearchFilter,
+ attrList,
+ typesOnly: false
+ )).ToList();
+ }
+
+ // If credentials are not checked return user that was found first
+ // It could happen that multiple users with the same name were found (impossible if dn was provided)
+ if (!validateCredentials && possibleUserEntries.Count > 0)
+ {
+ return possibleUserEntries.First();
+ }
+ // If credentials should be checked
+ else if (validateCredentials)
+ {
+ // Multiple users with the same name could have been found (impossible if dn was provided)
+ foreach (LdapEntry possibleUserEntry in possibleUserEntries)
+ {
+ // Check credentials - if multiple users were found and the credentials are valid this is most definitely the correct user
+ if (CredentialsValid(connection, possibleUserEntry.Dn, user.Password))
+ {
+ return possibleUserEntry;
+ }
+ }
+ }
+ }
catch (LdapException ldapException)
{
Log.WriteInfo("Ldap entry exception", $"Ldap entry search at \"{Address}:{Port}\" lead to exception: {ldapException.Message}");
@@ -215,34 +210,24 @@ private string GetGroupSearchFilter(string searchPattern)
return null;
}
+ ///
+ /// Get the LdapEntry for the given user by Dn
+ ///
+ /// LdapEntry for the given user if found
public LdapEntry? GetUserDetailsFromLdap(string distinguishedName)
{
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- TryBind(connection, SearchUser, SearchUserPwd);
+ using LdapConnection connection = Connect();
+ TryBind(connection, SearchUser, SearchUserPwd);
- LdapSearchConstraints cons = connection.SearchConstraints;
- cons.ReferralFollowing = true;
- connection.Constraints = cons;
+ LdapSearchConstraints cons = connection.SearchConstraints;
+ cons.ReferralFollowing = true;
+ connection.Constraints = cons;
- List possibleUserEntries = [];
-
- // Try to read user entry directly
- LdapEntry? userEntry = connection.Read(distinguishedName);
- if (userEntry != null)
- {
- possibleUserEntries.Add(userEntry);
- }
-
- if (possibleUserEntries.Count > 0)
- {
- return possibleUserEntries.First();
- }
- }
- }
+ // Try to read user entry directly
+ return connection.Read(distinguishedName);
+ }
catch (LdapException ldapException)
{
Log.WriteInfo("Ldap entry exception", $"Ldap entry search at \"{Address}:{Port}\" lead to exception: {ldapException.Message}");
@@ -351,7 +336,7 @@ public List GetGroups(LdapEntry user)
// - Probably this doesn't work for nested groups.
// - Some systems may only save the "primaryGroupID", then we would have to resolve the name.
// - Some others may force us to look into all groups to find the membership.
- List groups = new List();
+ List groups = [];
foreach (var attribute in user.GetAttributeSet())
{
if (attribute.Name.ToLower() == "memberof")
@@ -376,25 +361,22 @@ public string ChangePassword(string userDn, string oldPassword, string newPasswo
{
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- // Try to authenticate as user with old password
- if (TryBind(connection, userDn, oldPassword))
- {
- // authentication was successful (user is bound): set new password
- LdapAttribute attribute = new("userPassword", newPassword);
- LdapModification[] mods = { new LdapModification(LdapModification.Replace, attribute) };
-
- connection.Modify(userDn, mods);
- Log.WriteDebug("Change password", $"Password for user {userDn} changed in {Address}:{Port}");
- }
- else
- {
- return "wrong old password";
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Try to authenticate as user with old password
+ if (TryBind(connection, userDn, oldPassword))
+ {
+ // authentication was successful (user is bound): set new password
+ LdapAttribute attribute = new("userPassword", newPassword);
+ LdapModification[] mods = [new LdapModification(LdapModification.Replace, attribute)];
+
+ connection.Modify(userDn, mods);
+ Log.WriteDebug("Change password", $"Password for user {userDn} changed in {Address}:{Port}");
+ }
+ else
+ {
+ return "wrong old password";
+ }
+ }
catch (Exception exception)
{
return exception.Message;
@@ -410,24 +392,21 @@ public string SetPassword(string userDn, string newPassword)
{
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- if (TryBind(connection, WriteUser, WriteUserPwd))
- {
- // authentication was successful: set new password
- LdapAttribute attribute = new LdapAttribute("userPassword", newPassword);
- LdapModification[] mods = { new LdapModification(LdapModification.Replace, attribute) };
-
- connection.Modify(userDn, mods);
- Log.WriteDebug("Change password", $"Password for user {userDn} changed in {Address}:{Port}");
- }
- else
- {
- return "error in write user authentication";
- }
- }
- }
+ using LdapConnection connection = Connect();
+ if (TryBind(connection, WriteUser, WriteUserPwd))
+ {
+ // authentication was successful: set new password
+ LdapAttribute attribute = new ("userPassword", newPassword);
+ LdapModification[] mods = [new LdapModification(LdapModification.Replace, attribute)];
+
+ connection.Modify(userDn, mods);
+ Log.WriteDebug("Change password", $"Password for user {userDn} changed in {Address}:{Port}");
+ }
+ else
+ {
+ return "error in write user authentication";
+ }
+ }
catch (Exception exception)
{
return exception.Message;
@@ -462,46 +441,43 @@ private List GetMemberships(List dnList, string? searchPath)
{
try
{
- // Connect to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as search user
- TryBind(connection, SearchUser, AesEnc.Decrypt(SearchUserPwd, AesEnc.GetMainKey()));
-
- // Search for Ldap roles / groups in given directory
- int searchScope = LdapConnection.ScopeSub; // TODO: Correct search scope?
- string searchFilter = $"(&(objectClass=groupOfUniqueNames)(cn=*))";
- LdapSearchResults searchResults = (LdapSearchResults)connection.Search(searchPath, searchScope, searchFilter, null, false);
-
- // convert dnList to lower case to avoid case problems
- dnList = dnList.ConvertAll(dn => dn.ToLower());
-
- // Foreach found role / group
- foreach (LdapEntry entry in searchResults)
- {
- Log.WriteDebug("Ldap Roles/Groups", $"Try to get roles / groups from ldap entry {entry.GetAttribute("cn").StringValue}");
-
- // Get dn of users having current role / group
- LdapAttribute members = entry.GetAttribute("uniqueMember");
- string[] memberDn = members.StringValueArray;
-
- // Foreach user
- foreach (string currentDn in memberDn)
- {
- Log.WriteDebug("Ldap Roles/Groups", $"Checking if current Dn: \"{currentDn}\" is user Dn. Then user has current role / group.");
-
- // Check if current user dn is matching with given user dn => Given user has current role / group
- if (dnList.Contains(currentDn.ToLower()))
- {
- // Get name and add it to list of roles / groups of given user
- string name = entry.GetAttribute("cn").StringValue;
- userMemberships.Add(name);
- break;
- }
- }
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as search user
+ TryBind(connection, SearchUser, AesEnc.Decrypt(SearchUserPwd, AesEnc.GetMainKey()));
+
+ // Search for Ldap roles / groups in given directory
+ int searchScope = LdapConnection.ScopeSub; // TODO: Correct search scope?
+ string searchFilter = $"(&(objectClass=groupOfUniqueNames)(cn=*))";
+ LdapSearchResults searchResults = (LdapSearchResults)connection.Search(searchPath, searchScope, searchFilter, null, false);
+
+ // convert dnList to lower case to avoid case problems
+ dnList = dnList.ConvertAll(dn => dn.ToLower());
+
+ // Foreach found role / group
+ foreach (LdapEntry entry in searchResults)
+ {
+ Log.WriteDebug("Ldap Roles/Groups", $"Try to get roles / groups from ldap entry {entry.GetAttribute("cn").StringValue}");
+
+ // Get dn of users having current role / group
+ LdapAttribute members = entry.GetAttribute("uniqueMember");
+ string[] memberDn = members.StringValueArray;
+
+ // Foreach user
+ foreach (string currentDn in memberDn)
+ {
+ Log.WriteDebug("Ldap Roles/Groups", $"Checking if current Dn: \"{currentDn}\" is user Dn. Then user has current role / group.");
+
+ // Check if current user dn is matching with given user dn => Given user has current role / group
+ if (dnList.Contains(currentDn.ToLower()))
+ {
+ // Get name and add it to list of roles / groups of given user
+ string name = entry.GetAttribute("cn").StringValue;
+ userMemberships.Add(name);
+ break;
+ }
+ }
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to get memberships", exception);
@@ -525,36 +501,33 @@ public List GetAllRoles()
{
try
{
- // Connect to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as search user
- TryBind(connection, SearchUser, SearchUserPwd);
-
- // Search for Ldap roles in given directory
- int searchScope = LdapConnection.ScopeSub; // TODO: Correct search scope?
- string searchFilter = $"(&(objectClass=groupOfUniqueNames)(cn=*))";
- LdapSearchResults searchResults = (LdapSearchResults)connection.Search(RoleSearchPath, searchScope, searchFilter, null, false);
-
- // Foreach found role
- foreach (LdapEntry entry in searchResults)
- {
- List attributes = [];
- string roleDesc = entry.GetAttribute("description").StringValue;
- attributes.Add(new RoleAttribute() { Key = "description", Value = roleDesc });
-
- string[] roleMemberDn = entry.GetAttribute("uniqueMember").StringValueArray;
- foreach (string currentDn in roleMemberDn)
- {
- if (currentDn != "")
- {
- attributes.Add(new RoleAttribute() { Key = "user", Value = currentDn });
- }
- }
- roleUsers.Add(new RoleGetReturnParameters() { Role = entry.Dn, Attributes = attributes });
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as search user
+ TryBind(connection, SearchUser, SearchUserPwd);
+
+ // Search for Ldap roles in given directory
+ int searchScope = LdapConnection.ScopeSub; // TODO: Correct search scope?
+ string searchFilter = $"(&(objectClass=groupOfUniqueNames)(cn=*))";
+ LdapSearchResults searchResults = (LdapSearchResults)connection.Search(RoleSearchPath, searchScope, searchFilter, null, false);
+
+ // Foreach found role
+ foreach (LdapEntry entry in searchResults)
+ {
+ List attributes = [];
+ string roleDesc = entry.GetAttribute("description").StringValue;
+ attributes.Add(new () { Key = "description", Value = roleDesc });
+
+ string[] roleMemberDn = entry.GetAttribute("uniqueMember").StringValueArray;
+ foreach (string currentDn in roleMemberDn)
+ {
+ if (currentDn != "")
+ {
+ attributes.Add(new () { Key = "user", Value = currentDn });
+ }
+ }
+ roleUsers.Add(new RoleGetReturnParameters() { Role = entry.Dn, Attributes = attributes });
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to get all roles", exception);
@@ -572,22 +545,19 @@ public List GetAllGroups(string searchPattern)
List allGroups = [];
try
{
- // Connect to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as search user
- TryBind(connection, SearchUser, SearchUserPwd);
-
- // Search for Ldap groups in given directory
- int searchScope = LdapConnection.ScopeSub;
- LdapSearchResults searchResults = (LdapSearchResults)connection.Search(GroupSearchPath, searchScope, GetGroupSearchFilter(searchPattern), null, false);
-
- foreach (LdapEntry entry in searchResults)
- {
- allGroups.Add(entry.Dn);
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as search user
+ TryBind(connection, SearchUser, SearchUserPwd);
+
+ // Search for Ldap groups in given directory
+ int searchScope = LdapConnection.ScopeSub;
+ LdapSearchResults searchResults = (LdapSearchResults)connection.Search(GroupSearchPath, searchScope, GetGroupSearchFilter(searchPattern), null, false);
+
+ foreach (LdapEntry entry in searchResults)
+ {
+ allGroups.Add(entry.Dn);
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to get all groups", exception);
@@ -605,36 +575,33 @@ public List GetAllInternalGroups()
try
{
- // Connect to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as search user
- TryBind(connection, SearchUser, SearchUserPwd);
-
- // Search for Ldap groups in given directory
- int searchScope = LdapConnection.ScopeSub;
- LdapSearchResults searchResults = (LdapSearchResults)connection.Search(GroupSearchPath, searchScope, GetGroupSearchFilter(""), null, false);
-
- foreach (LdapEntry entry in searchResults)
- {
- List members = [];
- string[] groupMemberDn = entry.GetAttribute("uniqueMember").StringValueArray;
- foreach (string currentDn in groupMemberDn)
- {
- if (currentDn != "")
- {
- members.Add(currentDn);
- }
- }
- allGroups.Add(new GroupGetReturnParameters()
- {
- GroupDn = entry.Dn,
- Members = members,
- OwnerGroup = entry.GetAttributeSet().ContainsKey("businessCategory") && entry.GetAttribute("businessCategory").StringValue.Equals("ownergroup", StringComparison.CurrentCultureIgnoreCase)
- });
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as search user
+ TryBind(connection, SearchUser, SearchUserPwd);
+
+ // Search for Ldap groups in given directory
+ int searchScope = LdapConnection.ScopeSub;
+ LdapSearchResults searchResults = (LdapSearchResults)connection.Search(GroupSearchPath, searchScope, GetGroupSearchFilter(""), null, false);
+
+ foreach (LdapEntry entry in searchResults)
+ {
+ List members = [];
+ string[] groupMemberDn = entry.GetAttribute("uniqueMember").StringValueArray;
+ foreach (string currentDn in groupMemberDn)
+ {
+ if (currentDn != "")
+ {
+ members.Add(currentDn);
+ }
+ }
+ allGroups.Add(new GroupGetReturnParameters()
+ {
+ GroupDn = entry.Dn,
+ Members = members,
+ OwnerGroup = entry.GetAttributeSet().ContainsKey("businessCategory") && entry.GetAttribute("businessCategory").StringValue.Equals("ownergroup", StringComparison.CurrentCultureIgnoreCase)
+ });
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to get all internal groups", exception);
@@ -654,26 +621,23 @@ public List GetGroupMembers(string groupDn)
{
try
{
- // Connect to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as search user
- TryBind(connection, SearchUser, SearchUserPwd);
- LdapEntry entry = connection.Read(groupDn);
-
- if (entry != null)
- {
- string[] groupMemberDn = entry.GetAttribute("uniqueMember").StringValueArray;
- foreach (string currentDn in groupMemberDn)
- {
- if (currentDn != "")
- {
- allMembers.Add(currentDn);
- }
- }
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as search user
+ TryBind(connection, SearchUser, SearchUserPwd);
+ LdapEntry entry = connection.Read(groupDn);
+
+ if (entry != null)
+ {
+ string[] groupMemberDn = entry.GetAttribute("uniqueMember").StringValueArray;
+ foreach (string currentDn in groupMemberDn)
+ {
+ if (currentDn != "")
+ {
+ allMembers.Add(currentDn);
+ }
+ }
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", $"Unexpected error while trying to get all group members of group {groupDn}", exception);
@@ -693,32 +657,29 @@ public List GetAllUsers(string searchPattern)
try
{
- // Connect to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as search user
- TryBind(connection, SearchUser, SearchUserPwd);
-
- // Search for Ldap users in given directory
- int searchScope = LdapConnection.ScopeSub;
-
- LdapSearchConstraints cons = connection.SearchConstraints;
- cons.ReferralFollowing = true;
- connection.Constraints = cons;
-
- LdapSearchResults searchResults = (LdapSearchResults)connection.Search(UserSearchPath, searchScope, GetUserSearchFilter(searchPattern), null, false);
-
- foreach (LdapEntry entry in searchResults)
- {
- allUsers.Add(new LdapUserGetReturnParameters()
- {
- UserDn = entry.Dn,
- Email = entry.GetAttributeSet().ContainsKey("mail") ? entry.GetAttribute("mail").StringValue : null
- // add first and last name of user
- });
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as search user
+ TryBind(connection, SearchUser, SearchUserPwd);
+
+ // Search for Ldap users in given directory
+ int searchScope = LdapConnection.ScopeSub;
+
+ LdapSearchConstraints cons = connection.SearchConstraints;
+ cons.ReferralFollowing = true;
+ connection.Constraints = cons;
+
+ LdapSearchResults searchResults = (LdapSearchResults)connection.Search(UserSearchPath, searchScope, GetUserSearchFilter(searchPattern), null, false);
+
+ foreach (LdapEntry entry in searchResults)
+ {
+ allUsers.Add(new LdapUserGetReturnParameters()
+ {
+ UserDn = entry.Dn,
+ Email = entry.GetAttributeSet().ContainsKey("mail") ? entry.GetAttribute("mail").StringValue : null
+ // add first and last name of user
+ });
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to get all users", exception);
@@ -736,38 +697,35 @@ public bool AddUser(string userDn, string password, string email)
bool userAdded = false;
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as write user
- TryBind(connection, WriteUser, WriteUserPwd);
-
- string userName = new FWO.Api.Data.DistName(userDn).UserName;
- LdapAttributeSet attributeSet = new LdapAttributeSet
- {
- new LdapAttribute("objectclass", "inetOrgPerson"),
- new LdapAttribute("sn", userName),
- new LdapAttribute("cn", userName),
- new LdapAttribute("uid", userName),
- new LdapAttribute("userPassword", password),
- new LdapAttribute("mail", email)
- };
+ using LdapConnection connection = Connect();
+ // Authenticate as write user
+ TryBind(connection, WriteUser, WriteUserPwd);
- LdapEntry newEntry = new LdapEntry(userDn, attributeSet);
-
- try
- {
- //Add the entry to the directory
- connection.Add(newEntry);
- userAdded = true;
- Log.WriteDebug("Add user", $"User {userName} added in {Address}:{Port}");
- }
- catch (Exception exception)
- {
- Log.WriteInfo("Add User", $"couldn't add user to LDAP {Address}:{Port}: {exception.ToString()}");
- }
- }
- }
+ string userName = new DistName(userDn).UserName;
+ LdapAttributeSet attributeSet = new ()
+ {
+ new LdapAttribute("objectclass", "inetOrgPerson"),
+ new LdapAttribute("sn", userName),
+ new LdapAttribute("cn", userName),
+ new LdapAttribute("uid", userName),
+ new LdapAttribute("userPassword", password),
+ new LdapAttribute("mail", email)
+ };
+
+ LdapEntry newEntry = new (userDn, attributeSet);
+
+ try
+ {
+ //Add the entry to the directory
+ connection.Add(newEntry);
+ userAdded = true;
+ Log.WriteDebug("Add user", $"User {userName} added in {Address}:{Port}");
+ }
+ catch (Exception exception)
+ {
+ Log.WriteInfo("Add User", $"couldn't add user to LDAP {Address}:{Port}: {exception}");
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to add user", exception);
@@ -785,27 +743,24 @@ public bool UpdateUser(string userDn, string email)
bool userUpdated = false;
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as write user
- TryBind(connection, WriteUser, WriteUserPwd);
- LdapAttribute attribute = new LdapAttribute("mail", email);
- LdapModification[] mods = { new LdapModification(LdapModification.Replace, attribute) };
-
- try
- {
- //Add the entry to the directory
- connection.Modify(userDn, mods);
- userUpdated = true;
- Log.WriteDebug("Update user", $"User {userDn} updated in {Address}:{Port}");
- }
- catch (Exception exception)
- {
- Log.WriteInfo("Update User", $"couldn't update user in LDAP {Address}:{Port}: {exception.ToString()}");
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as write user
+ TryBind(connection, WriteUser, WriteUserPwd);
+ LdapAttribute attribute = new ("mail", email);
+ LdapModification[] mods = [new (LdapModification.Replace, attribute)];
+
+ try
+ {
+ //Add the entry to the directory
+ connection.Modify(userDn, mods);
+ userUpdated = true;
+ Log.WriteDebug("Update user", $"User {userDn} updated in {Address}:{Port}");
+ }
+ catch (Exception exception)
+ {
+ Log.WriteInfo("Update User", $"couldn't update user in LDAP {Address}:{Port}: {exception}");
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to update user", exception);
@@ -823,25 +778,22 @@ public bool DeleteUser(string userDn)
bool userDeleted = false;
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as write user
- TryBind(connection, WriteUser, WriteUserPwd);
-
- try
- {
- //Delete the entry in the directory
- connection.Delete(userDn);
- userDeleted = true;
- Log.WriteDebug("Delete user", $"User {userDn} deleted in {Address}:{Port}");
- }
- catch (Exception exception)
- {
- Log.WriteInfo("Delete User", $"couldn't delete user in LDAP {Address}:{Port}: {exception.ToString()}");
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as write user
+ TryBind(connection, WriteUser, WriteUserPwd);
+
+ try
+ {
+ //Delete the entry in the directory
+ connection.Delete(userDn);
+ userDeleted = true;
+ Log.WriteDebug("Delete user", $"User {userDn} deleted in {Address}:{Port}");
+ }
+ catch (Exception exception)
+ {
+ Log.WriteInfo("Delete User", $"couldn't delete user in LDAP {Address}:{Port}: {exception}");
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to delete user", exception);
@@ -860,41 +812,38 @@ public string AddGroup(string groupName, bool ownerGroup)
string groupDn = "";
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
+ using LdapConnection connection = Connect();
+ // Authenticate as write user
+ TryBind(connection, WriteUser, WriteUserPwd);
+
+ groupDn = $"cn={groupName},{GroupSearchPath}";
+ LdapAttributeSet attributeSet = new ();
+ attributeSet.Add(new LdapAttribute("objectclass", "groupofuniquenames"));
+ attributeSet.Add(new LdapAttribute("uniqueMember", ""));
+ if (ownerGroup)
{
- // Authenticate as write user
- TryBind(connection, WriteUser, WriteUserPwd);
-
- groupDn = $"cn={groupName},{GroupSearchPath}";
- LdapAttributeSet attributeSet = new LdapAttributeSet();
- attributeSet.Add(new LdapAttribute("objectclass", "groupofuniquenames"));
- attributeSet.Add(new LdapAttribute("uniqueMember", ""));
- if (ownerGroup)
- {
- attributeSet.Add(new LdapAttribute("businessCategory", "ownergroup"));
- }
-
- LdapEntry newEntry = new LdapEntry(groupDn, attributeSet);
-
- try
- {
- //Add the entry to the directory
- connection.Add(newEntry);
- groupAdded = true;
- Log.WriteDebug("Add group", $"Group {groupName} added in {Address}:{Port}");
- }
- catch (Exception exception)
- {
- Log.WriteInfo("Add Group", $"couldn't add group to LDAP {Address}:{Port}: {exception.ToString()}");
- }
+ attributeSet.Add(new LdapAttribute("businessCategory", "ownergroup"));
}
- }
+
+ LdapEntry newEntry = new (groupDn, attributeSet);
+
+ try
+ {
+ //Add the entry to the directory
+ connection.Add(newEntry);
+ groupAdded = true;
+ Log.WriteDebug("Add group", $"Group {groupName} added in {Address}:{Port}");
+ }
+ catch (Exception exception)
+ {
+ Log.WriteInfo("Add Group", $"couldn't add group to LDAP {Address}:{Port}: {exception}");
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to add group", exception);
}
- return (groupAdded ? groupDn : "");
+ return groupAdded ? groupDn : "";
}
///
@@ -910,30 +859,27 @@ public string UpdateGroup(string oldName, string newName)
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as write user
- TryBind(connection, WriteUser, WriteUserPwd);
-
- try
- {
- //Add the entry to the directory
- connection.Rename(oldGroupDn, newGroupRdn, true);
- groupUpdated = true;
- Log.WriteDebug("Update group", $"Group {oldName} renamed to {newName} in {Address}:{Port}");
- }
- catch (Exception exception)
- {
- Log.WriteInfo("Update Group", $"couldn't update group in LDAP {Address}:{Port}: {exception.ToString()}");
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as write user
+ TryBind(connection, WriteUser, WriteUserPwd);
+
+ try
+ {
+ //Add the entry to the directory
+ connection.Rename(oldGroupDn, newGroupRdn, true);
+ groupUpdated = true;
+ Log.WriteDebug("Update group", $"Group {oldName} renamed to {newName} in {Address}:{Port}");
+ }
+ catch (Exception exception)
+ {
+ Log.WriteInfo("Update Group", $"couldn't update group in LDAP {Address}:{Port}: {exception}");
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to update group", exception);
}
- return (groupUpdated ? $"{newGroupRdn},{GroupSearchPath}" : "");
+ return groupUpdated ? $"{newGroupRdn},{GroupSearchPath}" : "";
}
///
@@ -946,26 +892,23 @@ public bool DeleteGroup(string groupName)
bool groupDeleted = false;
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as write user
- TryBind(connection, WriteUser, WriteUserPwd);
-
- try
- {
- //Delete the entry in the directory
- string groupDn = $"cn={groupName},{GroupSearchPath}";
- connection.Delete(groupDn);
- groupDeleted = true;
- Log.WriteDebug("Delete group", $"Group {groupName} deleted in {Address}:{Port}");
- }
- catch (Exception exception)
- {
- Log.WriteInfo("Delete Group", $"couldn't delete group in LDAP {Address}:{Port}: {exception.ToString()}");
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as write user
+ TryBind(connection, WriteUser, WriteUserPwd);
+
+ try
+ {
+ //Delete the entry in the directory
+ string groupDn = $"cn={groupName},{GroupSearchPath}";
+ connection.Delete(groupDn);
+ groupDeleted = true;
+ Log.WriteDebug("Delete group", $"Group {groupName} deleted in {Address}:{Port}");
+ }
+ catch (Exception exception)
+ {
+ Log.WriteInfo("Delete Group", $"couldn't delete group in LDAP {Address}:{Port}: {exception}");
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to delete group", exception);
@@ -999,8 +942,7 @@ public bool RemoveUserFromEntry(string userDn, string entry)
/// true if user removed from all entries
public bool RemoveUserFromAllEntries(string userDn)
{
- List dnList = new List();
- dnList.Add(userDn); // group memberships do not need to be regarded here
+ List dnList = [userDn]; // group memberships do not need to be regarded here
List roles = GetRoles(dnList);
bool allRemoved = true;
foreach (var role in roles)
@@ -1015,34 +957,31 @@ public bool RemoveUserFromAllEntries(string userDn)
return allRemoved;
}
- private bool ModifyUserInEntry(string userDn, string entry, int LdapModification)
+ private bool ModifyUserInEntry(string userDn, string entry, int ldapModification)
{
bool userModified = false;
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as write user
- TryBind(connection, WriteUser, WriteUserPwd);
-
- // Add a new value to the description attribute
- LdapAttribute attribute = new LdapAttribute("uniquemember", userDn);
- LdapModification[] mods = { new LdapModification(LdapModification, attribute) };
-
- try
- {
- //Modify the entry in the directory
- connection.Modify(entry, mods);
- userModified = true;
- Log.WriteDebug("Modify Entry", $"Entry {entry} modified in {Address}:{Port}");
- }
- catch (Exception exception)
- {
- Log.WriteInfo("Modify Entry", $"maybe entry doesn't exist in this LDAP {Address}:{Port}: {exception.ToString()}");
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as write user
+ TryBind(connection, WriteUser, WriteUserPwd);
+
+ // Add a new value to the description attribute
+ LdapAttribute attribute = new("uniquemember", userDn);
+ LdapModification[] mods = [new(ldapModification, attribute)];
+
+ try
+ {
+ //Modify the entry in the directory
+ connection.Modify(entry, mods);
+ userModified = true;
+ Log.WriteDebug("Modify Entry", $"Entry {entry} modified in {Address}:{Port}");
+ }
+ catch (Exception exception)
+ {
+ Log.WriteInfo("Modify Entry", $"maybe entry doesn't exist in this LDAP {Address}:{Port}: {exception}");
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to modify user", exception);
@@ -1058,34 +997,31 @@ public bool AddTenant(string tenantName)
{
Log.WriteInfo("Add Tenant", $"Trying to add Tenant: \"{tenantName}\"");
bool tenantAdded = false;
- string tenantDn = "";
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as write user
- TryBind(connection, WriteUser, WriteUserPwd);
-
- tenantDn = $"ou={tenantName},{UserSearchPath}";
- LdapAttributeSet attributeSet = new LdapAttributeSet();
- attributeSet.Add(new LdapAttribute("objectclass", "organizationalUnit"));
+ using LdapConnection connection = Connect();
+ // Authenticate as write user
+ TryBind(connection, WriteUser, WriteUserPwd);
- LdapEntry newEntry = new LdapEntry(tenantDn, attributeSet);
-
- try
- {
- //Add the entry to the directory
- connection.Add(newEntry);
- tenantAdded = true;
- Log.WriteDebug("Add tenant", $"Tenant {tenantName} added in {Address}:{Port}");
- }
- catch (Exception exception)
- {
- Log.WriteInfo("Add Tenant", $"couldn't add tenant to LDAP {Address}:{Port}: {exception.ToString()}");
- }
- }
- }
+ LdapAttributeSet attributeSet = new ()
+ {
+ new LdapAttribute("objectclass", "organizationalUnit")
+ };
+
+ LdapEntry newEntry = new (TenantNameToDn(tenantName), attributeSet);
+
+ try
+ {
+ //Add the entry to the directory
+ connection.Add(newEntry);
+ tenantAdded = true;
+ Log.WriteDebug("Add tenant", $"Tenant {tenantName} added in {Address}:{Port}");
+ }
+ catch (Exception exception)
+ {
+ Log.WriteInfo("Add Tenant", $"couldn't add tenant to LDAP {Address}:{Port}: {exception}");
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to add tenant", exception);
@@ -1103,32 +1039,33 @@ public bool DeleteTenant(string tenantName)
bool tenantDeleted = false;
try
{
- // Connecting to Ldap
- using (LdapConnection connection = Connect())
- {
- // Authenticate as write user
- TryBind(connection, WriteUser, WriteUserPwd);
-
- try
- {
- string tenantDn = "ou=" + tenantName + "," + UserSearchPath;
-
- //Delete the entry in the directory
- connection.Delete(tenantDn);
- tenantDeleted = true;
- Log.WriteDebug("Delete Tenant", $"tenant {tenantDn} deleted in {Address}:{Port}");
- }
- catch (Exception exception)
- {
- Log.WriteInfo("Delete Tenant", $"couldn't delete tenant in LDAP {Address}:{Port}: {exception.ToString()}");
- }
- }
- }
+ using LdapConnection connection = Connect();
+ // Authenticate as write user
+ TryBind(connection, WriteUser, WriteUserPwd);
+
+ try
+ {
+ string tenantDn = TenantNameToDn(tenantName);
+ //Delete the entry in the directory
+ connection.Delete(tenantDn);
+ tenantDeleted = true;
+ Log.WriteDebug("Delete Tenant", $"tenant {tenantDn} deleted in {Address}:{Port}");
+ }
+ catch (Exception exception)
+ {
+ Log.WriteInfo("Delete Tenant", $"couldn't delete tenant in LDAP {Address}:{Port}: {exception}");
+ }
+ }
catch (Exception exception)
{
Log.WriteError($"Non-LDAP exception {Address}:{Port}", "Unexpected error while trying to delete tenant", exception);
}
return tenantDeleted;
}
+
+ private string TenantNameToDn(string tenantName)
+ {
+ return $"ou={tenantName},{UserSearchPath}";
+ }
}
}
diff --git a/roles/test/files/FWO.Test/FWO.Test.csproj b/roles/test/files/FWO.Test/FWO.Test.csproj
index f4df8c8a6..b79a2a8c0 100644
--- a/roles/test/files/FWO.Test/FWO.Test.csproj
+++ b/roles/test/files/FWO.Test/FWO.Test.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/roles/test/tasks/main.yml b/roles/test/tasks/main.yml
index 526b04450..a02f13b80 100644
--- a/roles/test/tasks/main.yml
+++ b/roles/test/tasks/main.yml
@@ -62,7 +62,6 @@
- name: auth testing
import_tasks: test-auth.yml
- when: "not run_on_github|bool"
- name: api testing
import_tasks: test-api.yml
diff --git a/roles/test/tasks/test-auth.yml b/roles/test/tasks/test-auth.yml
index dd400917c..b91352757 100644
--- a/roles/test/tasks/test-auth.yml
+++ b/roles/test/tasks/test-auth.yml
@@ -7,7 +7,6 @@
connect_timeout: 1
delay: 10
timeout: 25
- when: "not run_on_github|bool"
- name: middleware test get jwt valid creds
uri:
diff --git a/roles/ui/files/FWO.UI/Auth/AuthStateProvider.cs b/roles/ui/files/FWO.UI/Auth/AuthStateProvider.cs
index d2246234e..5457f5151 100644
--- a/roles/ui/files/FWO.UI/Auth/AuthStateProvider.cs
+++ b/roles/ui/files/FWO.UI/Auth/AuthStateProvider.cs
@@ -1,11 +1,8 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using FWO.Config.Api;
using FWO.Api.Client;
-using FWO.Api.Client.Queries;
-using FWO.GlobalConstants;
using FWO.Api.Data;
using FWO.Ui.Services;
using FWO.Middleware.Client;
@@ -15,201 +12,205 @@
using FWO.Logging;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
using System.Security.Authentication;
-using System.Security.Principal;
-
namespace FWO.Ui.Auth
{
- public class AuthStateProvider : AuthenticationStateProvider
- {
- private ClaimsPrincipal user = new ClaimsPrincipal(new ClaimsIdentity());
-
- public override Task GetAuthenticationStateAsync()
- {
- return Task.FromResult(new AuthenticationState(user));
- }
-
- public async Task> Authenticate(string username, string password, ApiConnection apiConnection, MiddlewareClient middlewareClient,
- GlobalConfig globalConfig, UserConfig userConfig, ProtectedSessionStorage sessionStorage, CircuitHandlerService circuitHandler)
- {
- // There is no jwt in session storage. Get one from auth module.
- AuthenticationTokenGetParameters authenticationParameters = new AuthenticationTokenGetParameters { Username = username, Password = password };
- RestResponse apiAuthResponse = await middlewareClient.AuthenticateUser(authenticationParameters);
-
- if (apiAuthResponse.StatusCode == HttpStatusCode.OK)
- {
- string jwtString = apiAuthResponse.Data ?? throw new Exception("no response data");
- await Authenticate(jwtString, apiConnection, middlewareClient, globalConfig, userConfig, circuitHandler, sessionStorage);
- Log.WriteAudit("AuthenticateUser", $"user {username} successfully authenticated");
- }
-
- return apiAuthResponse;
- }
-
- public async Task Authenticate(string jwtString, ApiConnection apiConnection, MiddlewareClient middlewareClient,
- GlobalConfig globalConfig, UserConfig userConfig, CircuitHandlerService circuitHandler, ProtectedSessionStorage sessionStorage)
- {
- // Try to auth with jwt (validates it and creates user context on UI side).
- JwtReader jwtReader = new JwtReader(jwtString);
-
- if (await jwtReader.Validate())
- {
- // importer is not allowed to login
- if (jwtReader.ContainsRole(Roles.Importer))
- {
- throw new AuthenticationException("login_importer_error");
- }
-
- // Save jwt in session storage.
- await sessionStorage.SetAsync("jwt", jwtString);
-
- // Tell api connection to use jwt as authentication
- apiConnection.SetAuthHeader(jwtString);
-
- // Tell middleware connection to use jwt as authentication
- middlewareClient.SetAuthenticationToken(jwtString);
-
- // Set user claims based on the jwt claims
- ClaimsIdentity identity = new ClaimsIdentity
- (
- claims: jwtReader.GetClaims(),
- authenticationType: "ldap",
- nameType: JwtRegisteredClaimNames.UniqueName,
- roleType: "role"
- );
-
- // Set user information
- user = new ClaimsPrincipal(identity);
- string userDn = user.FindFirstValue("x-hasura-uuid");
- await userConfig.SetUserInformation(userDn, apiConnection);
- userConfig.User.Jwt = jwtString;
- userConfig.User.Tenant = await getTenantFromJwt(userConfig.User.Jwt, apiConnection);
- userConfig.User.Roles = await getAllowedRoles(userConfig.User.Jwt);
- userConfig.User.Ownerships = await getAssignedOwners(userConfig.User.Jwt);
- circuitHandler.User = userConfig.User;
-
- // Add jwt expiry timer
- JwtEventService.AddJwtTimers(userDn, (int)jwtReader.TimeUntilExpiry().TotalMilliseconds, 1000 * 60 * globalConfig.SessionTimeoutNoticePeriod);
-
- if (!userConfig.User.PasswordMustBeChanged)
- {
- NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
- }
- }
- else
- {
- Deauthenticate();
- }
- }
-
- public void Deauthenticate()
- {
- user = new ClaimsPrincipal(new ClaimsIdentity());
- NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
- }
-
- public void ConfirmPasswordChanged()
- {
- NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user ?? throw new Exception("Password cannot be changed because user was not authenticated"))));
- }
-
- public async Task getTenantId(string jwtString)
- {
- JwtReader jwtReader = new JwtReader(jwtString);
- int tenantId = 0;
-
- if (await jwtReader.Validate())
- {
- ClaimsIdentity identity = new ClaimsIdentity
- (
- claims: jwtReader.GetClaims(),
- authenticationType: "ldap",
- nameType: JwtRegisteredClaimNames.UniqueName,
- roleType: "role"
- );
-
- // Set user information
- user = new ClaimsPrincipal(identity);
-
- if (!int.TryParse(user.FindFirstValue("x-hasura-tenant-id"), out tenantId))
- {
- // TODO: log warning
- }
- }
- return tenantId;
- }
-
- public async Task getTenantFromJwt(string jwtString, ApiConnection apiConnection)
- {
- JwtReader jwtReader = new JwtReader(jwtString);
- Tenant tenant = new();
-
- if (await jwtReader.Validate())
- {
- ClaimsIdentity identity = new ClaimsIdentity
- (
- claims: jwtReader.GetClaims(),
- authenticationType: "ldap",
- nameType: JwtRegisteredClaimNames.UniqueName,
- roleType: "role"
- );
-
- // Set user information
- user = new ClaimsPrincipal(identity);
-
- if (int.TryParse(user.FindFirstValue("x-hasura-tenant-id"), out int tenantId))
- {
- tenant = await Tenant.GetSingleTenant(apiConnection, tenantId) ?? new();
- }
- // else
- // {
- // // TODO: log warning
- // }
- }
- return tenant;
- }
-
- public async Task> getAllowedRoles(string jwtString)
- {
- return await GetClaimList(jwtString, "x-hasura-allowed-roles");
- }
-
- public async Task> getAssignedOwners(string jwtString)
- {
- List ownerIds = new();
- List ownerClaims = await GetClaimList(jwtString, "x-hasura-editable-owners");
- if(ownerClaims.Count > 0)
- {
- string[] separatingStrings = { ",", "{", "}" };
- string[] owners = ownerClaims[0].Split(separatingStrings, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
- ownerIds = Array.ConvertAll(owners, x => int.Parse(x)).ToList();
- }
- return ownerIds;
- }
-
- private async Task> GetClaimList(string jwtString, string claimType)
- {
- List claimList = new List();
- JwtReader jwtReader = new JwtReader(jwtString);
- if (await jwtReader.Validate())
- {
- ClaimsIdentity identity = new ClaimsIdentity
- (
- claims: jwtReader.GetClaims(),
- authenticationType: "ldap",
- nameType: JwtRegisteredClaimNames.UniqueName,
- roleType: "role"
- );
- foreach (Claim claim in identity.Claims)
- {
- if (claim.Type == claimType)
- {
- claimList.Add(claim.Value);
- }
- }
- }
- return claimList;
- }
- }
+ public class AuthStateProvider : AuthenticationStateProvider
+ {
+ private ClaimsPrincipal user = new ClaimsPrincipal(new ClaimsIdentity());
+
+ public override Task GetAuthenticationStateAsync()
+ {
+ return Task.FromResult(new AuthenticationState(user));
+ }
+
+ public async Task> Authenticate(string username, string password, ApiConnection apiConnection, MiddlewareClient middlewareClient,
+ GlobalConfig globalConfig, UserConfig userConfig, ProtectedSessionStorage sessionStorage, CircuitHandlerService circuitHandler)
+ {
+ // There is no jwt in session storage. Get one from auth module.
+ AuthenticationTokenGetParameters authenticationParameters = new AuthenticationTokenGetParameters { Username = username, Password = password };
+ RestResponse apiAuthResponse = await middlewareClient.AuthenticateUser(authenticationParameters);
+
+ if (apiAuthResponse.StatusCode == HttpStatusCode.OK)
+ {
+ string jwtString = apiAuthResponse.Data ?? throw new Exception("no response data");
+ await Authenticate(jwtString, apiConnection, middlewareClient, globalConfig, userConfig, circuitHandler, sessionStorage);
+ Log.WriteAudit("AuthenticateUser", $"user {username} successfully authenticated");
+ }
+
+ return apiAuthResponse;
+ }
+
+ public async Task Authenticate(string jwtString, ApiConnection apiConnection, MiddlewareClient middlewareClient,
+ GlobalConfig globalConfig, UserConfig userConfig, CircuitHandlerService circuitHandler, ProtectedSessionStorage sessionStorage)
+ {
+ // Try to auth with jwt (validates it and creates user context on UI side).
+ JwtReader jwtReader = new JwtReader(jwtString);
+
+ if (await jwtReader.Validate())
+ {
+ // importer is not allowed to login
+ if (jwtReader.ContainsRole(Roles.Importer))
+ {
+ throw new AuthenticationException("login_importer_error");
+ }
+
+ // anonymous has no authorization to login via UI
+ if (jwtReader.ContainsRole(Roles.Anonymous))
+ {
+ throw new AuthenticationException("not_authorized");
+ }
+
+ // Save jwt in session storage.
+ await sessionStorage.SetAsync("jwt", jwtString);
+
+ // Tell api connection to use jwt as authentication
+ apiConnection.SetAuthHeader(jwtString);
+
+ // Tell middleware connection to use jwt as authentication
+ middlewareClient.SetAuthenticationToken(jwtString);
+
+ // Set user claims based on the jwt claims
+ ClaimsIdentity identity = new ClaimsIdentity
+ (
+ claims: jwtReader.GetClaims(),
+ authenticationType: "ldap",
+ nameType: JwtRegisteredClaimNames.UniqueName,
+ roleType: "role"
+ );
+
+ // Set user information
+ user = new ClaimsPrincipal(identity);
+ string userDn = user.FindFirstValue("x-hasura-uuid");
+ await userConfig.SetUserInformation(userDn, apiConnection);
+ userConfig.User.Jwt = jwtString;
+ userConfig.User.Tenant = await getTenantFromJwt(userConfig.User.Jwt, apiConnection);
+ userConfig.User.Roles = await getAllowedRoles(userConfig.User.Jwt);
+ userConfig.User.Ownerships = await getAssignedOwners(userConfig.User.Jwt);
+ circuitHandler.User = userConfig.User;
+
+ // Add jwt expiry timer
+ JwtEventService.AddJwtTimers(userDn, (int)jwtReader.TimeUntilExpiry().TotalMilliseconds, 1000 * 60 * globalConfig.SessionTimeoutNoticePeriod);
+
+ if (!userConfig.User.PasswordMustBeChanged)
+ {
+ NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
+ }
+ }
+ else
+ {
+ Deauthenticate();
+ }
+ }
+
+ public void Deauthenticate()
+ {
+ user = new ClaimsPrincipal(new ClaimsIdentity());
+ NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
+ }
+
+ public void ConfirmPasswordChanged()
+ {
+ NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user ?? throw new Exception("Password cannot be changed because user was not authenticated"))));
+ }
+
+ public async Task getTenantId(string jwtString)
+ {
+ JwtReader jwtReader = new JwtReader(jwtString);
+ int tenantId = 0;
+
+ if (await jwtReader.Validate())
+ {
+ ClaimsIdentity identity = new ClaimsIdentity
+ (
+ claims: jwtReader.GetClaims(),
+ authenticationType: "ldap",
+ nameType: JwtRegisteredClaimNames.UniqueName,
+ roleType: "role"
+ );
+
+ // Set user information
+ user = new ClaimsPrincipal(identity);
+
+ if (!int.TryParse(user.FindFirstValue("x-hasura-tenant-id"), out tenantId))
+ {
+ // TODO: log warning
+ }
+ }
+ return tenantId;
+ }
+
+ public async Task getTenantFromJwt(string jwtString, ApiConnection apiConnection)
+ {
+ JwtReader jwtReader = new JwtReader(jwtString);
+ Tenant tenant = new();
+
+ if (await jwtReader.Validate())
+ {
+ ClaimsIdentity identity = new ClaimsIdentity
+ (
+ claims: jwtReader.GetClaims(),
+ authenticationType: "ldap",
+ nameType: JwtRegisteredClaimNames.UniqueName,
+ roleType: "role"
+ );
+
+ // Set user information
+ user = new ClaimsPrincipal(identity);
+
+ if (int.TryParse(user.FindFirstValue("x-hasura-tenant-id"), out int tenantId))
+ {
+ tenant = await Tenant.GetSingleTenant(apiConnection, tenantId) ?? new();
+ }
+ // else
+ // {
+ // // TODO: log warning
+ // }
+ }
+ return tenant;
+ }
+
+ public async Task> getAllowedRoles(string jwtString)
+ {
+ return await GetClaimList(jwtString, "x-hasura-allowed-roles");
+ }
+
+ public async Task> getAssignedOwners(string jwtString)
+ {
+ List ownerIds = new();
+ List ownerClaims = await GetClaimList(jwtString, "x-hasura-editable-owners");
+ if (ownerClaims.Count > 0)
+ {
+ string[] separatingStrings = { ",", "{", "}" };
+ string[] owners = ownerClaims[0].Split(separatingStrings, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
+ ownerIds = Array.ConvertAll(owners, x => int.Parse(x)).ToList();
+ }
+ return ownerIds;
+ }
+
+ private async Task> GetClaimList(string jwtString, string claimType)
+ {
+ List claimList = new List();
+ JwtReader jwtReader = new JwtReader(jwtString);
+ if (await jwtReader.Validate())
+ {
+ ClaimsIdentity identity = new ClaimsIdentity
+ (
+ claims: jwtReader.GetClaims(),
+ authenticationType: "ldap",
+ nameType: JwtRegisteredClaimNames.UniqueName,
+ roleType: "role"
+ );
+ foreach (Claim claim in identity.Claims)
+ {
+ if (claim.Type == claimType)
+ {
+ claimList.Add(claim.Value);
+ }
+ }
+ }
+ return claimList;
+ }
+ }
}
diff --git a/roles/ui/files/FWO.UI/Pages/Help/HelpModellingSidebar.cshtml b/roles/ui/files/FWO.UI/Pages/Help/HelpModellingSidebar.cshtml
index 679a4c9b6..c710dd21b 100644
--- a/roles/ui/files/FWO.UI/Pages/Help/HelpModellingSidebar.cshtml
+++ b/roles/ui/files/FWO.UI/Pages/Help/HelpModellingSidebar.cshtml
@@ -21,7 +21,9 @@
@(userConfig.GetText("services"))
-
+
+ @(userConfig.GetText("workflow"))
+
diff --git a/roles/ui/files/FWO.UI/Pages/Help/HelpModellingWorkflow.cshtml b/roles/ui/files/FWO.UI/Pages/Help/HelpModellingWorkflow.cshtml
new file mode 100644
index 000000000..ae2b73ec1
--- /dev/null
+++ b/roles/ui/files/FWO.UI/Pages/Help/HelpModellingWorkflow.cshtml
@@ -0,0 +1,17 @@
+@page "/help/modelling/workflow"
+@model FWO.Ui.Pages.Help.MainModel
+@{
+ Layout = "HelpLayout";
+}
+@section sidebar{
+ @{
+ await Html.RenderPartialAsync("HelpModellingSidebar.cshtml");
+ }
+}
+@using FWO.Config.Api
+@inject UserConfig userConfig
+
+