From 17538d578b4ece5b00641b82294b196dc658e1f2 Mon Sep 17 00:00:00 2001 From: Salvatore Coppola <83589980+salvatore-coppola@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:09:33 +0100 Subject: [PATCH] feat(rest.identity.provider): Added new Identity rest APIs and Request Handler (#4878) * First commit. * Some fixes and tests. * Added condition on shouldInvokeSetUserConfigSuccessfully * Added new module in the list * fixed tests. * Added the possibility to inject the identityService (mainly for testing purposes) * Fix tests * Fixed data test * fixed module name * updated expected data * Fix tests * fixed method names * Changed API to meet `JaxRsRequest` handler behavior * Fixed deleteUser path and tests. * Fixed test * Fixed test * Changed getUserConfig return type * Removed Gson * fixed test manifest.mf * Trying fix tests * fixed method path * removed wrong body * Test alternate delete * Test alternate delete * Yet another fix * Changed getDefinedPermissions returned object * Fixed methods name. * fixed test * Fixed absurd bug in test. * fixed identity.provider pom.xml * deleteUser has again a path parameter * Fixed deleteUser * Revert "deleteUser has again a path parameter" This reverts commit fa214e87cb2669f139b350764280af2fed0b7e31. * removed comment * Fixed error and copyright * Fixed bug in UserAdminHelper#createUser * Added check on requestHandlerMethod * Added new getUser method * Fixed test * added missing @Produces annotation. * fixed test * Fix test * Fix test * YA Fix test * YA Fix test * added tearDown with @AfterClass * attempt to fix test * fix getUserConfigResponse * changed tests order * Added policy greedy * Refactored API * Test updated. * Fixed test * Added password validation before user creation. * Fixed some bugs. Added new `/password-requirements` method. * Added missing method to mock. * Fixed expected json * Refactored updateUser to reduce complexity. * fixed test * Keep original identity field values if they are not specified Signed-off-by: Nicola Timeus * Added tests and several minor fixes. * Added rest.identity permission configuration in the snapshot_0.xml files. * Added rest.identity.provider to emulator configuration * Applied cleanup * Fixed sonar lint issue Signed-off-by: Nicola Timeus * Minor naming changes Signed-off-by: Nicola Timeus --------- Signed-off-by: Nicola Timeus Co-authored-by: Nicola Timeus --- kura/distrib/config/kura.build.properties | 1 + kura/distrib/pom.xml | 7 + .../src/main/ant/build_equinox_distrib.xml | 17 +- .../resources/common/Kura_Emulator.launch | 1 + .../snapshots/snapshot_0.xml-generic-device | 2 +- .../common/snapshots/snapshot_0.xml-intelup2 | 2 +- .../snapshots/snapshot_0.xml-jetson-nano | 2 +- .../resources/docker-x86_64-nn/snapshot_0.xml | 2 +- .../intel-up2-ubuntu-20-nn/snapshot_0.xml | 2 +- .../intel-up2-ubuntu-20/snapshot_0.xml | 2 +- .../nvidia-jetson-nano-nn/snapshot_0.xml | 2 +- .../nvidia-jetson-nano/snapshot_0.xml | 2 +- .../raspberry-pi-arm64-nn/snapshot_0.xml | 2 +- .../raspberry-pi-arm64/snapshot_0.xml | 2 +- .../raspberry-pi-armhf-nn/snapshot_0.xml | 2 +- .../raspberry-pi-armhf/snapshot_0.xml | 2 +- .../src/main/resources/Kura_Emulator.launch | 1 + .../META-INF/MANIFEST.MF | 27 ++ .../OSGI-INF/IdentityRestService.xml | 60 +++ .../about.html | 36 ++ .../about_files/epl-v20.html | 301 ++++++++++++ .../build.properties | 19 + .../pom.xml | 35 ++ .../provider/IdentityRestService.java | 205 ++++++++ .../identity/provider/IdentityService.java | 252 ++++++++++ .../identity/provider/dto/PermissionDTO.java | 29 ++ .../identity/provider/dto/UserConfigDTO.java | 30 ++ .../rest/identity/provider/dto/UserDTO.java | 105 +++++ .../provider/dto/ValidatorOptionsDTO.java | 47 ++ .../validator/PasswordStrengthValidators.java | 117 +++++ .../validator/PredicateValidator.java | 36 ++ .../provider/validator/RegexValidator.java | 21 + .../provider/validator/Validator.java | 21 + .../provider/validator/ValidatorOptions.java | 70 +++ .../kura/util/useradmin/UserAdminHelper.java | 6 +- kura/pom.xml | 1 + .../testutil/requesthandler/Transport.java | 14 +- .../META-INF/MANIFEST.MF | 28 ++ .../build.properties | 16 + .../pom.xml | 71 +++ .../provider/test/IdentityEndpointsTest.java | 243 ++++++++++ .../getPasswordRequirementsResponse.json | 6 + .../main/resources/getUserConfigResponse.json | 13 + .../src/main/resources/getUserResponse.json | 6 + .../IdentityRestServiceDependenciesTest.java | 221 +++++++++ .../provider/test/IdentityServiceTest.java | 446 ++++++++++++++++++ .../test/PackagesRestServiceTest.java | 4 +- kura/test/pom.xml | 1 + 48 files changed, 2514 insertions(+), 24 deletions(-) create mode 100644 kura/org.eclipse.kura.rest.identity.provider/META-INF/MANIFEST.MF create mode 100644 kura/org.eclipse.kura.rest.identity.provider/OSGI-INF/IdentityRestService.xml create mode 100644 kura/org.eclipse.kura.rest.identity.provider/about.html create mode 100644 kura/org.eclipse.kura.rest.identity.provider/about_files/epl-v20.html create mode 100644 kura/org.eclipse.kura.rest.identity.provider/build.properties create mode 100644 kura/org.eclipse.kura.rest.identity.provider/pom.xml create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/IdentityRestService.java create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/IdentityService.java create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/PermissionDTO.java create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/UserConfigDTO.java create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/UserDTO.java create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/dto/ValidatorOptionsDTO.java create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/PasswordStrengthValidators.java create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/PredicateValidator.java create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/RegexValidator.java create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/Validator.java create mode 100644 kura/org.eclipse.kura.rest.identity.provider/src/main/java/org/eclipse/kura/internal/rest/identity/provider/validator/ValidatorOptions.java create mode 100644 kura/test/org.eclipse.kura.rest.identity.provider.test/META-INF/MANIFEST.MF create mode 100644 kura/test/org.eclipse.kura.rest.identity.provider.test/build.properties create mode 100644 kura/test/org.eclipse.kura.rest.identity.provider.test/pom.xml create mode 100644 kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityEndpointsTest.java create mode 100644 kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getPasswordRequirementsResponse.json create mode 100644 kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getUserConfigResponse.json create mode 100644 kura/test/org.eclipse.kura.rest.identity.provider.test/src/main/resources/getUserResponse.json create mode 100644 kura/test/org.eclipse.kura.rest.identity.provider.test/src/test/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityRestServiceDependenciesTest.java create mode 100644 kura/test/org.eclipse.kura.rest.identity.provider.test/src/test/java/org/eclipse/kura/internal/rest/identity/provider/test/IdentityServiceTest.java diff --git a/kura/distrib/config/kura.build.properties b/kura/distrib/config/kura.build.properties index e0b642dea84..b83dbb0ce11 100644 --- a/kura/distrib/config/kura.build.properties +++ b/kura/distrib/config/kura.build.properties @@ -117,6 +117,7 @@ org.eclipse.kura.rest.command.provider.version=1.0.0-SNAPSHOT org.eclipse.kura.rest.packages.provider.version=1.0.0-SNAPSHOT org.eclipse.kura.rest.position.provider.version=1.0.0-SNAPSHOT org.eclipse.kura.rest.security.provider.version=1.0.0-SNAPSHOT +org.eclipse.kura.rest.identity.provider.version=1.0.0-SNAPSHOT org.eclipse.kura.rest.service.listing.provider.version=1.0.0-SNAPSHOT org.eclipse.kura.rest.system.provider.version=1.0.0-SNAPSHOT org.eclipse.kura.request.handler.jaxrs.version=1.3.0-SNAPSHOT diff --git a/kura/distrib/pom.xml b/kura/distrib/pom.xml index cb1a0a61e16..cd1b576f6c3 100644 --- a/kura/distrib/pom.xml +++ b/kura/distrib/pom.xml @@ -623,6 +623,11 @@ org.eclipse.kura.rest.security.provider ${org.eclipse.kura.rest.security.provider.version} + + org.eclipse.kura + org.eclipse.kura.rest.identity.provider + ${org.eclipse.kura.rest.identity.provider.version} + org.eclipse.kura org.eclipse.kura.rest.service.listing.provider @@ -827,6 +832,7 @@ + @@ -2543,6 +2549,7 @@ + diff --git a/kura/distrib/src/main/ant/build_equinox_distrib.xml b/kura/distrib/src/main/ant/build_equinox_distrib.xml index 4a0980c14e5..67d4d3bd205 100644 --- a/kura/distrib/src/main/ant/build_equinox_distrib.xml +++ b/kura/distrib/src/main/ant/build_equinox_distrib.xml @@ -1320,9 +1320,11 @@ fi]]> - + value=", reference:file:${kura.install.dir}/${kura.symlink}/${plugins.folder}/org.eclipse.kura.rest.security.provider_${org.eclipse.kura.rest.security.provider.version}.jar@4:start" /> + + 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