prefix="${build.output.name}/${plugins.folder}" />
+ prefix="${build.output.name}/${plugins.folder}" />
+ file="${project.build.directory}/plugins/org.eclipse.kura.rest.identity.provider_${org.eclipse.kura.rest.identity.provider.version}.jar"
+ prefix="${build.output.name}/${plugins.folder}" />
+
diff --git a/kura/distrib/src/main/resources/common/Kura_Emulator.launch b/kura/distrib/src/main/resources/common/Kura_Emulator.launch
index 745e715ca0c..b8a5ab73635 100644
--- a/kura/distrib/src/main/resources/common/Kura_Emulator.launch
+++ b/kura/distrib/src/main/resources/common/Kura_Emulator.launch
@@ -150,6 +150,7 @@
+
diff --git a/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-generic-device b/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-generic-device
index 29670978426..14a9bd0dcc3 100644
--- a/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-generic-device
+++ b/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-generic-device
@@ -463,7 +463,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.netadmin","credentials":{"kura.password":"3PgDKAMCxgRWBHiT1dEBS97bPqt7xckgdwrADJiDoWg="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.network.status"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.network.status"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-intelup2 b/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-intelup2
index f3f8a2f67cb..a4c1d4d21de 100644
--- a/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-intelup2
+++ b/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-intelup2
@@ -456,7 +456,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.netadmin","credentials":{"kura.password":"3PgDKAMCxgRWBHiT1dEBS97bPqt7xckgdwrADJiDoWg="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.network.status"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.network.status"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-jetson-nano b/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-jetson-nano
index 74c8789dd62..68f53811ecc 100644
--- a/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-jetson-nano
+++ b/kura/distrib/src/main/resources/common/snapshots/snapshot_0.xml-jetson-nano
@@ -382,7 +382,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.netadmin","credentials":{"kura.password":"3PgDKAMCxgRWBHiT1dEBS97bPqt7xckgdwrADJiDoWg="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.network.status"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.network.status"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/docker-x86_64-nn/snapshot_0.xml b/kura/distrib/src/main/resources/docker-x86_64-nn/snapshot_0.xml
index dddb15e1b1c..61e4baff5dd 100644
--- a/kura/distrib/src/main/resources/docker-x86_64-nn/snapshot_0.xml
+++ b/kura/distrib/src/main/resources/docker-x86_64-nn/snapshot_0.xml
@@ -337,7 +337,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.device"},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.device"},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/intel-up2-ubuntu-20-nn/snapshot_0.xml b/kura/distrib/src/main/resources/intel-up2-ubuntu-20-nn/snapshot_0.xml
index 6255c44abda..e21af1902ef 100644
--- a/kura/distrib/src/main/resources/intel-up2-ubuntu-20-nn/snapshot_0.xml
+++ b/kura/distrib/src/main/resources/intel-up2-ubuntu-20-nn/snapshot_0.xml
@@ -337,7 +337,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.device"},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.device"},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/intel-up2-ubuntu-20/snapshot_0.xml b/kura/distrib/src/main/resources/intel-up2-ubuntu-20/snapshot_0.xml
index 8302ecc48db..0042581a93e 100644
--- a/kura/distrib/src/main/resources/intel-up2-ubuntu-20/snapshot_0.xml
+++ b/kura/distrib/src/main/resources/intel-up2-ubuntu-20/snapshot_0.xml
@@ -444,7 +444,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.netadmin","credentials":{"kura.password":"3PgDKAMCxgRWBHiT1dEBS97bPqt7xckgdwrADJiDoWg="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/nvidia-jetson-nano-nn/snapshot_0.xml b/kura/distrib/src/main/resources/nvidia-jetson-nano-nn/snapshot_0.xml
index fdd079c85fe..c005116d6ea 100644
--- a/kura/distrib/src/main/resources/nvidia-jetson-nano-nn/snapshot_0.xml
+++ b/kura/distrib/src/main/resources/nvidia-jetson-nano-nn/snapshot_0.xml
@@ -337,7 +337,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.device"},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.device"},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/nvidia-jetson-nano/snapshot_0.xml b/kura/distrib/src/main/resources/nvidia-jetson-nano/snapshot_0.xml
index 7a9f0f12ac2..2e9a0d149e9 100644
--- a/kura/distrib/src/main/resources/nvidia-jetson-nano/snapshot_0.xml
+++ b/kura/distrib/src/main/resources/nvidia-jetson-nano/snapshot_0.xml
@@ -373,7 +373,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.netadmin","credentials":{"kura.password":"3PgDKAMCxgRWBHiT1dEBS97bPqt7xckgdwrADJiDoWg="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/raspberry-pi-arm64-nn/snapshot_0.xml b/kura/distrib/src/main/resources/raspberry-pi-arm64-nn/snapshot_0.xml
index f1bdd9f72af..18d21c321fd 100644
--- a/kura/distrib/src/main/resources/raspberry-pi-arm64-nn/snapshot_0.xml
+++ b/kura/distrib/src/main/resources/raspberry-pi-arm64-nn/snapshot_0.xml
@@ -337,7 +337,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.device"},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.device"},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/raspberry-pi-arm64/snapshot_0.xml b/kura/distrib/src/main/resources/raspberry-pi-arm64/snapshot_0.xml
index 388c288513e..abc777d9a1c 100644
--- a/kura/distrib/src/main/resources/raspberry-pi-arm64/snapshot_0.xml
+++ b/kura/distrib/src/main/resources/raspberry-pi-arm64/snapshot_0.xml
@@ -460,7 +460,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.netadmin","credentials":{"kura.password":"3PgDKAMCxgRWBHiT1dEBS97bPqt7xckgdwrADJiDoWg="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/raspberry-pi-armhf-nn/snapshot_0.xml b/kura/distrib/src/main/resources/raspberry-pi-armhf-nn/snapshot_0.xml
index 6b6f5b62b49..779d189e36f 100644
--- a/kura/distrib/src/main/resources/raspberry-pi-armhf-nn/snapshot_0.xml
+++ b/kura/distrib/src/main/resources/raspberry-pi-armhf-nn/snapshot_0.xml
@@ -337,7 +337,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.device"},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.device"},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/distrib/src/main/resources/raspberry-pi-armhf/snapshot_0.xml b/kura/distrib/src/main/resources/raspberry-pi-armhf/snapshot_0.xml
index 9801677fef5..0344ff7e6b6 100644
--- a/kura/distrib/src/main/resources/raspberry-pi-armhf/snapshot_0.xml
+++ b/kura/distrib/src/main/resources/raspberry-pi-armhf/snapshot_0.xml
@@ -466,7 +466,7 @@
[{"name":"kura.user.admin","credentials":{"kura.password":"jGl25bVBBBW96Qi9Te4V37Fnqchz/Eu4qB9vKrRIqRg="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.appadmin","credentials":{"kura.password":"3hPckF8Zc+IF3pVineBvck3zJERUl8itosySULE1hpM="},"properties":{"kura.need.password.change":"true"}},{"name":"kura.user.netadmin","credentials":{"kura.password":"3PgDKAMCxgRWBHiT1dEBS97bPqt7xckgdwrADJiDoWg="},"properties":{"kura.need.password.change":"true"}}]
- [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
+ [{"name":"kura.permission.kura.admin","basicMembers":["kura.user.admin"]},{"name":"kura.permission.kura.cloud.connection.admin","basicMembers":["kura.user.appadmin","kura.user.netadmin"]},{"name":"kura.permission.kura.device","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.maintenance"},{"name":"kura.permission.kura.network.admin","basicMembers":["kura.user.netadmin"]},{"name":"kura.permission.kura.packages.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.kura.wires.admin","basicMembers":["kura.user.appadmin"]},{"name":"kura.permission.rest.assets"},{"name":"kura.permission.rest.command"},{"name":"kura.permission.rest.configuration"},{"name":"kura.permission.rest.identity"},{"name":"kura.permission.rest.inventory"},{"name":"kura.permission.rest.keystores"},{"name":"kura.permission.rest.position"},{"name":"kura.permission.rest.system"},{"name":"kura.permission.rest.tamper.detection"},{"name":"kura.permission.rest.wires.admin"}]
diff --git a/kura/emulator/org.eclipse.kura.emulator/src/main/resources/Kura_Emulator.launch b/kura/emulator/org.eclipse.kura.emulator/src/main/resources/Kura_Emulator.launch
index c351f8160db..ec702361a37 100644
--- a/kura/emulator/org.eclipse.kura.emulator/src/main/resources/Kura_Emulator.launch
+++ b/kura/emulator/org.eclipse.kura.emulator/src/main/resources/Kura_Emulator.launch
@@ -149,6 +149,7 @@
+
diff --git a/kura/org.eclipse.kura.rest.identity.provider/META-INF/MANIFEST.MF b/kura/org.eclipse.kura.rest.identity.provider/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..295b8ba55b5
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/META-INF/MANIFEST.MF
@@ -0,0 +1,27 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Identity REST Service
+Bundle-SymbolicName: org.eclipse.kura.rest.identity.provider;singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
+Bundle-ClassPath: .
+Bundle-ActivationPolicy: lazy
+Service-Component: OSGI-INF/*.xml
+Import-Package: javax.annotation.security;version="1.2.0",
+ javax.ws.rs;version="2.0.1",
+ javax.ws.rs.core;version="2.0.1",
+ org.apache.commons.io;version="2.4.0",
+ org.eclipse.kura;version="[1.3,2.0)",
+ org.eclipse.kura.cloudconnection.request;version="[1.0,2.0)",
+ org.eclipse.kura.configuration;version="[1.2,2.0)",
+ org.eclipse.kura.crypto;version="[1.3,2.0)",
+ org.eclipse.kura.request.handler.jaxrs;version="[1.0,2.0)",
+ org.eclipse.kura.request.handler.jaxrs.annotation;version="[1.0,2.0)",
+ org.eclipse.kura.rest.configuration.api;version="[1.0,2.0)",
+ org.eclipse.kura.util.configuration;version="[1.0,2.0)",
+ org.eclipse.kura.util.useradmin;version="[1.0,2.0)",
+ org.osgi.framework;version="1.8.0",
+ org.osgi.service.cm;version="1.6.0",
+ org.osgi.service.component;version="[1.3,2.0)",
+ org.osgi.service.useradmin;version="1.1.0";resolution:=optional,
+ org.slf4j;version="1.7.25"
diff --git a/kura/org.eclipse.kura.rest.identity.provider/OSGI-INF/IdentityRestService.xml b/kura/org.eclipse.kura.rest.identity.provider/OSGI-INF/IdentityRestService.xml
new file mode 100644
index 00000000000..10887b7fce0
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/OSGI-INF/IdentityRestService.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/kura/org.eclipse.kura.rest.identity.provider/about.html b/kura/org.eclipse.kura.rest.identity.provider/about.html
new file mode 100644
index 00000000000..ec5809fefb9
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/about.html
@@ -0,0 +1,36 @@
+
+
+
+
+ About
+
+
+About This Content
+
+November 30, 2017
+License
+
+
+ The Eclipse Foundation makes available all content in this plug-in
+ ("Content"). Unless otherwise indicated below, the Content
+ is provided to you under the terms and conditions of the Eclipse
+ Public License Version 2.0 ("EPL"). A copy of the EPL is
+ available at http://www.eclipse.org/legal/epl-2.0.
+ For purposes of the EPL, "Program" will mean the Content.
+
+
+
+ If you did not receive this Content directly from the Eclipse
+ Foundation, the Content is being redistributed by another party
+ ("Redistributor") and different terms and conditions may
+ apply to your use of any object code in the Content. Check the
+ Redistributor's license that was provided with the Content. If no such
+ license exists, contact the Redistributor. Unless otherwise indicated
+ below, the terms and conditions of the EPL still apply to any source
+ code in the Content and such source code may be obtained at http://www.eclipse.org.
+
+
+
+
\ No newline at end of file
diff --git a/kura/org.eclipse.kura.rest.identity.provider/about_files/epl-v20.html b/kura/org.eclipse.kura.rest.identity.provider/about_files/epl-v20.html
new file mode 100644
index 00000000000..cc699dea65b
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/about_files/epl-v20.html
@@ -0,0 +1,301 @@
+
+
+
+
+
+ Eclipse Public License - Version 2.0
+
+
+
+Eclipse Public License - v 2.0
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+ PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION
+ OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+“Contribution” means:
+
+ - a) in the case of the initial Contributor, the initial content
+ Distributed under this Agreement, and
+
+ -
+ b) in the case of each subsequent Contributor:
+
+ - i) changes to the Program, and
+ - ii) additions to the Program;
+
+ where such changes and/or additions to the Program originate from
+ and are Distributed by that particular Contributor. A Contribution
+ “originates” from a Contributor if it was added to the Program by such
+ Contributor itself or anyone acting on such Contributor's behalf.
+ Contributions do not include changes or additions to the Program that
+ are not Modified Works.
+
+
+“Contributor” means any person or entity that Distributes the Program.
+“Licensed Patents” mean patent claims licensable by a Contributor which
+ are necessarily infringed by the use or sale of its Contribution alone
+ or when combined with the Program.
+
+“Program” means the Contributions Distributed in accordance with this
+ Agreement.
+
+“Recipient” means anyone who receives the Program under this Agreement
+ or any Secondary License (as applicable), including Contributors.
+
+“Derivative Works” shall mean any work, whether in Source Code or other
+ form, that is based on (or derived from) the Program and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship.
+
+“Modified Works” shall mean any work in Source Code or other form that
+ results from an addition to, deletion from, or modification of the
+ contents of the Program, including, for purposes of clarity any new file
+ in Source Code form that contains any contents of the Program. Modified
+ Works shall not include works that contain only declarations, interfaces,
+ types, classes, structures, or files of the Program solely in each case
+ in order to link to, bind by name, or subclass the Program or Modified
+ Works thereof.
+
+“Distribute” means the acts of a) distributing or b) making available
+ in any manner that enables the transfer of a copy.
+
+“Source Code” means the form of a Program preferred for making
+ modifications, including but not limited to software source code,
+ documentation source, and configuration files.
+
+“Secondary License” means either the GNU General Public License,
+ Version 2.0, or any later versions of that license, including any
+ exceptions or additional permissions as identified by the initial
+ Contributor.
+
+2. GRANT OF RIGHTS
+
+ - a) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free copyright
+ license to reproduce, prepare Derivative Works of, publicly display,
+ publicly perform, Distribute and sublicense the Contribution of such
+ Contributor, if any, and such Derivative Works.
+
+ - b) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free patent
+ license under Licensed Patents to make, use, sell, offer to sell,
+ import and otherwise transfer the Contribution of such Contributor,
+ if any, in Source Code or other form. This patent license shall
+ apply to the combination of the Contribution and the Program if,
+ at the time the Contribution is added by the Contributor, such
+ addition of the Contribution causes such combination to be covered
+ by the Licensed Patents. The patent license shall not apply to any
+ other combinations which include the Contribution. No hardware per
+ se is licensed hereunder.
+
+ - c) Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity.
+ Each Contributor disclaims any liability to Recipient for claims
+ brought by any other entity based on infringement of intellectual
+ property rights or otherwise. As a condition to exercising the rights
+ and licenses granted hereunder, each Recipient hereby assumes sole
+ responsibility to secure any other intellectual property rights needed,
+ if any. For example, if a third party patent license is required to
+ allow Recipient to Distribute the Program, it is Recipient's
+ responsibility to acquire that license before distributing the Program.
+
+ - d) Each Contributor represents that to its knowledge it has sufficient
+ copyright rights in its Contribution, if any, to grant the copyright
+ license set forth in this Agreement.
+
+ - e) Notwithstanding the terms of any Secondary License, no Contributor
+ makes additional grants to any Recipient (other than those set forth
+ in this Agreement) as a result of such Recipient's receipt of the
+ Program under the terms of a Secondary License (if permitted under
+ the terms of Section 3).
+
+
+3. REQUIREMENTS
+3.1 If a Contributor Distributes the Program in any form, then:
+
+ - a) the Program must also be made available as Source Code, in
+ accordance with section 3.2, and the Contributor must accompany
+ the Program with a statement that the Source Code for the Program
+ is available under this Agreement, and informs Recipients how to
+ obtain it in a reasonable manner on or through a medium customarily
+ used for software exchange; and
+
+ -
+ b) the Contributor may Distribute the Program under a license
+ different than this Agreement, provided that such license:
+
+ - i) effectively disclaims on behalf of all other Contributors all
+ warranties and conditions, express and implied, including warranties
+ or conditions of title and non-infringement, and implied warranties
+ or conditions of merchantability and fitness for a particular purpose;
+
+ - ii) effectively excludes on behalf of all other Contributors all
+ liability for damages, including direct, indirect, special, incidental
+ and consequential damages, such as lost profits;
+
+ - iii) does not attempt to limit or alter the recipients' rights in the
+ Source Code under section 3.2; and
+
+ - iv) requires any subsequent distribution of the Program by any party
+ to be under a license that satisfies the requirements of this section 3.
+
+
+
+
+3.2 When the Program is Distributed as Source Code:
+
+ - a) it must be made available under this Agreement, or if the Program (i)
+ is combined with other material in a separate file or files made available
+ under a Secondary License, and (ii) the initial Contributor attached to
+ the Source Code the notice described in Exhibit A of this Agreement,
+ then the Program may be made available under the terms of such
+ Secondary Licenses, and
+
+ - b) a copy of this Agreement must be included with each copy of the Program.
+
+3.3 Contributors may not remove or alter any copyright, patent, trademark,
+ attribution notices, disclaimers of warranty, or limitations of liability
+ (‘notices’) contained within the Program from any copy of the Program which
+ they Distribute, provided that Contributors may add their own appropriate
+ notices.
+
+4. COMMERCIAL DISTRIBUTION
+Commercial distributors of software may accept certain responsibilities
+ with respect to end users, business partners and the like. While this
+ license is intended to facilitate the commercial use of the Program, the
+ Contributor who includes the Program in a commercial product offering should
+ do so in a manner which does not create potential liability for other
+ Contributors. Therefore, if a Contributor includes the Program in a
+ commercial product offering, such Contributor (“Commercial Contributor”)
+ hereby agrees to defend and indemnify every other Contributor
+ (“Indemnified Contributor”) against any losses, damages and costs
+ (collectively “Losses”) arising from claims, lawsuits and other legal actions
+ brought by a third party against the Indemnified Contributor to the extent
+ caused by the acts or omissions of such Commercial Contributor in connection
+ with its distribution of the Program in a commercial product offering.
+ The obligations in this section do not apply to any claims or Losses relating
+ to any actual or alleged intellectual property infringement. In order to
+ qualify, an Indemnified Contributor must: a) promptly notify the
+ Commercial Contributor in writing of such claim, and b) allow the Commercial
+ Contributor to control, and cooperate with the Commercial Contributor in,
+ the defense and any related settlement negotiations. The Indemnified
+ Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program
+ in a commercial product offering, Product X. That Contributor is then a
+ Commercial Contributor. If that Commercial Contributor then makes performance
+ claims, or offers warranties related to Product X, those performance claims
+ and warranties are such Commercial Contributor's responsibility alone.
+ Under this section, the Commercial Contributor would have to defend claims
+ against the other Contributors related to those performance claims and
+ warranties, and if a court requires any other Contributor to pay any damages
+ as a result, the Commercial Contributor must pay those damages.
+
+5. NO WARRANTY
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED
+ BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING,
+ WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+ solely responsible for determining the appropriateness of using and
+ distributing the Program and assumes all risks associated with its
+ exercise of rights under this Agreement, including but not limited to the
+ risks and costs of program errors, compliance with applicable laws, damage
+ to or loss of data, programs or equipment, and unavailability or
+ interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED
+ BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY
+ LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS),
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
+ GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+If any provision of this Agreement is invalid or unenforceable under
+ applicable law, it shall not affect the validity or enforceability of the
+ remainder of the terms of this Agreement, and without further action by the
+ parties hereto, such provision shall be reformed to the minimum extent
+ necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Program itself
+ (excluding combinations of the Program with other software or hardware)
+ infringes such Recipient's patent(s), then such Recipient's rights granted
+ under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to
+ comply with any of the material terms or conditions of this Agreement and
+ does not cure such failure in a reasonable period of time after becoming
+ aware of such noncompliance. If all Recipient's rights under this Agreement
+ terminate, Recipient agrees to cease use and distribution of the Program
+ as soon as reasonably practicable. However, Recipient's obligations under
+ this Agreement and any licenses granted by Recipient relating to the
+ Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement,
+ but in order to avoid inconsistency the Agreement is copyrighted and may
+ only be modified in the following manner. The Agreement Steward reserves
+ the right to publish new versions (including revisions) of this Agreement
+ from time to time. No one other than the Agreement Steward has the right
+ to modify this Agreement. The Eclipse Foundation is the initial Agreement
+ Steward. The Eclipse Foundation may assign the responsibility to serve as
+ the Agreement Steward to a suitable separate entity. Each new version of
+ the Agreement will be given a distinguishing version number. The Program
+ (including Contributions) may always be Distributed subject to the version
+ of the Agreement under which it was received. In addition, after a new
+ version of the Agreement is published, Contributor may elect to Distribute
+ the Program (including its Contributions) under the new version.
+
+Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
+ receives no rights or licenses to the intellectual property of any
+ Contributor under this Agreement, whether expressly, by implication,
+ estoppel or otherwise. All rights in the Program not expressly granted
+ under this Agreement are reserved. Nothing in this Agreement is intended
+ to be enforceable by any entity that is not a Contributor or Recipient.
+ No third-party beneficiary rights are created under this Agreement.
+
+Exhibit A – Form of Secondary Licenses Notice
+“This Source Code may also be made available under the following
+ Secondary Licenses when the conditions for such availability set forth
+ in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
+ version(s), and exceptions or additional permissions here}.”
+
+
+ Simply including a copy of this Agreement, including this Exhibit A
+ is not sufficient to license the Source Code under Secondary Licenses.
+
+ If it is not possible or desirable to put the notice in a particular file,
+ then You may include the notice in a location (such as a LICENSE file in a
+ relevant directory) where a recipient would be likely to look for
+ such a notice.
+
+ You may add additional accurate notices of copyright ownership.
+
+
+
\ No newline at end of file
diff --git a/kura/org.eclipse.kura.rest.identity.provider/build.properties b/kura/org.eclipse.kura.rest.identity.provider/build.properties
new file mode 100644
index 00000000000..61baba2a407
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/build.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2023 Eurotech and/or its affiliates and others
+#
+# This program and the accompanying materials are made
+# available under the terms of the Eclipse Public License 2.0
+# which is available at https://www.eclipse.org/legal/epl-2.0/
+#
+# SPDX-License-Identifier: EPL-2.0
+#
+# Contributors:
+# Eurotech
+#
+output.. = target/classes
+bin.includes = .,\
+ META-INF/,\
+ OSGI-INF/,\
+ about.html,\
+ about_files/
+source.. = src/main/java/
diff --git a/kura/org.eclipse.kura.rest.identity.provider/pom.xml b/kura/org.eclipse.kura.rest.identity.provider/pom.xml
new file mode 100644
index 00000000000..8acdb56d518
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/pom.xml
@@ -0,0 +1,35 @@
+
+
+
+ 4.0.0
+
+
+ org.eclipse.kura
+ kura
+ 5.4.0-SNAPSHOT
+
+
+
+ ${project.basedir}/..
+ ${project.basedir}/../test/*/target/site/jacoco-aggregate/jacoco.xml
+
+
+ org.eclipse.kura.rest.identity.provider
+ eclipse-plugin
+ 1.0.0-SNAPSHOT
+
+
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/IdentityRestService.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/IdentityRestService.java
new file mode 100644
index 00000000000..668fc3db455
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/IdentityRestService.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ ******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.eclipse.kura.cloudconnection.request.RequestHandler;
+import org.eclipse.kura.cloudconnection.request.RequestHandlerRegistry;
+import org.eclipse.kura.configuration.ConfigurationService;
+import org.eclipse.kura.crypto.CryptoService;
+import org.eclipse.kura.internal.rest.identity.provider.dto.PermissionDTO;
+import org.eclipse.kura.internal.rest.identity.provider.dto.UserConfigDTO;
+import org.eclipse.kura.internal.rest.identity.provider.dto.UserDTO;
+import org.eclipse.kura.internal.rest.identity.provider.dto.ValidatorOptionsDTO;
+import org.eclipse.kura.internal.rest.identity.provider.validator.ValidatorOptions;
+import org.eclipse.kura.request.handler.jaxrs.DefaultExceptionHandler;
+import org.eclipse.kura.request.handler.jaxrs.JaxRsRequestHandlerProxy;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Path("identity/v1")
+public class IdentityRestService {
+
+ private static final Logger logger = LoggerFactory.getLogger(IdentityRestService.class);
+ private static final String DEBUG_MESSAGE = "Processing request for method '{}'";
+
+ private static final String MQTT_APP_ID = "IDN-V1";
+ private static final String REST_ROLE_NAME = "identity";
+ private static final String KURA_PERMISSION_REST_ROLE = "kura.permission.rest." + REST_ROLE_NAME;
+
+ private final RequestHandler requestHandler = new JaxRsRequestHandlerProxy(this);
+
+ private CryptoService cryptoService;
+
+ private UserAdmin userAdmin;
+ private IdentityService identityService;
+ private ConfigurationService configurationService;
+
+ public void bindCryptoService(CryptoService cryptoService) {
+ this.cryptoService = cryptoService;
+ }
+
+ public void bindConfigurationService(ConfigurationService configurationService) {
+ this.configurationService = configurationService;
+ }
+
+ public void bindUserAdmin(UserAdmin userAdmin) {
+ this.userAdmin = userAdmin;
+ this.userAdmin.createRole(KURA_PERMISSION_REST_ROLE, Role.GROUP);
+ }
+
+ public void bindRequestHandlerRegistry(RequestHandlerRegistry registry) {
+ try {
+ registry.registerRequestHandler(MQTT_APP_ID, this.requestHandler);
+ } catch (final Exception e) {
+ logger.warn("Failed to register {} request handler", MQTT_APP_ID, e);
+ }
+ }
+
+ // Added mainly for testing purposes. Currently the service is created by this endpoint.
+ public void bindIdentityService(IdentityService identityService) {
+ this.identityService = identityService;
+ }
+
+ public void unbindRequestHandlerRegistry(RequestHandlerRegistry registry) {
+ try {
+ registry.unregister(MQTT_APP_ID);
+ } catch (final Exception e) {
+ logger.warn("Failed to unregister {} request handler", MQTT_APP_ID, e);
+ }
+ }
+
+ public void activate() {
+ // create only if not set externally. Added mainly for testing purposes.
+ if (this.identityService == null) {
+ this.identityService = new IdentityService(this.cryptoService, this.userAdmin, this.configurationService);
+ }
+ }
+
+ @POST
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/identities")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response createUser(final UserDTO userName) {
+ try {
+ logger.debug(DEBUG_MESSAGE, "createUser");
+ this.identityService.createUser(userName);
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @PUT
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/identities")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response updateUser(final UserDTO user) {
+ try {
+ logger.debug(DEBUG_MESSAGE, "updateUser");
+ this.identityService.updateUser(user);
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @POST
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/identities/byName")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public UserDTO getUser(final UserDTO userName) {
+ try {
+ logger.debug(DEBUG_MESSAGE, "getUser");
+ return this.identityService.getUser(userName.getUserName());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ }
+
+ @DELETE
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/identities")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response deleteUser(final UserDTO userName) {
+ try {
+ logger.debug(DEBUG_MESSAGE, "deleteUser");
+ this.identityService.deleteUser(userName.getUserName());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/definedPermissions")
+ @Produces(MediaType.APPLICATION_JSON)
+ public PermissionDTO getDefinedPermissions() {
+ try {
+ logger.debug(DEBUG_MESSAGE, "getDefinedPermissions");
+ return new PermissionDTO(this.identityService.getDefinedPermissions());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+ }
+
+ @GET
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/identities")
+ @Produces(MediaType.APPLICATION_JSON)
+ public UserConfigDTO getUserConfig() {
+ try {
+ logger.debug(DEBUG_MESSAGE, "getUserConfig");
+ UserConfigDTO userConfig = new UserConfigDTO();
+ userConfig.setUserConfig(this.identityService.getUserConfig());
+ return userConfig;
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+ }
+
+ @GET
+ @Path("/passwordRequirements")
+ @Produces(MediaType.APPLICATION_JSON)
+ public ValidatorOptionsDTO getValidatorOptions() {
+ try {
+ logger.debug(DEBUG_MESSAGE, "getValidatorOptions");
+ ValidatorOptions validatorOptions = this.identityService.getValidatorOptions();
+ return new ValidatorOptionsDTO(//
+ validatorOptions.isPasswordMinimumLength(), //
+ validatorOptions.isPasswordRequireDigits(), //
+ validatorOptions.isPasswordRequireBothCases(), //
+ validatorOptions.isPasswordRequireSpecialChars());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+ }
+
+}
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/IdentityService.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/IdentityService.java
new file mode 100644
index 00000000000..438e2d74c0d
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/IdentityService.java
@@ -0,0 +1,252 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import org.eclipse.kura.KuraErrorCode;
+import org.eclipse.kura.KuraException;
+import org.eclipse.kura.configuration.ComponentConfiguration;
+import org.eclipse.kura.configuration.ConfigurationService;
+import org.eclipse.kura.crypto.CryptoService;
+import org.eclipse.kura.internal.rest.identity.provider.dto.UserDTO;
+import org.eclipse.kura.internal.rest.identity.provider.validator.PasswordStrengthValidators;
+import org.eclipse.kura.internal.rest.identity.provider.validator.Validator;
+import org.eclipse.kura.internal.rest.identity.provider.validator.ValidatorOptions;
+import org.eclipse.kura.util.useradmin.UserAdminHelper;
+import org.eclipse.kura.util.useradmin.UserAdminHelper.FallibleConsumer;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+@SuppressWarnings("restriction")
+public class IdentityService {
+
+ private static final String IDENTITY = "Identity ";
+ private static final String KURA_WEB_CONSOLE_SERVICE_PID = "org.eclipse.kura.web.Console";
+ private static final String PERMISSION_ROLE_NAME_PREFIX = "kura.permission.";
+ private static final String USER_ROLE_NAME_PREFIX = "kura.user.";
+
+ private static final String KURA_NEED_PASSWORD_CHANGE_PROPERTY = "kura.need.password.change";
+
+ private final UserAdminHelper userAdminHelper;
+ private final ConfigurationService configurationService;
+ private final CryptoService cryptoService;
+
+ public IdentityService(CryptoService cryptoService, UserAdmin userAdmin,
+ ConfigurationService configurationService) {
+
+ this.configurationService = configurationService;
+ this.cryptoService = cryptoService;
+
+ this.userAdminHelper = new UserAdminHelper(userAdmin, cryptoService);
+ }
+
+ public void createUser(UserDTO user) throws KuraException {
+ if (!this.userAdminHelper.getUser(user.getUserName()).isPresent()) {
+
+ final String password = user.getPassword();
+ if (password != null) {
+ validateUserPassword(password);
+ }
+
+ this.userAdminHelper.createUser(user.getUserName());
+ updateUser(user);
+ } else {
+ throw new KuraException(KuraErrorCode.BAD_REQUEST, IDENTITY + user.getUserName() + " already exists");
+ }
+ }
+
+ public void deleteUser(String userName) {
+ this.userAdminHelper.deleteUser(userName);
+ }
+
+ public UserDTO getUser(String userName) throws KuraException {
+ Optional user = this.userAdminHelper.getUser(userName);
+ if (user.isPresent()) {
+ UserDTO userFound = initUserConfig(user.get());
+ fillPermissions(Collections.singletonMap(user.get().getName(), userFound));
+ return userFound;
+ } else {
+ throw new KuraException(KuraErrorCode.NOT_FOUND, IDENTITY + userName + " not found");
+ }
+ }
+
+ public Set getDefinedPermissions() {
+ return this.userAdminHelper.getDefinedPermissions();
+ }
+
+ public Set getUserConfig() {
+ final Map result = new HashMap<>();
+
+ this.userAdminHelper.foreachUser((name, user) -> {
+
+ final UserDTO userData = initUserConfig(user);
+
+ result.put(user.getName(), userData);
+ });
+
+ fillPermissions(result);
+
+ return new HashSet<>(result.values());
+ }
+
+ private UserDTO initUserConfig(final User user) {
+
+ final boolean isPasswordEnabled = user.getCredentials()
+ .get(KURA_NEED_PASSWORD_CHANGE_PROPERTY) instanceof String;
+ final boolean isPasswordChangeRequired = Objects.equals("true",
+ user.getProperties().get(KURA_NEED_PASSWORD_CHANGE_PROPERTY));
+
+ return new UserDTO(getBaseName(user), new HashSet<>(), isPasswordEnabled, isPasswordChangeRequired);
+ }
+
+ private static boolean isKuraUser(final Role role) {
+ return role.getName().startsWith(USER_ROLE_NAME_PREFIX);
+ }
+
+ private static boolean isKuraPermission(final Role role) {
+ return role.getName().startsWith(PERMISSION_ROLE_NAME_PREFIX);
+ }
+
+ private static String getBaseName(final Role role) {
+ final String name = role.getName();
+
+ if (isKuraUser(role)) {
+ return name.substring(USER_ROLE_NAME_PREFIX.length());
+ } else if (isKuraPermission(role)) {
+ return name.substring(PERMISSION_ROLE_NAME_PREFIX.length());
+ } else {
+ throw new IllegalArgumentException("not a Kura role");
+ }
+ }
+
+ private void fillPermissions(final Map userData) {
+ this.userAdminHelper.foreachPermission((permission, group) ->
+
+ forEach(group.getMembers(), member -> {
+ final UserDTO data = userData.get(member.getName());
+
+ if (data != null) {
+ data.getPermissions().add(permission);
+ }
+ }));
+ }
+
+ private static void forEach(final T[] items, final FallibleConsumer consumer)
+ throws E {
+ if (items != null) {
+ for (final T item : items) {
+ consumer.accept(item);
+ }
+ }
+ }
+
+ public void updateUser(UserDTO userDTOToUpdate) throws KuraException {
+
+ final Optional user = this.userAdminHelper.getUser(userDTOToUpdate.getUserName());
+
+ if (user.isPresent()) {
+ final Set permissions = userDTOToUpdate.getPermissions();
+
+ if (permissions != null) {
+
+ this.userAdminHelper.foreachPermission((permissionName, permissionGroup) -> {
+
+ if (permissions.contains(permissionName)) {
+ permissionGroup.addMember(user.get());
+ } else {
+ permissionGroup.removeMember(user.get());
+ }
+ });
+
+ }
+
+ updatePasswordOptions(userDTOToUpdate, user.get().getCredentials(), user.get().getProperties());
+ } else {
+ throw new KuraException(KuraErrorCode.NOT_FOUND, IDENTITY + userDTOToUpdate.getUserName() + " not found");
+ }
+
+ }
+
+ private void updatePasswordOptions(UserDTO userDTO, final Dictionary credentials,
+ final Dictionary properties) throws KuraException {
+
+ final Optional isPasswordAuthEnabledParam = userDTO.isPasswordAuthEnabled();
+
+ if (isPasswordAuthEnabledParam.isPresent()) {
+
+ if (Boolean.TRUE.equals(isPasswordAuthEnabledParam.get())) {
+ final String password = userDTO.getPassword();
+
+ if (password != null) {
+ validateUserPassword(password);
+ try {
+ credentials.put(KURA_NEED_PASSWORD_CHANGE_PROPERTY, this.cryptoService.sha256Hash(password));
+ } catch (final Exception e) {
+ throw new KuraException(KuraErrorCode.SERVICE_UNAVAILABLE, e);
+ }
+ }
+ } else {
+ credentials.remove(KURA_NEED_PASSWORD_CHANGE_PROPERTY);
+ }
+ }
+
+ final Optional isPasswordChangeNeededParam = userDTO.isPasswordChangeNeeded();
+
+ if (isPasswordChangeNeededParam.isPresent()) {
+
+ if (Boolean.TRUE.equals(isPasswordChangeNeededParam.get())) {
+ properties.put(KURA_NEED_PASSWORD_CHANGE_PROPERTY, "true");
+ } else {
+ properties.remove(KURA_NEED_PASSWORD_CHANGE_PROPERTY);
+ }
+
+ }
+
+ }
+
+ public void validateUserPassword(String password) throws KuraException {
+
+ ValidatorOptions validatorOptions = getValidatorOptions();
+
+ final List> validators = PasswordStrengthValidators.fromConfig(validatorOptions);
+
+ final List errors = new ArrayList<>();
+
+ for (final Validator validator : validators) {
+ validator.validate(password, errors::add);
+ }
+
+ if (!errors.isEmpty()) {
+ throw new KuraException(KuraErrorCode.BAD_REQUEST, "password strenght requirements not satisfied", errors);
+ }
+ }
+
+ public ValidatorOptions getValidatorOptions() throws KuraException {
+ ComponentConfiguration consoleConfig = this.configurationService
+ .getComponentConfiguration(KURA_WEB_CONSOLE_SERVICE_PID);
+
+ return new ValidatorOptions(consoleConfig.getConfigurationProperties());
+
+ }
+}
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/PermissionDTO.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/PermissionDTO.java
new file mode 100644
index 00000000000..a4e04d0b72f
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/PermissionDTO.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.dto;
+
+import java.util.Set;
+
+public class PermissionDTO {
+
+ private final Set permissions;
+
+ public PermissionDTO(Set permissions) {
+ this.permissions = permissions;
+ }
+
+ public Set getPermissions() {
+ return this.permissions;
+ }
+
+}
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/UserConfigDTO.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/UserConfigDTO.java
new file mode 100644
index 00000000000..289d24cbfde
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/UserConfigDTO.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.dto;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class UserConfigDTO {
+
+ private Set userConfig = new HashSet<>();
+
+ public Set getUserConfig() {
+ return this.userConfig;
+ }
+
+ public void setUserConfig(Set userConfig) {
+ this.userConfig = userConfig;
+ }
+
+}
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/UserDTO.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/UserDTO.java
new file mode 100644
index 00000000000..dbe7df943a5
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/UserDTO.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.dto;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+public class UserDTO {
+
+ private String userName;
+ private Boolean passwordAuthEnabled;
+ private Boolean passwordChangeNeeded;
+ private Set permissions;
+ private String password;
+
+ public UserDTO() {
+
+ }
+
+ public UserDTO(final String userName, final Set permissions, final boolean passwordAuthEnabled,
+ final boolean passwordChangeNeeded, final String password) {
+
+ this.userName = userName;
+ this.passwordAuthEnabled = passwordAuthEnabled;
+ this.passwordChangeNeeded = passwordChangeNeeded;
+ this.permissions = permissions;
+ this.password = password;
+ }
+
+ public UserDTO(final String userName, final Set permissions, final boolean passwordAuthEnabled,
+ final boolean passwordChangeNeeded) {
+
+ this(userName, permissions, passwordAuthEnabled, passwordChangeNeeded, null);
+
+ }
+
+ public String getUserName() {
+ return this.userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public Optional isPasswordAuthEnabled() {
+ return Optional.ofNullable(this.passwordAuthEnabled);
+ }
+
+ public void setPasswordAuthEnabled(boolean passwordAuthEnabled) {
+ this.passwordAuthEnabled = passwordAuthEnabled;
+ }
+
+ public Optional isPasswordChangeNeeded() {
+ return Optional.ofNullable(this.passwordChangeNeeded);
+ }
+
+ public void setPasswordChangeNeeded(boolean passwordChangeNeeded) {
+ this.passwordChangeNeeded = passwordChangeNeeded;
+ }
+
+ public Set getPermissions() {
+ return this.permissions;
+ }
+
+ public void setPermissions(Set permissions) {
+ this.permissions = permissions;
+ }
+
+ public String getPassword() {
+ return this.password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.userName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if ((obj == null) || (getClass() != obj.getClass())) {
+ return false;
+ }
+ UserDTO other = (UserDTO) obj;
+ return Objects.equals(this.userName, other.userName);
+ }
+
+}
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/ValidatorOptionsDTO.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/ValidatorOptionsDTO.java
new file mode 100644
index 00000000000..2161dee9ceb
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/ValidatorOptionsDTO.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.dto;
+
+public class ValidatorOptionsDTO {
+
+ private final int passwordMinimumLength;
+ private final boolean passwordRequireDigits;
+ private final boolean passwordRequireSpecialChars;
+ private final boolean passwordRequireBothCases;
+
+ public ValidatorOptionsDTO(int passwordMinimumLength, boolean passwordRequireDigits,
+ boolean passwordRequireBothCases, boolean passwordRequireSpecialChars) {
+
+ this.passwordMinimumLength = passwordMinimumLength;
+ this.passwordRequireDigits = passwordRequireDigits;
+ this.passwordRequireSpecialChars = passwordRequireSpecialChars;
+ this.passwordRequireBothCases = passwordRequireBothCases;
+ }
+
+ public int getPasswordMinimumLength() {
+ return this.passwordMinimumLength;
+ }
+
+ public boolean isPasswordRequireDigits() {
+ return this.passwordRequireDigits;
+ }
+
+ public boolean isPasswordRequireSpecialChars() {
+ return this.passwordRequireSpecialChars;
+ }
+
+ public boolean isPasswordRequireBothCases() {
+ return this.passwordRequireBothCases;
+ }
+
+}
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/PasswordStrengthValidators.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/PasswordStrengthValidators.java
new file mode 100644
index 00000000000..65a707ab0d7
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/PasswordStrengthValidators.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.validator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PasswordStrengthValidators {
+
+ private static final String CONTAINS_DIGITS = ".*\\d.*";
+ private static final String CONTAINS_NOT_ALPHANUMERIC = ".*[^a-zA-Z0-9].*";
+ private static final String LOWERCASE = ".*[a-z].*";
+ private static final String UPPERCASE = ".*[A-Z].*";
+
+ private PasswordStrengthValidators() {
+ }
+
+ public static List> fromConfig(final ValidatorOptions userOptions) {
+ return fromConfig(userOptions, new DefaultMessages());
+ }
+
+ public static List> fromConfig(final ValidatorOptions userOptions, final Messages messages) {
+ final List> result = new ArrayList<>();
+
+ final int minPasswordLength = userOptions.isPasswordMinimumLength();
+
+ if (minPasswordLength > 0) {
+ result.add(passwordLengthValidator(minPasswordLength, messages));
+ }
+
+ if (userOptions.isPasswordRequireDigits()) {
+ result.add(containsDigitsValidator(messages));
+ }
+
+ if (userOptions.isPasswordRequireBothCases()) {
+ result.add(containsBothCases(messages));
+ }
+
+ if (userOptions.isPasswordRequireSpecialChars()) {
+ result.add(containsSpecialChars(messages));
+ }
+
+ return result;
+ }
+
+ private static Validator passwordLengthValidator(final int minPasswordLength, final Messages messages) {
+ return new PredicateValidator(v -> {
+ final int passwordLength = v == null ? 0 : v.length();
+ return passwordLength >= minPasswordLength;
+ }, messages.pwdStrengthMinLength(minPasswordLength));
+ }
+
+ private static Validator containsDigitsValidator(final Messages messages) {
+ return new RegexValidator(CONTAINS_DIGITS, messages.pwdStrengthDigitsRequired()) {
+ };
+ }
+
+ private static Validator containsSpecialChars(final Messages messages) {
+ return new RegexValidator(CONTAINS_NOT_ALPHANUMERIC, messages.pwdStrengthNonAlphanumericRequired()) {
+ };
+ }
+
+ private static Validator containsBothCases(final Messages messages) {
+ final RegexValidator containsLowercase = new RegexValidator(LOWERCASE, messages.pwdStrengthBothCasesRequired());
+ final RegexValidator containsUppercase = new RegexValidator(UPPERCASE, messages.pwdStrengthBothCasesRequired());
+
+ return (v, c) -> {
+ containsLowercase.validate(v, c);
+ containsUppercase.validate(v, c);
+ };
+ }
+
+ public interface Messages {
+
+ public String pwdStrengthDigitsRequired();
+
+ public String pwdStrengthNonAlphanumericRequired();
+
+ public String pwdStrengthBothCasesRequired();
+
+ public String pwdStrengthMinLength(final int value);
+ }
+
+ private static class DefaultMessages implements Messages {
+
+ @Override
+ public String pwdStrengthDigitsRequired() {
+ return "Password must contain at least one digit";
+ }
+
+ @Override
+ public String pwdStrengthNonAlphanumericRequired() {
+ return "Password must contain at least one non alphanumeric character";
+ }
+
+ @Override
+ public String pwdStrengthBothCasesRequired() {
+ return "Password must contain both uppercase and lowercase characters";
+ }
+
+ @Override
+ public String pwdStrengthMinLength(final int value) {
+ return "Password length must be at least " + value + " characters";
+ }
+ }
+
+}
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/PredicateValidator.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/PredicateValidator.java
new file mode 100644
index 00000000000..00d651fddd7
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/PredicateValidator.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.validator;
+
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+public class PredicateValidator implements Validator {
+
+ private final Predicate predicate;
+ private final String message;
+
+ public PredicateValidator(final Predicate predicate, final String message) {
+ this.predicate = predicate;
+ this.message = message;
+ }
+
+ @Override
+ public void validate(final String value, final Consumer errorMessageConsumer) {
+
+ if (!this.predicate.test(value)) {
+ errorMessageConsumer.accept(this.message);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/RegexValidator.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/RegexValidator.java
new file mode 100644
index 00000000000..b3ec585e37a
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/RegexValidator.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.validator;
+
+public class RegexValidator extends PredicateValidator {
+
+ public RegexValidator(final String pattern, final String message) {
+ super(v -> v.matches(pattern), message);
+ }
+
+}
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/Validator.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/Validator.java
new file mode 100644
index 00000000000..99e3956fe96
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/Validator.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+
+package org.eclipse.kura.internal.rest.identity.provider.validator;
+
+import java.util.function.Consumer;
+
+public interface Validator {
+
+ void validate(T value, Consumer errorMessageConsumer);
+}
diff --git a/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/ValidatorOptions.java b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/ValidatorOptions.java
new file mode 100644
index 00000000000..d65ff7d4f5f
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/ValidatorOptions.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.validator;
+
+import java.util.Map;
+
+import org.eclipse.kura.util.configuration.Property;
+
+public class ValidatorOptions {
+
+ private int passwordMinimumLength = 8;
+ private boolean passwordRequireDigits = false;
+ private boolean passwordRequireBothCases = false;
+ private boolean passwordRequireSpecialChars = false;
+
+ private static final String NEW_PASSW_MIN_LENGTH_PROP = "new.password.min.length";
+ private static final String NEW_PASSW_REQUIRE_DIGITS = "new.password.require.digits";
+ private static final String NEW_PASSW_REQUIRE_SPECIAL_CHARS = "new.password.require.special.characters";
+ private static final String NEW_PASSW_REQUIRE_BOTH_CASES = "new.password.require.both.cases";
+
+ private final Property newPasswMinLenghthProperty = new Property<>(NEW_PASSW_MIN_LENGTH_PROP, 8);
+ private final Property newPassRequireDigits = new Property<>(NEW_PASSW_REQUIRE_DIGITS, false);
+ private final Property newPassRequireSpecialChars = new Property<>(NEW_PASSW_REQUIRE_SPECIAL_CHARS, false);
+ private final Property newPassRequireBothCases = new Property<>(NEW_PASSW_REQUIRE_BOTH_CASES, false);
+
+ public ValidatorOptions(int passwordMinimumLength, boolean passwordRequireDigits, boolean passwordRequireBothCases,
+ boolean passwordRequireSpecialChars) {
+
+ this.passwordMinimumLength = passwordMinimumLength;
+ this.passwordRequireDigits = passwordRequireDigits;
+ this.passwordRequireSpecialChars = passwordRequireSpecialChars;
+ this.passwordRequireBothCases = passwordRequireBothCases;
+ }
+
+ public ValidatorOptions(Map configurationProperties) {
+ if (configurationProperties != null) {
+ this.passwordMinimumLength = this.newPasswMinLenghthProperty.get(configurationProperties);
+ this.passwordRequireDigits = this.newPassRequireDigits.get(configurationProperties);
+ this.passwordRequireSpecialChars = this.newPassRequireSpecialChars.get(configurationProperties);
+ this.passwordRequireBothCases = this.newPassRequireBothCases.get(configurationProperties);
+ }
+ }
+
+ public int isPasswordMinimumLength() {
+ return this.passwordMinimumLength;
+ }
+
+ public boolean isPasswordRequireDigits() {
+ return this.passwordRequireDigits;
+ }
+
+ public boolean isPasswordRequireBothCases() {
+ return this.passwordRequireBothCases;
+ }
+
+ public boolean isPasswordRequireSpecialChars() {
+ return this.passwordRequireSpecialChars;
+ }
+
+}
diff --git a/kura/org.eclipse.kura.util/src/main/java/org/eclipse/kura/util/useradmin/UserAdminHelper.java b/kura/org.eclipse.kura.util/src/main/java/org/eclipse/kura/util/useradmin/UserAdminHelper.java
index fe21f7e64f5..7dc67086ee9 100644
--- a/kura/org.eclipse.kura.util/src/main/java/org/eclipse/kura/util/useradmin/UserAdminHelper.java
+++ b/kura/org.eclipse.kura.util/src/main/java/org/eclipse/kura/util/useradmin/UserAdminHelper.java
@@ -89,7 +89,6 @@ public void requirePermissions(final String username, final String... permission
}
}
- @SuppressWarnings("unchecked")
public void changeUserPassword(final String username, final String userPassword) throws AuthenticationException {
final User user = getUser(username)
.orElseThrow(() -> new AuthenticationException(AuthenticationException.Reason.USER_NOT_FOUND));
@@ -123,7 +122,8 @@ public boolean isPasswordChangeRequired(final String username) {
}
public void createUser(final String userName) {
- getOrCreateUser(getUserRoleName(userName));
+ User createdUser = getOrCreateUser(userName);
+ Objects.requireNonNull(createdUser, "Could not create user " + userName);
}
public void deleteUser(final String userName) {
@@ -290,6 +290,8 @@ public void foreachPermission(final PermissionConsumer
public static class AuthenticationException extends Exception {
+ private static final long serialVersionUID = -8534499595655286448L;
+
public enum Reason {
USER_NOT_FOUND,
INCORRECT_PASSWORD,
diff --git a/kura/pom.xml b/kura/pom.xml
index 5b084074f5b..6298f233c43 100644
--- a/kura/pom.xml
+++ b/kura/pom.xml
@@ -126,6 +126,7 @@
org.eclipse.kura.wire.script.tools
org.eclipse.kura.db.sqlite.provider
org.eclipse.kura.rest.network.status.provider
+ org.eclipse.kura.rest.identity.provider
emulator
test-util
diff --git a/kura/test/org.eclipse.kura.core.testutil/src/main/java/org/eclipse/kura/core/testutil/requesthandler/Transport.java b/kura/test/org.eclipse.kura.core.testutil/src/main/java/org/eclipse/kura/core/testutil/requesthandler/Transport.java
index 4167532c5c6..6a66df98268 100644
--- a/kura/test/org.eclipse.kura.core.testutil/src/main/java/org/eclipse/kura/core/testutil/requesthandler/Transport.java
+++ b/kura/test/org.eclipse.kura.core.testutil/src/main/java/org/eclipse/kura/core/testutil/requesthandler/Transport.java
@@ -49,19 +49,29 @@ public class MethodSpec {
public MethodSpec(final String method) {
this.requestHandlerMethod = method;
this.restMethod = method;
+
+ if (this.requestHandlerMethod.equalsIgnoreCase("DELETE")) {
+ throw new IllegalArgumentException(
+ "Method " + this.requestHandlerMethod + " is not allowed for RequestHandler");
+ }
}
public MethodSpec(final String restMethod, final String requestHandlerMethod) {
this.restMethod = restMethod;
this.requestHandlerMethod = requestHandlerMethod;
+
+ if (this.requestHandlerMethod.equalsIgnoreCase("DELETE")) {
+ throw new IllegalArgumentException(
+ "Method " + this.requestHandlerMethod + " is not allowed for RequestHandler");
+ }
}
public String getRestMethod() {
- return restMethod;
+ return this.restMethod;
}
public String getRequestHandlerMethod() {
- return requestHandlerMethod;
+ return this.requestHandlerMethod;
}
}
}
diff --git a/kura/test/org.eclipse.kura.rest.identity.provider.test/META-INF/MANIFEST.MF b/kura/test/org.eclipse.kura.rest.identity.provider.test/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..c504d0cf628
--- /dev/null
+++ b/kura/test/org.eclipse.kura.rest.identity.provider.test/META-INF/MANIFEST.MF
@@ -0,0 +1,28 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: org.eclipse.kura.rest.identity.provider.test
+Bundle-SymbolicName: org.eclipse.kura.rest.identity.provider.test
+Bundle-Version: 5.4.0.qualifier
+Bundle-Vendor: Eclipse Kura
+Bundle-License: Eclipse Public License v2.0
+Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
+Bundle-ActivationPolicy: lazy
+Import-Package: com.google.gson;version="2.9.0",
+ javax.annotation;version="1.2.0",
+ org.eclipse.kura.core.testutil.requesthandler;version="1.1.0",
+ org.eclipse.kura.util.wire.test;version="1.1.0",
+ org.junit;version="[4.12.0,5.0.0)",
+ org.junit.runner;version="[4.12.0,5.0.0)",
+ org.junit.runners;version="[4.12.0,5.0.0)",
+ org.mockito;version="[4.0.0,5.0.0)",
+ org.mockito.invocation;version="[4.0.0,5.0.0)",
+ org.mockito.stubbing;version="[4.0.0,5.0.0)",
+ org.osgi.framework;version="1.10.0",
+ org.osgi.service.cm;version="1.6.0",
+ org.osgi.service.useradmin;version="1.1.0",
+ org.osgi.util.tracker;version="1.5.2",
+ org.slf4j;version="1.7.25"
+Fragment-Host: org.eclipse.kura.rest.identity.provider
+Require-Bundle: org.eclipse.kura.http.server.manager;bundle-version="1.1.0",
+ org.eclipse.kura.broker.artemis.core;bundle-version="1.2.0",
+ org.eclipse.kura.broker.artemis.simple.mqtt;bundle-version="1.1.0"
diff --git a/kura/test/org.eclipse.kura.rest.identity.provider.test/build.properties b/kura/test/org.eclipse.kura.rest.identity.provider.test/build.properties
new file mode 100644
index 00000000000..d69abee4fb4
--- /dev/null
+++ b/kura/test/org.eclipse.kura.rest.identity.provider.test/build.properties
@@ -0,0 +1,16 @@
+#
+# Copyright (c) 2023 Eurotech and/or its affiliates and others
+#
+# This program and the accompanying materials are made
+# available under the terms of the Eclipse Public License 2.0
+# which is available at https://www.eclipse.org/legal/epl-2.0/
+#
+# SPDX-License-Identifier: EPL-2.0
+#
+# Contributors:
+# Eurotech
+#
+source.. = src/main/java,\
+ src/test/java
+bin.includes = META-INF/,\
+ .
diff --git a/kura/test/org.eclipse.kura.rest.identity.provider.test/pom.xml b/kura/test/org.eclipse.kura.rest.identity.provider.test/pom.xml
new file mode 100644
index 00000000000..0cef6efd1dd
--- /dev/null
+++ b/kura/test/org.eclipse.kura.rest.identity.provider.test/pom.xml
@@ -0,0 +1,71 @@
+
+
+
+ 4.0.0
+
+
+ org.eclipse.kura
+ test
+ 5.4.0-SNAPSHOT
+
+
+ org.eclipse.kura.rest.identity.provider.test
+ eclipse-test-plugin
+
+
+ ${project.basedir}/../..
+ ${project.build.directory}/site/jacoco-aggregate/jacoco.xml
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compiletests
+ test-compile
+
+ testCompile
+
+
+
+
+
+ org.eclipse.tycho
+ tycho-surefire-plugin
+
+ classes
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ org.eclipse.tycho
+ target-platform-configuration
+
+
+
+
diff --git a/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityEndpointsTest.java b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityEndpointsTest.java
new file mode 100644
index 00000000000..57683c1077e
--- /dev/null
+++ b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityEndpointsTest.java
@@ -0,0 +1,243 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Optional;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.kura.KuraException;
+import org.eclipse.kura.core.testutil.requesthandler.AbstractRequestHandlerTest;
+import org.eclipse.kura.core.testutil.requesthandler.MqttTransport;
+import org.eclipse.kura.core.testutil.requesthandler.RestTransport;
+import org.eclipse.kura.core.testutil.requesthandler.Transport;
+import org.eclipse.kura.core.testutil.requesthandler.Transport.MethodSpec;
+import org.eclipse.kura.internal.rest.identity.provider.IdentityRestService;
+import org.eclipse.kura.internal.rest.identity.provider.IdentityService;
+import org.eclipse.kura.internal.rest.identity.provider.dto.UserDTO;
+import org.eclipse.kura.internal.rest.identity.provider.validator.ValidatorOptions;
+import org.eclipse.kura.util.wire.test.WireTestUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+import com.google.gson.Gson;
+
+@RunWith(Parameterized.class)
+public class IdentityEndpointsTest extends AbstractRequestHandlerTest {
+
+ private static final String MQTT_APP_ID = "IDN-V1";
+
+ private static final String METHOD_SPEC_GET = "GET";
+ private static final String METHOD_SPEC_POST = "POST";
+ private static final String METHOD_SPEC_DELETE = "DELETE";
+ private static final String MQTT_METHOD_SPEC_DEL = "DEL";
+ private static final String METHOD_SPEC_PUT = "PUT";
+ private static final String REST_APP_ID = "identity/v1";
+
+ private static IdentityService identityServiceMock = mock(IdentityService.class);
+
+ private static UserDTO user;
+
+ private Gson gson = new Gson();
+
+ private static final String EXPECTED_GET_USER_CONFIG_RESPONSE = new Scanner(
+ IdentityEndpointsTest.class.getResourceAsStream("/getUserConfigResponse.json"), "UTF-8").useDelimiter("\\A")
+ .next().replace(" ", "");
+
+ private static final String EXPECTED_GET_USER_RESPONSE = new Scanner(
+ IdentityEndpointsTest.class.getResourceAsStream("/getUserResponse.json"), "UTF-8").useDelimiter("\\A")
+ .next().replace(" ", "");
+
+ private static final String EXPECTED_GET_PASSWORD_REQUIREMENTS_RESPONSE = new Scanner(
+ IdentityEndpointsTest.class.getResourceAsStream("/getPasswordRequirementsResponse.json"), "UTF-8")
+ .useDelimiter("\\A").next().replace(" ", "");
+
+ private static Set userConfigs;
+
+ private static ServiceRegistration identityServiceRegistration;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection transports() {
+ return Arrays.asList(new MqttTransport(MQTT_APP_ID), new RestTransport(REST_APP_ID));
+ }
+
+ public IdentityEndpointsTest(Transport transport) {
+ super(transport);
+ }
+
+ @Test
+ public void shouldInvokeCreateUserSuccessfully() throws KuraException {
+ givenUser(new UserDTO("testuser", Collections.emptySet(), true, false, "testpassw"));
+
+ givenIdentityService();
+
+ whenRequestIsPerformed(new MethodSpec(METHOD_SPEC_POST), "/identities", gson.toJson(user));
+
+ thenRequestSucceeds();
+ thenResponseBodyIsEmpty();
+ }
+
+ @Test
+ public void shouldInvokeUpdateUserSuccessfully() throws KuraException {
+ givenUser(new UserDTO("testuser", Collections.emptySet(), true, false, "testpassw"));
+
+ givenIdentityService();
+
+ whenRequestIsPerformed(new MethodSpec(METHOD_SPEC_PUT), "/identities", gson.toJson(user));
+
+ thenRequestSucceeds();
+ thenResponseBodyIsEmpty();
+ }
+
+ @Test
+ public void shouldInvokeDeleteUserSuccessfully() throws KuraException {
+ givenUser(new UserDTO("testuser", Collections.emptySet(), true, false, "testpassw"));
+
+ givenIdentityService();
+
+ whenRequestIsPerformed(new MethodSpec(METHOD_SPEC_DELETE, MQTT_METHOD_SPEC_DEL), "/identities",
+ gson.toJson(user));
+
+ thenRequestSucceeds();
+ thenResponseBodyIsEmpty();
+ }
+
+ @Test
+ public void shouldReturnUserSuccessfully() throws KuraException {
+ givenUser(new UserDTO("testuser3", Collections.emptySet(), true, false));
+
+ givenIdentityService();
+
+ whenRequestIsPerformed(new MethodSpec(METHOD_SPEC_POST), "/identities/byName", gson.toJson(user));
+
+ thenRequestSucceeds();
+ thenResponseBodyEqualsJson(EXPECTED_GET_USER_RESPONSE);
+ }
+
+ @Test
+ public void shouldReturnUserConfig() throws KuraException {
+ givenUserConfigs(new UserDTO("testuser2", //
+ new HashSet(Arrays.asList("perm1", "perm2")), //
+ false, //
+ true));
+
+ givenIdentityService();
+
+ whenRequestIsPerformed(new MethodSpec(METHOD_SPEC_GET), "/identities");
+
+ thenRequestSucceeds();
+ thenResponseBodyEqualsJson(EXPECTED_GET_USER_CONFIG_RESPONSE);
+ }
+
+ @Test
+ public void shouldReturnDefinedPermissions() throws KuraException {
+ givenIdentityService();
+
+ whenRequestIsPerformed(new MethodSpec(METHOD_SPEC_GET), "/definedPermissions");
+
+ thenRequestSucceeds();
+ thenResponseBodyEqualsJson("{\"permissions\": [\"perm1\",\"perm2\"]}");
+ }
+
+ @Test
+ public void shouldReturnPasswordRequirements() throws KuraException {
+ givenIdentityService();
+
+ whenRequestIsPerformed(new MethodSpec(METHOD_SPEC_GET), "/passwordRequirements");
+
+ thenRequestSucceeds();
+ thenResponseBodyEqualsJson(EXPECTED_GET_PASSWORD_REQUIREMENTS_RESPONSE);
+ }
+
+ private void givenUser(UserDTO userParam) {
+ user = userParam;
+ }
+
+ private void givenUserConfigs(UserDTO... userConfigurations) {
+ userConfigs = new HashSet<>(Arrays.asList(userConfigurations));
+ }
+
+ private static void givenIdentityService() throws KuraException {
+ reset(identityServiceMock);
+
+ when(identityServiceMock.getDefinedPermissions())
+ .thenReturn(new HashSet(Arrays.asList("perm1", "perm2")));
+
+ when(identityServiceMock.getUserConfig()).thenReturn(userConfigs);
+
+ if (user != null) {
+ when(identityServiceMock.getUser("testuser3"))
+ .thenReturn(new UserDTO("testuser3", Collections.emptySet(), true, false));
+
+ when(identityServiceMock.getUser("testuser"))
+ .thenReturn(new UserDTO("testuser", Collections.emptySet(), true, false));
+ }
+
+ when(identityServiceMock.getValidatorOptions()).thenReturn(new ValidatorOptions(8, false, false, false));
+ }
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ createIdentityServiceMock();
+ registerIdentityServiceMock();
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ unregisterIdentityServiceMock();
+ }
+
+ private static void createIdentityServiceMock() throws KuraException {
+ givenIdentityService();
+
+ final Dictionary configurationServiceProperties = new Hashtable<>();
+ configurationServiceProperties.put("service.ranking", Integer.MIN_VALUE);
+ configurationServiceProperties.put("kura.service.pid", "identityServiceMock");
+ identityServiceRegistration = FrameworkUtil.getBundle(IdentityEndpointsTest.class).getBundleContext()
+ .registerService(IdentityService.class, identityServiceMock, configurationServiceProperties);
+ }
+
+ private static void registerIdentityServiceMock() throws Exception {
+ final Dictionary properties = new Hashtable<>();
+ properties.put("IdentityService.target", "(kura.service.pid=identityServiceMock)");
+
+ final ConfigurationAdmin configurationAdmin = WireTestUtil
+ .trackService(ConfigurationAdmin.class, Optional.empty()).get(30, TimeUnit.SECONDS);
+ final Configuration config = configurationAdmin.getConfiguration(IdentityRestService.class.getName(), "?");
+ config.update(properties);
+ }
+
+ private static void unregisterIdentityServiceMock() {
+ identityServiceRegistration.unregister();
+
+ }
+
+}
diff --git a/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getPasswordRequirementsResponse.json b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getPasswordRequirementsResponse.json
new file mode 100644
index 00000000000..2568733c6e1
--- /dev/null
+++ b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getPasswordRequirementsResponse.json
@@ -0,0 +1,6 @@
+{
+ "passwordMinimumLength": 8,
+ "passwordRequireDigits": false,
+ "passwordRequireSpecialChars": false,
+ "passwordRequireBothCases": false
+}
\ No newline at end of file
diff --git a/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getUserConfigResponse.json b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getUserConfigResponse.json
new file mode 100644
index 00000000000..7393a1f4d66
--- /dev/null
+++ b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getUserConfigResponse.json
@@ -0,0 +1,13 @@
+{
+ "userConfig": [
+ {
+ "userName": "testuser2",
+ "passwordAuthEnabled": false,
+ "passwordChangeNeeded": true,
+ "permissions": [
+ "perm1",
+ "perm2"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getUserResponse.json b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getUserResponse.json
new file mode 100644
index 00000000000..2c63abc6cb2
--- /dev/null
+++ b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getUserResponse.json
@@ -0,0 +1,6 @@
+{
+ "userName": "testuser3",
+ "passwordAuthEnabled": true,
+ "passwordChangeNeeded": false,
+ "permissions": []
+}
\ No newline at end of file
diff --git a/kura/test/org.eclipse.kura.rest.identity.provider.test/src/test/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityRestServiceDependenciesTest.java b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/test/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityRestServiceDependenciesTest.java
new file mode 100644
index 00000000000..70d30044eea
--- /dev/null
+++ b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/test/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityRestServiceDependenciesTest.java
@@ -0,0 +1,221 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.test;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Objects;
+
+import org.eclipse.kura.KuraErrorCode;
+import org.eclipse.kura.KuraException;
+import org.eclipse.kura.cloudconnection.request.RequestHandler;
+import org.eclipse.kura.cloudconnection.request.RequestHandlerRegistry;
+import org.eclipse.kura.configuration.ConfigurationService;
+import org.eclipse.kura.crypto.CryptoService;
+import org.eclipse.kura.internal.rest.identity.provider.IdentityRestService;
+import org.junit.Test;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+
+public class IdentityRestServiceDependenciesTest {
+
+ private static final String MQTT_APP_ID = "IDN-V1";
+ private static final String REST_ROLE_NAME = "identity";
+ private static final String KURA_PERMISSION_REST_ROLE = "kura.permission.rest." + REST_ROLE_NAME;
+
+ private final IdentityRestService service = new IdentityRestService();
+ private UserAdmin userAdmin;
+ private CryptoService cryptoService;
+ private ConfigurationService configurationService;
+
+ private RequestHandlerRegistry requestHandlerRegistry;
+ private Exception occurredException;
+
+ /*
+ * Scenarios
+ */
+
+ @Test
+ public void shouldCreateRoleOnUserAdminBinding() {
+ givenMockUserAdmin();
+
+ whenBindUserAdmin();
+
+ thenRoleIsCreated(KURA_PERMISSION_REST_ROLE, Role.GROUP);
+ }
+
+ @Test
+ public void shouldRegisterRequestHandler() throws KuraException {
+ givenMockRequestHandlerRegistry();
+
+ whenBindRequestHandlerRegistry();
+
+ thenRequestHandlerIsRegistered(MQTT_APP_ID);
+ }
+
+ @Test
+ public void shouldUnregisterRequestHandler() throws KuraException {
+ givenMockRequestHandlerRegistry();
+ givenBoundRequestHandlerRegistry();
+
+ whenUnbindRequestHandlerRegistry();
+
+ thenRequestHandlerIsUnregistered(MQTT_APP_ID);
+ }
+
+ @Test
+ public void shouldCatchExceptionsOnRequestHandlerBind() throws KuraException {
+ givenFailingMockRequestHandlerRegistry();
+
+ whenBindRequestHandlerRegistry();
+
+ thenNoExceptionOccurred();
+ }
+
+ @Test
+ public void shouldCatchExceptionsOnRequestHandlerUnbind() throws KuraException {
+ givenFailingMockRequestHandlerRegistry();
+
+ whenUnbindRequestHandlerRegistry();
+
+ thenNoExceptionOccurred();
+ }
+
+ @Test
+ public void shouldCreateIdentityService() throws KuraException {
+ givenMockUserAdmin();
+ givenMockCryptoService();
+ givenMockConfigurationService();
+ givenBoundCryptoService();
+ givensBoundConfigurationService();
+ givenBoundUserAdmin();
+
+ whenActivate();
+
+ thenNoExceptionOccurred();
+ }
+
+ /*
+ * Given
+ */
+
+ private void givenMockCryptoService() {
+ this.cryptoService = mock(CryptoService.class);
+ }
+
+ private void givenMockConfigurationService() {
+ this.configurationService = mock(ConfigurationService.class);
+ }
+
+ private void givenMockUserAdmin() {
+ this.userAdmin = mock(UserAdmin.class);
+ }
+
+ private void givenBoundCryptoService() {
+ this.service.bindCryptoService(this.cryptoService);
+ }
+
+ private void givensBoundConfigurationService() {
+ this.service.bindConfigurationService(this.configurationService);
+ }
+
+ private void givenBoundUserAdmin() {
+ this.service.bindUserAdmin(this.userAdmin);
+ }
+
+ private void givenMockRequestHandlerRegistry() {
+ this.requestHandlerRegistry = mock(RequestHandlerRegistry.class);
+ }
+
+ private void givenBoundRequestHandlerRegistry() {
+ bindRequestHandlerRegistry();
+ }
+
+ private void givenFailingMockRequestHandlerRegistry() throws KuraException {
+ this.requestHandlerRegistry = mock(RequestHandlerRegistry.class);
+ doThrow(new KuraException(KuraErrorCode.BAD_REQUEST)).when(this.requestHandlerRegistry)
+ .registerRequestHandler(any(), any());
+ doThrow(new KuraException(KuraErrorCode.BAD_REQUEST)).when(this.requestHandlerRegistry).unregister(any());
+ }
+
+ /*
+ * When
+ */
+
+ private void whenBindUserAdmin() {
+ this.service.bindUserAdmin(this.userAdmin);
+ }
+
+ private void whenBindRequestHandlerRegistry() {
+ bindRequestHandlerRegistry();
+ }
+
+ private void whenUnbindRequestHandlerRegistry() {
+ try {
+ this.service.unbindRequestHandlerRegistry(this.requestHandlerRegistry);
+ } catch (Exception e) {
+ this.occurredException = e;
+ }
+ }
+
+ private void whenActivate() {
+ this.service.activate();
+ }
+
+ /*
+ * Then
+ */
+
+ private void thenRoleIsCreated(String expectedKuraPermission, int expectedRole) {
+ verify(this.userAdmin, times(1)).createRole(expectedKuraPermission, expectedRole);
+ }
+
+ private void thenRequestHandlerIsRegistered(String expectedMqttAppId) throws KuraException {
+ verify(this.requestHandlerRegistry, times(1)).registerRequestHandler(eq(expectedMqttAppId),
+ any(RequestHandler.class));
+ }
+
+ private void thenRequestHandlerIsUnregistered(String expectedMqttAppId) throws KuraException {
+ verify(this.requestHandlerRegistry, times(1)).unregister(expectedMqttAppId);
+ }
+
+ private void thenNoExceptionOccurred() {
+ String errorMessage = "Empty message";
+ if (Objects.nonNull(this.occurredException)) {
+ StringWriter sw = new StringWriter();
+ this.occurredException.printStackTrace(new PrintWriter(sw));
+
+ errorMessage = String.format("No exception expected, \"%s\" found. Caused by: %s",
+ this.occurredException.getClass().getName(), sw.toString());
+ }
+
+ assertNull(errorMessage, this.occurredException);
+ }
+
+ private void bindRequestHandlerRegistry() {
+ try {
+ this.service.bindRequestHandlerRegistry(this.requestHandlerRegistry);
+ } catch (Exception e) {
+ this.occurredException = e;
+ }
+ }
+
+}
diff --git a/kura/test/org.eclipse.kura.rest.identity.provider.test/src/test/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityServiceTest.java b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/test/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityServiceTest.java
new file mode 100644
index 00000000000..7fd9587c11e
--- /dev/null
+++ b/kura/test/org.eclipse.kura.rest.identity.provider.test/src/test/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityServiceTest.java
@@ -0,0 +1,446 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.identity.provider.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.eclipse.kura.KuraException;
+import org.eclipse.kura.configuration.ComponentConfiguration;
+import org.eclipse.kura.configuration.ConfigurationService;
+import org.eclipse.kura.crypto.CryptoService;
+import org.eclipse.kura.internal.rest.identity.provider.IdentityService;
+import org.eclipse.kura.internal.rest.identity.provider.dto.UserDTO;
+import org.eclipse.kura.internal.rest.identity.provider.validator.ValidatorOptions;
+import org.junit.Test;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+public class IdentityServiceTest {
+
+ private static final String KURA_WEB_CONSOLE_SERVICE_PID = "org.eclipse.kura.web.Console";
+ private static final String USER_ROLE_NAME_PREFIX = "kura.user.";
+ private static final String PERMISSION_ROLE_NAME_PREFIX = "kura.permission.";
+
+ private IdentityService identityService;
+ private Exception occurredException;
+ private UserDTO user;
+ private UserDTO newUser;
+
+ private CryptoService cryptoService;
+ private UserAdmin userAdmin;
+ private ConfigurationService configurationService;
+
+ private Set definedPermissions;
+ private Set userConfig;
+ private boolean isPasswordValid;
+ private ValidatorOptions validatorOptions;
+
+ @Test
+ public void shouldCreateAnUser() throws KuraException {
+ givenIdentityService();
+
+ whenCreatingUser(new UserDTO("testuser", Collections.emptySet(), true, false, "testpassw"));
+
+ thenNoExceptionOccurred();
+ thenUserIsCreated();
+ }
+
+ @Test
+ public void shouldDeleteExistingUser() {
+ givenIdentityService();
+ givenExistingUser(new UserDTO("testuser", Collections.emptySet(), true, false, "testpassw"));
+
+ whenDeleting("testuser");
+
+ thenNoExceptionOccurred();
+ thenUserIsDeleted();
+ }
+
+ @Test
+ public void shouldGetDefinedPermissions() throws InvalidSyntaxException {
+ givenIdentityService();
+ givenExistingPermissions(new PermissionRole("perm1"), new PermissionRole("perm2"));
+
+ whenGettingDefinedPermissions();
+
+ thenNoExceptionOccurred();
+ thenPermissionsAre("perm1", "perm2");
+
+ }
+
+ @Test
+ public void shoulGetUser() {
+ givenIdentityService();
+ givenExistingUser(new UserDTO("testuser", Collections.emptySet(), true, false, "testpassw"));
+
+ whenGettingUser("testuser");
+
+ thenNoExceptionOccurred();
+ thenUserIs("testuser");
+
+ }
+
+ private void whenGettingUser(String username) {
+ try {
+ this.user = this.identityService.getUser(username);
+ } catch (KuraException e) {
+ this.occurredException = e;
+ }
+ }
+
+ @Test
+ public void shouldGetUserConfig() {
+ givenIdentityService();
+ givenExistingUser(new UserDTO("testuser", Collections.emptySet(), true, false, "testpassw"));
+
+ whenGetUserConfig();
+
+ thenNoExceptionOccurred();
+ thenConfigContains(new UserDTO("testuser", Collections.emptySet(), true, false, "testpassw"));
+ }
+
+ @Test
+ public void shouldGetValidatorOptions() {
+ givenIdentityService();
+
+ whenGettingValidatorOptions();
+
+ thenNoExceptionOccurred();
+ thenValidatorOptionsAre(8, false, false, false);
+
+ }
+
+ private void thenValidatorOptionsAre(int passwordMinimumLength, boolean passwordRequireDigits,
+ boolean passwordRequireBothCases, boolean passwordRequireSpecialChars) {
+
+ assertEquals(passwordMinimumLength, this.validatorOptions.isPasswordMinimumLength());
+ assertEquals(passwordRequireDigits, this.validatorOptions.isPasswordRequireDigits());
+ assertEquals(passwordRequireBothCases, this.validatorOptions.isPasswordRequireBothCases());
+ assertEquals(passwordRequireSpecialChars, this.validatorOptions.isPasswordRequireSpecialChars());
+
+ }
+
+ @Test
+ public void shouldUpdatedUser() throws InvalidSyntaxException {
+ givenIdentityService();
+ givenExistingUser(new UserDTO("testuser", Collections.emptySet(), true, false, "testpassw"));
+
+ whenUpdatingUser(new UserDTO("testuser", Collections.emptySet(), true, true, "testpassw2"));
+
+ thenNoExceptionOccurred();
+ thenUserIsUpdated();
+ }
+
+ @Test
+ public void shouldValidateRightPassword() {
+ givenIdentityService();
+
+ whenValidatingPassword("password123");
+
+ thenNoExceptionOccurred();
+ thenPasswordValidationIs(true);
+ }
+
+ @Test
+ public void shouldNotValidateWrongPassword() {
+ givenIdentityService();
+
+ whenValidatingPassword("short");
+
+ thenPasswordValidationIs(false);
+ }
+
+ private void whenValidatingPassword(String password) {
+ try {
+ this.identityService.validateUserPassword(password);
+ this.isPasswordValid = true;
+ } catch (Exception e) {
+ this.isPasswordValid = false;
+ this.occurredException = e;
+ }
+ }
+
+ private void givenIdentityService() {
+ this.cryptoService = mock(CryptoService.class);
+ this.userAdmin = mock(UserAdmin.class);
+ this.configurationService = mock(ConfigurationService.class);
+
+ ComponentConfiguration mockWebConsoleConfiguration = mock(ComponentConfiguration.class);
+ when(mockWebConsoleConfiguration.getConfigurationProperties()).thenReturn(defaultValidatorProperties());
+
+ try {
+ when(this.configurationService.getComponentConfiguration(KURA_WEB_CONSOLE_SERVICE_PID))
+ .thenReturn(mockWebConsoleConfiguration);
+
+ when(this.cryptoService.sha256Hash(anyString())).thenReturn("sha256hash");
+ } catch (KuraException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ fail("fail to setup mocks");
+ }
+
+ this.identityService = new IdentityService(this.cryptoService, this.userAdmin, this.configurationService);
+
+ }
+
+ private void givenExistingUser(UserDTO user) {
+ this.user = user;
+
+ UserImpl userImpl = new UserImpl(this.user.getUserName(), this.user.getPassword(),
+ this.user.isPasswordChangeNeeded().get());
+
+ try {
+ when(this.userAdmin.getRole(USER_ROLE_NAME_PREFIX + user.getUserName())).thenReturn(userImpl);
+ when(this.userAdmin.getRoles(null)).thenReturn(new Role[] { userImpl });
+ } catch (InvalidSyntaxException e) {
+ fail("unable to setup mock user admin");
+ }
+ }
+
+ private void givenExistingPermissions(Role... roles) throws InvalidSyntaxException {
+ when(this.userAdmin.getRoles(null)).thenReturn(roles);
+ }
+
+ private void whenCreatingUser(UserDTO newUser) {
+ try {
+ this.newUser = newUser;
+ when(this.userAdmin.createRole(USER_ROLE_NAME_PREFIX + newUser.getUserName(), Role.USER)).then(anser -> {
+
+ User userImpl = new UserImpl(newUser.getUserName(), newUser.getPassword(),
+ newUser.isPasswordChangeNeeded().get());
+
+ when(this.userAdmin.getRole(USER_ROLE_NAME_PREFIX + newUser.getUserName())).thenReturn(userImpl);
+
+ return userImpl;
+ });
+ this.identityService.createUser(this.newUser);
+ } catch (Exception e) {
+ this.occurredException = e;
+ }
+ }
+
+ private void whenDeleting(String username) {
+ try {
+ this.identityService.deleteUser(username);
+ } catch (Exception e) {
+ this.occurredException = e;
+ }
+ }
+
+ private void whenGettingDefinedPermissions() {
+ this.definedPermissions = this.identityService.getDefinedPermissions();
+ }
+
+ private void whenGetUserConfig() {
+ this.userConfig = this.identityService.getUserConfig();
+ }
+
+ private void whenGettingValidatorOptions() {
+ try {
+ this.validatorOptions = this.identityService.getValidatorOptions();
+ } catch (Exception e) {
+ this.occurredException = e;
+ }
+
+ }
+
+ private void whenUpdatingUser(UserDTO user) {
+ this.user = user;
+ try {
+ this.identityService.updateUser(user);
+ } catch (Exception e) {
+ this.occurredException = e;
+ }
+ }
+
+ private void thenUserIsCreated() throws KuraException {
+ verify(this.userAdmin, times(1)).createRole(USER_ROLE_NAME_PREFIX + this.newUser.getUserName(), Role.USER);
+ }
+
+ private void thenUserIsDeleted() {
+ verify(this.userAdmin, times(1)).removeRole(USER_ROLE_NAME_PREFIX + this.user.getUserName());
+ }
+
+ private void thenPermissionsAre(String... permissions) {
+ assertTrue(new HashSet<>(this.definedPermissions).containsAll(Arrays.asList(permissions)));
+ }
+
+ private void thenUserIs(String username) {
+ assertEquals(username, this.user.getUserName());
+ }
+
+ private void thenConfigContains(UserDTO userDTO) {
+ assertTrue(this.userConfig.contains(userDTO));
+ }
+
+ private void thenUserIsUpdated() throws InvalidSyntaxException {
+ verify(this.userAdmin, times(1)).getRole(USER_ROLE_NAME_PREFIX + this.user.getUserName());
+ verify(this.userAdmin, times(1)).getRoles(null);
+ }
+
+ private void thenPasswordValidationIs(boolean expectedValue) {
+ assertEquals(expectedValue, this.isPasswordValid);
+
+ }
+
+ private void thenNoExceptionOccurred() {
+ String errorMessage = "Empty message";
+ if (Objects.nonNull(this.occurredException)) {
+ StringWriter sw = new StringWriter();
+ this.occurredException.printStackTrace(new PrintWriter(sw));
+
+ errorMessage = String.format("No exception expected, \"%s\" found. Caused by: %s",
+ this.occurredException.getClass().getName(), sw.toString());
+ }
+
+ assertNull(errorMessage, this.occurredException);
+ }
+
+ public static class PermissionRole implements Group {
+
+ private final String name;
+
+ public PermissionRole(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ return PERMISSION_ROLE_NAME_PREFIX + this.name;
+
+ }
+
+ @Override
+ public int getType() {
+ return Role.GROUP;
+ }
+
+ @Override
+ public Dictionary getProperties() {
+ return null;
+ }
+
+ @Override
+ public Dictionary getCredentials() {
+ return null;
+ }
+
+ @Override
+ public boolean hasCredential(String key, Object value) {
+ return false;
+ }
+
+ @Override
+ public boolean addMember(Role role) {
+ return false;
+ }
+
+ @Override
+ public boolean addRequiredMember(Role role) {
+ return false;
+ }
+
+ @Override
+ public boolean removeMember(Role role) {
+ return false;
+ }
+
+ @Override
+ public Role[] getMembers() {
+ return null;
+ }
+
+ @Override
+ public Role[] getRequiredMembers() {
+ return null;
+ }
+ }
+
+ public static class UserImpl implements User {
+
+ private final String name;
+ private final String password;
+ private final boolean needPasswordChange;
+
+ public UserImpl(String name, String password, boolean needPasswordChange) {
+ this.name = name;
+ this.password = password;
+ this.needPasswordChange = needPasswordChange;
+ }
+
+ @Override
+ public String getName() {
+ return USER_ROLE_NAME_PREFIX + this.name;
+ }
+
+ @Override
+ public int getType() {
+ return Role.USER;
+ }
+
+ @Override
+ public Dictionary getProperties() {
+ Dictionary properties = new Hashtable<>();
+ properties.put("kura.need.password.change", this.needPasswordChange);
+ return properties;
+ }
+
+ @Override
+ public boolean hasCredential(String key, Object value) {
+ return true;
+ }
+
+ @Override
+ public Dictionary getCredentials() {
+ Dictionary credentials = new Hashtable<>();
+ credentials.put("kura.password", this.password);
+
+ return credentials;
+ }
+ }
+
+ private static Map defaultValidatorProperties() {
+ Map properties = new HashMap<>();
+
+ properties.put("new.password.min.length", 8);
+ properties.put("new.password.require.digits", false);
+ properties.put("new.password.require.special.characters", false);
+ properties.put("new.password.require.both.cases", false);
+
+ return properties;
+ }
+
+}
diff --git a/kura/test/org.eclipse.kura.rest.packages.provider.test/src/main/java/org/eclipse/kura/rest/packages/provider/test/PackagesRestServiceTest.java b/kura/test/org.eclipse.kura.rest.packages.provider.test/src/main/java/org/eclipse/kura/rest/packages/provider/test/PackagesRestServiceTest.java
index d78199d2c76..b3fb272e261 100644
--- a/kura/test/org.eclipse.kura.rest.packages.provider.test/src/main/java/org/eclipse/kura/rest/packages/provider/test/PackagesRestServiceTest.java
+++ b/kura/test/org.eclipse.kura.rest.packages.provider.test/src/main/java/org/eclipse/kura/rest/packages/provider/test/PackagesRestServiceTest.java
@@ -145,7 +145,7 @@ public void installShouldWorkWithValidURLWhenARequestWasAlreadyIssued() {
@Test
public void uninstallShouldWorkWithValidPackageName() {
- whenRequestIsPerformed(new MethodSpec("DELETE"), "/testPackage");
+ whenRequestIsPerformed(new MethodSpec("DELETE", "DEL"), "/testPackage");
thenRequestSucceeds();
@@ -158,7 +158,7 @@ public void uninstallShouldWorkWithValidPackageName() {
public void uninstallShouldWorkWithValidPackageNameWhenARequestWasAlreadyIssued() {
givenAnUninstallationRequestWasAlreadyIssuedFor("testPackage");
- whenRequestIsPerformed(new MethodSpec("DELETE"), "/testPackage");
+ whenRequestIsPerformed(new MethodSpec("DELETE", "DEL"), "/testPackage");
thenRequestSucceeds();
diff --git a/kura/test/pom.xml b/kura/test/pom.xml
index 1730aa1ff0c..e939eaaddb0 100644
--- a/kura/test/pom.xml
+++ b/kura/test/pom.xml
@@ -931,6 +931,7 @@
org.eclipse.kura.rest.packages.provider.test
org.eclipse.kura.rest.position.provider.test
org.eclipse.kura.rest.security.provider.test
+ org.eclipse.kura.rest.identity.provider.test
org.eclipse.kura.rest.service.listing.provider.test
org.eclipse.kura.rest.system.provider.test
org.eclipse.kura.rest.wire.provider.test