From 947e116347df8f813322319d8224ebb698f167fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Fri, 29 Jan 2021 00:27:02 +0100 Subject: [PATCH 01/14] Reorganization of authentication and security API. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The main point is usage of CredentialExtractor API and delegation of all security related operations to AuthenticationManager and multiple implementations of AuthenticationProvider. Relates to opensmarthouse/opensmarthouse-core#16 Signed-off-by: Łukasz Dywicki --- bom/opensmarthouse-core/pom.xml | 6 + bom/opensmarthouse/pom.xml | 55 +++++++ .../NOTICE | 18 +++ .../pom.xml | 40 +++++ .../ApitokenAuthenticationProvider.java | 84 ++++++++++ .../ApitokenAuthenticationProviderTest.java | 90 +++++++++++ .../NOTICE | 18 +++ .../pom.xml | 27 ++++ .../apitoken}/UserApiTokenCredentials.java | 10 +- .../auth/AuthenticationManagerImpl.java | 9 +- .../org.opensmarthouse.core.auth.jaas/pom.xml | 8 + .../internal/JaasAuthenticationProvider.java | 21 ++- .../jaas/internal/ManagedUserLoginModule.java | 27 ++-- .../main/resources/OH-INF/config/config.xml | 19 +++ .../NOTICE | 18 +++ .../pom.xml | 41 +++++ .../provider/internal/ClaimSetPrincipal.java | 36 +++++ .../internal/JwtAuthenticationProvider.java | 144 ++++++++++++++++++ .../main/resources/OH-INF/config/config.xml | 23 +++ .../org.opensmarthouse.core.auth.jwt/NOTICE | 13 ++ .../org.opensmarthouse.core.auth.jwt/pom.xml | 23 +++ .../openhab/core/auth/jwt/JWTCredentials.java | 33 ++++ .../NOTICE | 13 ++ .../pom.xml | 40 +++++ .../core/internal}/ManagedUserProvider.java | 6 +- .../core/internal}/UserRegistryImpl.java | 56 ++----- .../core/internal}/UserRegistryImplTest.java | 29 +--- .../org.opensmarthouse.core.auth.local/NOTICE | 18 +++ .../pom.xml | 27 ++++ .../openhab/core/auth/local}/GenericUser.java | 6 +- .../openhab/core/auth/local}/ManagedUser.java | 3 +- .../core/auth/local}/PendingToken.java | 2 +- .../org/openhab/core/auth/local}/User.java | 3 +- .../core/auth/local}/UserApiToken.java | 2 +- .../core/auth/local}/UserProvider.java | 2 +- .../core/auth/local}/UserRegistry.java | 52 ++++++- .../openhab/core/auth/local}/UserSession.java | 2 +- .../NOTICE | 18 +++ .../pom.xml | 23 +++ .../org/openhab/core/auth/Authentication.java | 33 +++- .../core/auth/AuthenticationManager.java | 2 +- .../core/auth/AuthenticationProvider.java | 2 +- .../core/auth/AuthenticationResult.java | 46 ++++++ .../org/openhab/core/auth/Credentials.java | 2 + .../auth/UsernamePasswordCredentials.java | 20 ++- .../org.opensmarthouse.core.io.auth/NOTICE | 13 ++ .../org.opensmarthouse.core.io.auth/pom.xml | 23 +++ .../core/io}/auth/CredentialsExtractor.java | 2 +- .../pom.xml | 4 + .../UserConsoleCommandExtension.java | 8 +- .../NOTICE | 18 +++ .../pom.xml | 31 ++++ .../ApitokenCredentialsExtractor.java | 38 +++++ .../pom.xml | 9 +- .../internal/BasicCredentialsExtractor.java | 21 +-- .../NOTICE | 18 +++ .../pom.xml | 31 ++++ .../jwt/internal/JwtCredentialsExtractor.java | 47 ++++++ .../pom.xml | 12 ++ .../internal/AbstractAuthPageServlet.java | 22 +-- .../auth/internal/AuthenticationHandler.java | 20 +-- .../auth/internal/AuthorizePageServlet.java | 14 +- .../internal/ChangePasswordPageServlet.java | 11 +- .../internal/CreateAPITokenPageServlet.java | 11 +- .../auth/internal/ServletRequestDelegate.java | 37 +++++ .../NOTICE | 13 ++ .../pom.xml | 17 +++ .../io/http/facade/HttpRequestDelegate.java | 29 ++++ .../pom.xml | 48 ++++++ .../rest/auth/local}/internal/JwtHelper.java | 4 +- .../LocalJwtAuthenticationProvider.java | 54 +++++++ .../internal/TokenEndpointException.java | 2 +- .../auth/local}/internal/TokenResource.java | 16 +- .../local}/internal/TokenResponseDTO.java | 4 +- .../internal/TokenResponseErrorDTO.java | 2 +- .../auth/local}/internal/UserApiTokenDTO.java | 2 +- .../io/rest/auth/local}/internal/UserDTO.java | 4 +- .../local}/internal/UserSecurityContext.java | 33 ++-- .../auth/local}/internal/UserSessionDTO.java | 2 +- .../pom.xml | 11 ++ .../AuthenticationSecurityContext.java | 4 +- .../internal/ApplicationSecurityContext.java | 64 ++++++++ .../io/rest/auth/internal/AuthFilter.java | 136 ++++++----------- .../auth/internal/JaxRsRequestDelegate.java | 37 +++++ .../auth/internal/JwtSecurityContext.java | 9 +- .../pom.xml | 2 - .../org.opensmarthouse.core.karaf.jaas/NOTICE | 18 +++ .../pom.xml | 36 +++++ .../internal}/ManagedUserBackingEngine.java | 8 +- .../ManagedUserBackingEngineFactory.java | 4 +- .../jaas/internal}/ManagedUserRealm.java | 6 +- bundles/org.opensmarthouse.core.karaf/pom.xml | 6 - bundles/pom.xml | 13 ++ .../src/main/feature/feature.xml | 82 ++++++++++ 94 files changed, 1917 insertions(+), 309 deletions(-) create mode 100755 bundles/org.opensmarthouse.core.auth.apitoken.provider/NOTICE create mode 100755 bundles/org.opensmarthouse.core.auth.apitoken.provider/pom.xml create mode 100644 bundles/org.opensmarthouse.core.auth.apitoken.provider/src/main/java/org/openhab/core/auth/apitoken/provider/internal/ApitokenAuthenticationProvider.java create mode 100644 bundles/org.opensmarthouse.core.auth.apitoken.provider/src/test/java/org/openhab/core/auth/apitoken/provider/internal/ApitokenAuthenticationProviderTest.java create mode 100755 bundles/org.opensmarthouse.core.auth.apitoken/NOTICE create mode 100755 bundles/org.opensmarthouse.core.auth.apitoken/pom.xml rename bundles/{org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth => org.opensmarthouse.core.auth.apitoken/src/main/java/org/openhab/core/auth/apitoken}/UserApiTokenCredentials.java (85%) create mode 100755 bundles/org.opensmarthouse.core.auth.jaas/src/main/resources/OH-INF/config/config.xml create mode 100755 bundles/org.opensmarthouse.core.auth.jwt.provider/NOTICE create mode 100755 bundles/org.opensmarthouse.core.auth.jwt.provider/pom.xml create mode 100644 bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/java/org/openhab/core/auth/jwt/provider/internal/ClaimSetPrincipal.java create mode 100755 bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/java/org/openhab/core/auth/jwt/provider/internal/JwtAuthenticationProvider.java create mode 100755 bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/resources/OH-INF/config/config.xml create mode 100755 bundles/org.opensmarthouse.core.auth.jwt/NOTICE create mode 100755 bundles/org.opensmarthouse.core.auth.jwt/pom.xml create mode 100644 bundles/org.opensmarthouse.core.auth.jwt/src/main/java/org/openhab/core/auth/jwt/JWTCredentials.java create mode 100755 bundles/org.opensmarthouse.core.auth.local.provider/NOTICE create mode 100755 bundles/org.opensmarthouse.core.auth.local.provider/pom.xml rename bundles/{org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth => org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal}/ManagedUserProvider.java (90%) rename bundles/{org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth => org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal}/UserRegistryImpl.java (77%) rename bundles/{org.opensmarthouse.core.auth.core/src/test/java/org/openhab/core/internal/auth => org.opensmarthouse.core.auth.local.provider/src/test/java/org/openhab/core/auth/local/core/internal}/UserRegistryImplTest.java (73%) create mode 100755 bundles/org.opensmarthouse.core.auth.local/NOTICE create mode 100755 bundles/org.opensmarthouse.core.auth.local/pom.xml rename bundles/{org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth => org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local}/GenericUser.java (92%) rename bundles/{org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth => org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local}/ManagedUser.java (98%) rename bundles/{org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth => org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local}/PendingToken.java (98%) rename bundles/{org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth => org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local}/User.java (92%) rename bundles/{org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth => org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local}/UserApiToken.java (98%) rename bundles/{org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth => org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local}/UserProvider.java (94%) rename bundles/{org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth => org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local}/UserRegistry.java (58%) rename bundles/{org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth => org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local}/UserSession.java (99%) create mode 100755 bundles/org.opensmarthouse.core.auth.password/NOTICE create mode 100755 bundles/org.opensmarthouse.core.auth.password/pom.xml create mode 100755 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationResult.java create mode 100755 bundles/org.opensmarthouse.core.io.auth/NOTICE create mode 100755 bundles/org.opensmarthouse.core.io.auth/pom.xml rename bundles/{org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http => org.opensmarthouse.core.io.auth/src/main/java/org/openhab/core/io}/auth/CredentialsExtractor.java (95%) create mode 100755 bundles/org.opensmarthouse.core.io.http.auth.apitoken/NOTICE create mode 100755 bundles/org.opensmarthouse.core.io.http.auth.apitoken/pom.xml create mode 100644 bundles/org.opensmarthouse.core.io.http.auth.apitoken/src/main/java/org/openhab/core/io/http/auth/apitoken/internal/ApitokenCredentialsExtractor.java create mode 100755 bundles/org.opensmarthouse.core.io.http.auth.jwt/NOTICE create mode 100755 bundles/org.opensmarthouse.core.io.http.auth.jwt/pom.xml create mode 100644 bundles/org.opensmarthouse.core.io.http.auth.jwt/src/main/java/org/openhab/core/io/http/auth/jwt/internal/JwtCredentialsExtractor.java create mode 100644 bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ServletRequestDelegate.java create mode 100755 bundles/org.opensmarthouse.core.io.http.facade/NOTICE create mode 100755 bundles/org.opensmarthouse.core.io.http.facade/pom.xml create mode 100644 bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/HttpRequestDelegate.java create mode 100755 bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml rename bundles/{org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth => org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local}/internal/JwtHelper.java (98%) create mode 100644 bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/LocalJwtAuthenticationProvider.java rename bundles/{org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth => org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local}/internal/TokenEndpointException.java (97%) rename bundles/{org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth => org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local}/internal/TokenResource.java (97%) rename bundles/{org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth => org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local}/internal/TokenResponseDTO.java (94%) rename bundles/{org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth => org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local}/internal/TokenResponseErrorDTO.java (94%) rename bundles/{org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth => org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local}/internal/UserApiTokenDTO.java (93%) rename bundles/{org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth => org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local}/internal/UserDTO.java (88%) rename bundles/{org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth => org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local}/internal/UserSecurityContext.java (59%) rename bundles/{org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth => org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local}/internal/UserSessionDTO.java (94%) rename bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/{internal => }/AuthenticationSecurityContext.java (89%) create mode 100755 bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/ApplicationSecurityContext.java create mode 100644 bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JaxRsRequestDelegate.java create mode 100755 bundles/org.opensmarthouse.core.karaf.jaas/NOTICE create mode 100755 bundles/org.opensmarthouse.core.karaf.jaas/pom.xml rename bundles/{org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas => org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal}/ManagedUserBackingEngine.java (95%) rename bundles/{org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas => org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal}/ManagedUserBackingEngineFactory.java (93%) rename bundles/{org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas => org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal}/ManagedUserRealm.java (91%) diff --git a/bom/opensmarthouse-core/pom.xml b/bom/opensmarthouse-core/pom.xml index 9244035a3..55dfa785a 100755 --- a/bom/opensmarthouse-core/pom.xml +++ b/bom/opensmarthouse-core/pom.xml @@ -117,6 +117,12 @@ ${project.version} compile + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth + ${project.version} + compile + org.opensmarthouse.core.bundles org.opensmarthouse.core.auth.jaas diff --git a/bom/opensmarthouse/pom.xml b/bom/opensmarthouse/pom.xml index 8d270d639..f0c3f1b7a 100755 --- a/bom/opensmarthouse/pom.xml +++ b/bom/opensmarthouse/pom.xml @@ -162,6 +162,11 @@ org.opensmarthouse.core.id ${project.version} + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.auth + ${project.version} + org.opensmarthouse.core.bundles org.opensmarthouse.core.persistence @@ -217,11 +222,41 @@ org.opensmarthouse.core.auth ${project.version} + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.apitoken + ${project.version} + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.core + ${project.version} + org.opensmarthouse.core.bundles org.opensmarthouse.core.auth.jaas ${project.version} + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.jwt + ${project.version} + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.jwt.provider + ${project.version} + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.local + ${project.version} + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.local.provider + ${project.version} + org.opensmarthouse.core.bundles org.opensmarthouse.core.auth.oauth2client @@ -287,11 +322,26 @@ org.opensmarthouse.core.io.http.auth ${project.version} + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.http.auth.apitoken + ${project.version} + org.opensmarthouse.core.bundles org.opensmarthouse.core.io.http.auth.basic ${project.version} + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.http.auth.jwt + ${project.version} + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.http.facade + ${project.version} + org.opensmarthouse.core.bundles org.opensmarthouse.core.io.monitor @@ -307,6 +357,11 @@ org.opensmarthouse.core.io.rest.auth ${project.version} + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.rest.auth.local + ${project.version} + org.opensmarthouse.core.bundles org.opensmarthouse.core.io.rest.binding diff --git a/bundles/org.opensmarthouse.core.auth.apitoken.provider/NOTICE b/bundles/org.opensmarthouse.core.auth.apitoken.provider/NOTICE new file mode 100755 index 000000000..3e53bd427 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.apitoken.provider/NOTICE @@ -0,0 +1,18 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +Elements of this bundle are copyright to the following -: + +* The Eclipse Foundation, having come from the Eclipse SmartHome project +* The openHAB project + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.auth.apitoken.provider/pom.xml b/bundles/org.opensmarthouse.core.auth.apitoken.provider/pom.xml new file mode 100755 index 000000000..52538c120 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.apitoken.provider/pom.xml @@ -0,0 +1,40 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.auth.apitoken.provider + + OpenSmartHouse Core | Bundles | Apitoken Authentication Provider + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.local + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.apitoken + + + org.osgi + osgi.cmpn + + + + org.junit.jupiter + junit-jupiter-api + + + org.mockito + mockito-junit-jupiter + + + + diff --git a/bundles/org.opensmarthouse.core.auth.apitoken.provider/src/main/java/org/openhab/core/auth/apitoken/provider/internal/ApitokenAuthenticationProvider.java b/bundles/org.opensmarthouse.core.auth.apitoken.provider/src/main/java/org/openhab/core/auth/apitoken/provider/internal/ApitokenAuthenticationProvider.java new file mode 100644 index 000000000..fdd5ed22f --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.apitoken.provider/src/main/java/org/openhab/core/auth/apitoken/provider/internal/ApitokenAuthenticationProvider.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth.apitoken.provider.internal; + +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.auth.AuthenticationProvider; +import org.openhab.core.auth.AuthenticationResult; +import org.openhab.core.auth.Credentials; +import org.openhab.core.auth.local.GenericUser; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.User; +import org.openhab.core.auth.local.UserApiToken; +import org.openhab.core.auth.local.UserRegistry; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.openhab.core.auth.apitoken.UserApiTokenCredentials; + +/** + * Authentication provider which is able to correlate received authentication token. + * + * @author Yannick Schaus - initial contribution + * @author Łukasz Dywicki - Extracted from io.rest.auth module. + */ +@Component +public class ApitokenAuthenticationProvider implements AuthenticationProvider { + + private final UserRegistry userRegistry; + + @Activate + public ApitokenAuthenticationProvider(@Reference UserRegistry userRegistry) { + this.userRegistry = userRegistry; + } + + @Override + public AuthenticationResult authenticate(Credentials credentials) throws AuthenticationException { + if (!(credentials instanceof UserApiTokenCredentials)) { + throw new IllegalArgumentException("Invalid credential type"); + } + + UserApiTokenCredentials apiTokenCreds = (UserApiTokenCredentials) credentials; + String[] apiTokenParts = apiTokenCreds.getApiToken().split("\\."); + if (apiTokenParts.length != 3 || !UserRegistry.APITOKEN_PREFIX.equals(apiTokenParts[0])) { + throw new AuthenticationException("Invalid API token format"); + } + for (User user : userRegistry.getAll()) { + ManagedUser managedUser = (ManagedUser) user; + for (UserApiToken userApiToken : managedUser.getApiTokens()) { + // only check if the name in the token matches + if (!userApiToken.getName().equals(apiTokenParts[1])) { + continue; + } + String[] existingTokenHashAndSalt = userApiToken.getApiToken().split(":"); + String incomingTokenHash = UserRegistry.hash(apiTokenCreds.getApiToken(), existingTokenHashAndSalt[1], + UserRegistry.APITOKEN_ITERATIONS).get(); + + if (incomingTokenHash.equals(existingTokenHashAndSalt[0])) { + return new AuthenticationResult(new GenericUser(managedUser.getName()), credentials.getScheme(), + new Authentication(managedUser.getName(), managedUser.getRoles().stream().toArray(String[]::new), userApiToken.getScope()) + ); + } + } + } + + throw new AuthenticationException("Unknown API token"); + } + + @Override + public boolean supports(Class type) { + return UserApiTokenCredentials.class.isAssignableFrom(type); + } + +} diff --git a/bundles/org.opensmarthouse.core.auth.apitoken.provider/src/test/java/org/openhab/core/auth/apitoken/provider/internal/ApitokenAuthenticationProviderTest.java b/bundles/org.opensmarthouse.core.auth.apitoken.provider/src/test/java/org/openhab/core/auth/apitoken/provider/internal/ApitokenAuthenticationProviderTest.java new file mode 100644 index 000000000..26d756b55 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.apitoken.provider/src/test/java/org/openhab/core/auth/apitoken/provider/internal/ApitokenAuthenticationProviderTest.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth.apitoken.provider.internal; + +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.UserApiToken; +import org.openhab.core.auth.local.UserRegistry; +import org.openhab.core.auth.apitoken.UserApiTokenCredentials; + +@ExtendWith(MockitoExtension.class) +class ApitokenAuthenticationProviderTest { + + public static final String TOKEN_X = "X"; + public static final String TOKEN_Y = "Y"; + public static final String TOKEN_Z = "Z"; + private static final String TOKEN_SALT = "asdf"; + + + @Mock + private UserRegistry registry; + + private ApitokenAuthenticationProvider provider; + + @BeforeEach + public void setup() throws Exception { + provider = new ApitokenAuthenticationProvider(registry); + + List users = Arrays.asList( + createUser("foo", "x", TOKEN_SALT, TOKEN_X), + createUser("bar", "y", TOKEN_SALT, TOKEN_Y), + createUser("baz", "z", TOKEN_SALT, TOKEN_Z) + ); + when(registry.getAll()).thenAnswer((inv) -> users); + } + + @Test + public void testApiTokens() throws Exception { + String token1 = UserRegistry.APITOKEN_PREFIX + ".x." + TOKEN_X; + String token2 = UserRegistry.APITOKEN_PREFIX + ".y." + TOKEN_Y; + String token3 = UserRegistry.APITOKEN_PREFIX + ".z." + TOKEN_Z; + + provider.authenticate(new UserApiTokenCredentials(token1)); + provider.authenticate(new UserApiTokenCredentials(token2)); + provider.authenticate(new UserApiTokenCredentials(token3)); + } + + @Test + public void testInvalidTokens() throws Exception { + String token1 = UserRegistry.APITOKEN_PREFIX + ".x.x"; + String token2 = UserRegistry.APITOKEN_PREFIX + ".Y.y"; + + Assertions.assertThrows(AuthenticationException.class, () -> provider.authenticate(new UserApiTokenCredentials(token1))); + Assertions.assertThrows(AuthenticationException.class, () -> provider.authenticate(new UserApiTokenCredentials(token2))); + } + + private ManagedUser createUser(String name, String tokenId, String tokenSalt, String tokenValue) { + ManagedUser user = mock(ManagedUser.class, name); + + String token = UserRegistry.APITOKEN_PREFIX + "." + tokenId + "." + tokenValue; + String tokenHash = UserRegistry.hash(token, tokenSalt, UserRegistry.APITOKEN_ITERATIONS).get(); + System.out.println(token); + + UserApiToken userApiToken = new UserApiToken(tokenId, tokenHash + ":" + tokenSalt, ""); + when(user.getApiTokens()).thenReturn(Collections.singletonList(userApiToken)); + return user; + } + +} \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.auth.apitoken/NOTICE b/bundles/org.opensmarthouse.core.auth.apitoken/NOTICE new file mode 100755 index 000000000..3e53bd427 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.apitoken/NOTICE @@ -0,0 +1,18 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +Elements of this bundle are copyright to the following -: + +* The Eclipse Foundation, having come from the Eclipse SmartHome project +* The openHAB project + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.auth.apitoken/pom.xml b/bundles/org.opensmarthouse.core.auth.apitoken/pom.xml new file mode 100755 index 000000000..942cd4684 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.apitoken/pom.xml @@ -0,0 +1,27 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.auth.apitoken + + OpenSmartHouse Core | Bundles | Apitoken Authentication + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.core + + + org.osgi + osgi.cmpn + + + + diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserApiTokenCredentials.java b/bundles/org.opensmarthouse.core.auth.apitoken/src/main/java/org/openhab/core/auth/apitoken/UserApiTokenCredentials.java similarity index 85% rename from bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserApiTokenCredentials.java rename to bundles/org.opensmarthouse.core.auth.apitoken/src/main/java/org/openhab/core/auth/apitoken/UserApiTokenCredentials.java index 7c893e67e..a548aced4 100644 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserApiTokenCredentials.java +++ b/bundles/org.opensmarthouse.core.auth.apitoken/src/main/java/org/openhab/core/auth/apitoken/UserApiTokenCredentials.java @@ -10,7 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.auth; +package org.openhab.core.auth.apitoken; + +import org.openhab.core.auth.Credentials; /** * Credentials which represent a user API token. @@ -38,4 +40,10 @@ public UserApiTokenCredentials(String userApiToken) { public String getApiToken() { return userApiToken; } + + @Override + public String getScheme() { + return "apitoken"; + } + } diff --git a/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/AuthenticationManagerImpl.java b/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/AuthenticationManagerImpl.java index a8845f322..7a4d838d8 100755 --- a/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/AuthenticationManagerImpl.java +++ b/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/AuthenticationManagerImpl.java @@ -19,6 +19,7 @@ import org.openhab.core.auth.AuthenticationException; import org.openhab.core.auth.AuthenticationManager; import org.openhab.core.auth.AuthenticationProvider; +import org.openhab.core.auth.AuthenticationResult; import org.openhab.core.auth.Credentials; import org.openhab.core.auth.UnsupportedCredentialsException; import org.osgi.service.component.annotations.Component; @@ -41,15 +42,15 @@ public class AuthenticationManagerImpl implements AuthenticationManager { private final List providers = new CopyOnWriteArrayList<>(); @Override - public Authentication authenticate(Credentials credentials) throws AuthenticationException { + public AuthenticationResult authenticate(Credentials credentials) throws AuthenticationException { boolean unmatched = true; for (AuthenticationProvider provider : providers) { if (provider.supports(credentials.getClass())) { unmatched = false; try { - Authentication authentication = provider.authenticate(credentials); - if (authentication != null) { - return authentication; + AuthenticationResult authenticationResult = provider.authenticate(credentials); + if (authenticationResult != null) { + return authenticationResult; } } catch (AuthenticationException e) { logger.info("Failed to authenticate credentials {} with provider {}", credentials.getClass(), diff --git a/bundles/org.opensmarthouse.core.auth.jaas/pom.xml b/bundles/org.opensmarthouse.core.auth.jaas/pom.xml index b3caf9f39..2a5b4aa0e 100755 --- a/bundles/org.opensmarthouse.core.auth.jaas/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.jaas/pom.xml @@ -18,10 +18,18 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.auth + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.local + org.osgi osgi.cmpn + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.config + diff --git a/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java b/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java index 9799cd8c8..d3e9844f7 100755 --- a/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java +++ b/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java @@ -32,11 +32,15 @@ import org.openhab.core.auth.Authentication; import org.openhab.core.auth.AuthenticationException; import org.openhab.core.auth.AuthenticationProvider; +import org.openhab.core.auth.AuthenticationResult; import org.openhab.core.auth.Credentials; -import org.openhab.core.auth.GenericUser; +import org.openhab.core.auth.local.GenericUser; import org.openhab.core.auth.UsernamePasswordCredentials; +import org.openhab.core.config.core.ConfigurableService; +import org.osgi.framework.Constants; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Modified; @@ -50,14 +54,17 @@ * @author Yannick Schaus - provides a configuration with the ManagedUserLoginModule as a sufficient login module */ @NonNullByDefault -@Component(configurationPid = "org.openhab.jaas") +@Component(configurationPid = "org.openhab.jaas", property = Constants.SERVICE_PID + "=org.openhab.jaas", configurationPolicy = ConfigurationPolicy.REQUIRE) +@ConfigurableService(category = "auth", label = "JAAS Authentication", description_uri = JaasAuthenticationProvider.CONFIG_URI) public class JaasAuthenticationProvider implements AuthenticationProvider { + private static final String DEFAULT_REALM = "openhab"; + static final String CONFIG_URI = "auth:jaas"; private @Nullable String realmName; @Override - public Authentication authenticate(final Credentials credentials) throws AuthenticationException { + public AuthenticationResult authenticate(final Credentials credentials) throws AuthenticationException { if (realmName == null) { // configuration is not yet ready or set realmName = DEFAULT_REALM; } @@ -93,7 +100,7 @@ public void handle(@NonNullByDefault({}) Callback[] callbacks) }, new ManagedUserLoginConfiguration()); loginContext.login(); - return getAuthentication(name, loginContext.getSubject()); + return getAuthentication(name, userCredentials.getScheme(), loginContext.getSubject()); } catch (LoginException e) { throw new AuthenticationException(e.getMessage()); } finally { @@ -101,8 +108,10 @@ public void handle(@NonNullByDefault({}) Callback[] callbacks) } } - private Authentication getAuthentication(String name, Subject subject) { - return new Authentication(name, getRoles(subject.getPrincipals())); + private AuthenticationResult getAuthentication(String name, String scheme, Subject subject) { + return new AuthenticationResult(subject.getPrincipals().iterator().next(), scheme, + new Authentication(name, getRoles(subject.getPrincipals())) + ); } private String[] getRoles(Set principals) { diff --git a/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/ManagedUserLoginModule.java b/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/ManagedUserLoginModule.java index 31ff32224..1c4e7def7 100755 --- a/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/ManagedUserLoginModule.java +++ b/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/ManagedUserLoginModule.java @@ -16,12 +16,13 @@ import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import org.openhab.core.auth.AuthenticationException; import org.openhab.core.auth.Credentials; -import org.openhab.core.auth.UserRegistry; +import org.openhab.core.auth.local.UserRegistry; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; @@ -37,8 +38,6 @@ public class ManagedUserLoginModule implements LoginModule { private final Logger logger = LoggerFactory.getLogger(ManagedUserLoginModule.class); - private UserRegistry userRegistry; - private Subject subject; @Override @@ -49,23 +48,25 @@ public void initialize(Subject subject, CallbackHandler callbackHandler, Map serviceReference = null; try { // try to get the UserRegistry instance - BundleContext bundleContext = FrameworkUtil.getBundle(UserRegistry.class).getBundleContext(); - ServiceReference serviceReference = bundleContext.getServiceReference(UserRegistry.class); - - userRegistry = bundleContext.getService(serviceReference); - } catch (Exception e) { - logger.error("Cannot initialize the ManagedLoginModule", e); - throw new LoginException("Authorization failed"); - } + serviceReference = bundleContext.getServiceReference(UserRegistry.class); - try { + UserRegistry userRegistry = bundleContext.getService(serviceReference); Credentials credentials = (Credentials) this.subject.getPrivateCredentials().iterator().next(); userRegistry.authenticate(credentials); return true; } catch (AuthenticationException e) { - throw new LoginException(e.getMessage()); + throw new FailedLoginException(e.getMessage()); + } catch (Exception e) { + logger.error("Cannot initialize the ManagedLoginModule", e); + throw new LoginException("Authorization failed"); + } finally { + if (serviceReference != null) { + bundleContext.ungetService(serviceReference); + } } } diff --git a/bundles/org.opensmarthouse.core.auth.jaas/src/main/resources/OH-INF/config/config.xml b/bundles/org.opensmarthouse.core.auth.jaas/src/main/resources/OH-INF/config/config.xml new file mode 100755 index 000000000..e58bba83b --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.jaas/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,19 @@ + + + + + + + + Name of underlying JAAS realm used to verify user credentials. + This parameter points to actual configuration which is used to authenticate user. + + openhab + + + + diff --git a/bundles/org.opensmarthouse.core.auth.jwt.provider/NOTICE b/bundles/org.opensmarthouse.core.auth.jwt.provider/NOTICE new file mode 100755 index 000000000..3e53bd427 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.jwt.provider/NOTICE @@ -0,0 +1,18 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +Elements of this bundle are copyright to the following -: + +* The Eclipse Foundation, having come from the Eclipse SmartHome project +* The openHAB project + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.auth.jwt.provider/pom.xml b/bundles/org.opensmarthouse.core.auth.jwt.provider/pom.xml new file mode 100755 index 000000000..df4128ac3 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.jwt.provider/pom.xml @@ -0,0 +1,41 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.auth.jwt.provider + + OpenSmartHouse Core | Bundles | JWT Authentication Provider + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.jwt + + + org.bitbucket.b_c + jose4j + + + org.osgi + org.osgi.service.component.annotations + 1.4.0 + provided + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.config + + + + diff --git a/bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/java/org/openhab/core/auth/jwt/provider/internal/ClaimSetPrincipal.java b/bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/java/org/openhab/core/auth/jwt/provider/internal/ClaimSetPrincipal.java new file mode 100644 index 000000000..99680ec76 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/java/org/openhab/core/auth/jwt/provider/internal/ClaimSetPrincipal.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth.jwt.provider.internal; + +import java.security.Principal; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.MalformedClaimException; + +public class ClaimSetPrincipal implements Principal { + + private final String name; + + public ClaimSetPrincipal(JwtClaims claimsSet) throws MalformedClaimException { + this(claimsSet.getStringClaimValue("preferred_username")); + } + + public ClaimSetPrincipal(String username) { + this.name = username; + } + + @Override + public String getName() { + return this.name; + } + +} diff --git a/bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/java/org/openhab/core/auth/jwt/provider/internal/JwtAuthenticationProvider.java b/bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/java/org/openhab/core/auth/jwt/provider/internal/JwtAuthenticationProvider.java new file mode 100755 index 000000000..fc63e459c --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/java/org/openhab/core/auth/jwt/provider/internal/JwtAuthenticationProvider.java @@ -0,0 +1,144 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth.jwt.provider.internal; + +import java.util.List; +import java.util.Map; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.jose4j.jwa.AlgorithmConstraints.ConstraintType; +import org.jose4j.jwk.HttpsJwks; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.MalformedClaimException; +import org.jose4j.jwt.consumer.InvalidJwtException; +import org.jose4j.jwt.consumer.JwtConsumer; +import org.jose4j.jwt.consumer.JwtConsumerBuilder; +import org.jose4j.jwt.consumer.JwtContext; +import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver; +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.auth.AuthenticationProvider; +import org.openhab.core.auth.AuthenticationResult; +import org.openhab.core.auth.Credentials; +import org.openhab.core.auth.jwt.JWTCredentials; +import org.openhab.core.config.core.ConfigurableService; +import org.osgi.framework.Constants; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; + +/** + * Implementation of authentication provider which is backed by JWT realm. + * + * Real authentication logic is embedded in login modules implemented by 3rd party, this code is just for bridging it to + * smarthome platform. + * + * @author Łukasz Dywicki - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "org.openhab.jwt", property = Constants.SERVICE_PID + "=org.openhab.jwt", configurationPolicy = ConfigurationPolicy.REQUIRE) +@ConfigurableService(category = "auth", label = "JWT Authentication", description_uri = JwtAuthenticationProvider.CONFIG_URI) +public class JwtAuthenticationProvider implements AuthenticationProvider { + + static final String CONFIG_URI = "auth:jwt"; + private @Nullable JwtConsumer jwtProcessor; + + @Override + public AuthenticationResult authenticate(final Credentials credentials) throws AuthenticationException { + if (jwtProcessor == null) { // configuration is not yet ready or set + throw new AuthenticationException("Authenticator is not set up"); + } + + if (!(credentials instanceof JWTCredentials)) { + throw new AuthenticationException("Unsupported credentials passed to provider."); + } + + JWTCredentials userCredentials = (JWTCredentials) credentials; + final String token = userCredentials.getToken(); + + try { + JwtContext jwtContext = jwtProcessor.process(token); + JwtClaims claims = jwtContext.getJwtClaims(); + return new AuthenticationResult(new ClaimSetPrincipal(claims), credentials.getScheme(), + new Authentication(claims.getSubject(), extractRoles(claims)) + ); + } catch (InvalidJwtException e) { + throw new AuthenticationException("Error while processing token", e); + } catch (MalformedClaimException e) { + throw new AuthenticationException("Authentication failed", e); + } + } + + private String[] extractRoles(JwtClaims claims) { + Object roles = claims.getClaimValue("roles"); + if (roles instanceof List) { + List rolesClaim = (List) roles; + return rolesClaim.toArray(new String[rolesClaim.size()]); + } + return new String[0]; + } + + @Activate + protected void activate(Map properties) throws ConfigurationException { + modified(properties); + } + + @Deactivate + protected void deactivate(Map properties) { + } + + @Modified + protected void modified(Map properties) throws ConfigurationException { + if (properties == null) { + jwtProcessor = null; + return; + } + + Object jwkSetUrlVal = properties.get("jwkSetUrl"); + Object signatureAlgorithmVal = properties.get("signatureAlgorithm"); + + if (signatureAlgorithmVal != null) { + String jwkUrl = null; + String signatureAlgorithm = null; + + if (jwkSetUrlVal instanceof String && !((String) jwkSetUrlVal).trim().isEmpty()) { + jwkUrl = (String) jwkSetUrlVal; + } else { + throw new ConfigurationException("jwkSetUrl", "Invalid or empty value for property"); + } + if (signatureAlgorithmVal instanceof String && !((String) signatureAlgorithmVal).trim().isEmpty()) { + signatureAlgorithm = (String) signatureAlgorithmVal; + } else { + throw new ConfigurationException("signatureAlgorithmVal", "Invalid or empty value for property"); + } + + HttpsJwks httpsJkws = new HttpsJwks(jwkUrl); + HttpsJwksVerificationKeyResolver keyResolver = new HttpsJwksVerificationKeyResolver(httpsJkws); + jwtProcessor = new JwtConsumerBuilder().setRequireExpirationTime().setAllowedClockSkewInSeconds(30) + .setJwsAlgorithmConstraints(ConstraintType.WHITELIST, signatureAlgorithm) + .setVerificationKeyResolver(keyResolver) + .build(); + } else { + // value could be unset, we should reset its value + jwtProcessor = null; + } + } + + @Override + public boolean supports(Class type) { + return JWTCredentials.class.isAssignableFrom(type); + } +} diff --git a/bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/resources/OH-INF/config/config.xml b/bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/resources/OH-INF/config/config.xml new file mode 100755 index 000000000..8a3955d8c --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.jwt.provider/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,23 @@ + + + + + + + + A location of Json Web Key set which should be used to validate incoming JWT tokens. + + + + + + Algorithm used to validate signature. + + + + + diff --git a/bundles/org.opensmarthouse.core.auth.jwt/NOTICE b/bundles/org.opensmarthouse.core.auth.jwt/NOTICE new file mode 100755 index 000000000..bed08ed3f --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.jwt/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.auth.jwt/pom.xml b/bundles/org.opensmarthouse.core.auth.jwt/pom.xml new file mode 100755 index 000000000..4c71c29dd --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.jwt/pom.xml @@ -0,0 +1,23 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.auth.jwt + + OpenSmartHouse Core | Bundles | JWT Credentials + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth + + + + diff --git a/bundles/org.opensmarthouse.core.auth.jwt/src/main/java/org/openhab/core/auth/jwt/JWTCredentials.java b/bundles/org.opensmarthouse.core.auth.jwt/src/main/java/org/openhab/core/auth/jwt/JWTCredentials.java new file mode 100644 index 000000000..8bd010311 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.jwt/src/main/java/org/openhab/core/auth/jwt/JWTCredentials.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth.jwt; + +import org.openhab.core.auth.Credentials; + +public class JWTCredentials implements Credentials { + + private final String token; + + public JWTCredentials(String token) { + this.token = token; + } + + public String getToken() { + return token; + } + + public String getScheme() { + return "Bearer"; + } + +} diff --git a/bundles/org.opensmarthouse.core.auth.local.provider/NOTICE b/bundles/org.opensmarthouse.core.auth.local.provider/NOTICE new file mode 100755 index 000000000..bed08ed3f --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.local.provider/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml b/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml new file mode 100755 index 000000000..8f8e131d9 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml @@ -0,0 +1,40 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.auth.local.provider + + OpenSmartHouse Core | Bundles | Local Authentication Provider + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.local + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.apitoken + + + org.osgi + osgi.cmpn + + + + org.mockito + mockito-junit-jupiter + + + org.junit.jupiter + junit-jupiter-api + + + + diff --git a/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/ManagedUserProvider.java b/bundles/org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal/ManagedUserProvider.java similarity index 90% rename from bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/ManagedUserProvider.java rename to bundles/org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal/ManagedUserProvider.java index f456fafdf..0e2b3b1c6 100755 --- a/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/ManagedUserProvider.java +++ b/bundles/org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal/ManagedUserProvider.java @@ -10,11 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.internal.auth; +package org.openhab.core.auth.local.core.internal; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.auth.ManagedUser; -import org.openhab.core.auth.User; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.User; import org.openhab.core.common.registry.DefaultAbstractManagedProvider; import org.openhab.core.common.registry.ManagedProvider; import org.openhab.core.storage.StorageService; diff --git a/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/UserRegistryImpl.java b/bundles/org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal/UserRegistryImpl.java similarity index 77% rename from bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/UserRegistryImpl.java rename to bundles/org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal/UserRegistryImpl.java index 37a1e0c66..f4c651aaa 100755 --- a/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/UserRegistryImpl.java +++ b/bundles/org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal/UserRegistryImpl.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.internal.auth; +package org.openhab.core.auth.local.core.internal; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -28,15 +28,17 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.auth.Authentication; import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.auth.AuthenticationProvider; +import org.openhab.core.auth.AuthenticationResult; import org.openhab.core.auth.Credentials; -import org.openhab.core.auth.ManagedUser; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserApiToken; -import org.openhab.core.auth.UserApiTokenCredentials; -import org.openhab.core.auth.UserProvider; -import org.openhab.core.auth.UserRegistry; -import org.openhab.core.auth.UserSession; +import org.openhab.core.auth.local.GenericUser; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.User; import org.openhab.core.auth.UsernamePasswordCredentials; +import org.openhab.core.auth.local.UserApiToken; +import org.openhab.core.auth.local.UserProvider; +import org.openhab.core.auth.local.UserRegistry; +import org.openhab.core.auth.local.UserSession; import org.openhab.core.common.registry.AbstractRegistry; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Activate; @@ -54,16 +56,11 @@ * @author Yannick Schaus - initial contribution */ @NonNullByDefault -@Component(service = UserRegistry.class, immediate = true) +@Component(service = {UserRegistry.class, AuthenticationProvider.class}, immediate = true) public class UserRegistryImpl extends AbstractRegistry implements UserRegistry { private final Logger logger = LoggerFactory.getLogger(UserRegistryImpl.class); - private static final int PASSWORD_ITERATIONS = 65536; - private static final int APITOKEN_ITERATIONS = 1024; - private static final String APITOKEN_PREFIX = "oh"; - private static final int KEY_LENGTH = 512; - private static final String ALGORITHM = "PBKDF2WithHmacSHA512"; private static final SecureRandom RAND = new SecureRandom(); @Activate @@ -132,7 +129,7 @@ private Optional hash(String password, String salt, int iterations) { } @Override - public Authentication authenticate(Credentials credentials) throws AuthenticationException { + public AuthenticationResult authenticate(Credentials credentials) throws AuthenticationException { if (credentials instanceof UsernamePasswordCredentials) { UsernamePasswordCredentials usernamePasswordCreds = (UsernamePasswordCredentials) credentials; User user = get(usernamePasswordCreds.getUsername()); @@ -147,32 +144,9 @@ public Authentication authenticate(Credentials credentials) throws Authenticatio throw new AuthenticationException("Wrong password for user " + usernamePasswordCreds.getUsername()); } - return new Authentication(managedUser.getName(), managedUser.getRoles().stream().toArray(String[]::new)); - } else if (credentials instanceof UserApiTokenCredentials) { - UserApiTokenCredentials apiTokenCreds = (UserApiTokenCredentials) credentials; - String[] apiTokenParts = apiTokenCreds.getApiToken().split("\\."); - if (apiTokenParts.length != 3 || !APITOKEN_PREFIX.equals(apiTokenParts[0])) { - throw new AuthenticationException("Invalid API token format"); - } - for (User user : getAll()) { - ManagedUser managedUser = (ManagedUser) user; - for (UserApiToken userApiToken : managedUser.getApiTokens()) { - // only check if the name in the token matches - if (!userApiToken.getName().equals(apiTokenParts[1])) { - continue; - } - String[] existingTokenHashAndSalt = userApiToken.getApiToken().split(":"); - String incomingTokenHash = hash(apiTokenCreds.getApiToken(), existingTokenHashAndSalt[1], - APITOKEN_ITERATIONS).get(); - - if (incomingTokenHash.equals(existingTokenHashAndSalt[0])) { - return new Authentication(managedUser.getName(), - managedUser.getRoles().stream().toArray(String[]::new), userApiToken.getScope()); - } - } - } - - throw new AuthenticationException("Unknown API token"); + return new AuthenticationResult(new GenericUser(managedUser.getName()), credentials.getScheme(), + new Authentication(managedUser.getName(), managedUser.getRoles().stream().toArray(String[]::new)) + ); } throw new IllegalArgumentException("Invalid credential type"); diff --git a/bundles/org.opensmarthouse.core.auth.core/src/test/java/org/openhab/core/internal/auth/UserRegistryImplTest.java b/bundles/org.opensmarthouse.core.auth.local.provider/src/test/java/org/openhab/core/auth/local/core/internal/UserRegistryImplTest.java similarity index 73% rename from bundles/org.opensmarthouse.core.auth.core/src/test/java/org/openhab/core/internal/auth/UserRegistryImplTest.java rename to bundles/org.opensmarthouse.core.auth.local.provider/src/test/java/org/openhab/core/auth/local/core/internal/UserRegistryImplTest.java index 3d64d31a1..2551c1393 100644 --- a/bundles/org.opensmarthouse.core.auth.core/src/test/java/org/openhab/core/internal/auth/UserRegistryImplTest.java +++ b/bundles/org.opensmarthouse.core.auth.local.provider/src/test/java/org/openhab/core/auth/local/core/internal/UserRegistryImplTest.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.internal.auth; +package org.openhab.core.auth.local.core.internal; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; @@ -26,11 +26,10 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.openhab.core.auth.ManagedUser; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserApiTokenCredentials; -import org.openhab.core.auth.UserSession; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.User; import org.openhab.core.auth.UsernamePasswordCredentials; +import org.openhab.core.auth.local.UserSession; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceListener; @@ -103,24 +102,4 @@ public void testSessions() throws Exception { assertEquals(user.getSessions().size(), 0); } - @Test - public void testApiTokens() throws Exception { - ManagedUser user = (ManagedUser) registry.register("username", "password", Set.of("administrator")); - registry.added(managedProvider, user); - assertNotNull(user); - String token1 = registry.addUserApiToken(user, "token1", "scope1"); - String token2 = registry.addUserApiToken(user, "token2", "scope2"); - String token3 = registry.addUserApiToken(user, "token3", "scope3"); - assertEquals(user.getApiTokens().size(), 3); - registry.authenticate(new UserApiTokenCredentials(token1)); - registry.authenticate(new UserApiTokenCredentials(token2)); - registry.authenticate(new UserApiTokenCredentials(token3)); - registry.removeUserApiToken(user, - user.getApiTokens().stream().filter(t -> t.getName().equals("token1")).findAny().get()); - registry.removeUserApiToken(user, - user.getApiTokens().stream().filter(t -> t.getName().equals("token2")).findAny().get()); - registry.removeUserApiToken(user, - user.getApiTokens().stream().filter(t -> t.getName().equals("token3")).findAny().get()); - assertEquals(user.getApiTokens().size(), 0); - } } diff --git a/bundles/org.opensmarthouse.core.auth.local/NOTICE b/bundles/org.opensmarthouse.core.auth.local/NOTICE new file mode 100755 index 000000000..3e53bd427 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.local/NOTICE @@ -0,0 +1,18 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +Elements of this bundle are copyright to the following -: + +* The Eclipse Foundation, having come from the Eclipse SmartHome project +* The openHAB project + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.auth.local/pom.xml b/bundles/org.opensmarthouse.core.auth.local/pom.xml new file mode 100755 index 000000000..864d77497 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.local/pom.xml @@ -0,0 +1,27 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.auth.local + + OpenSmartHouse Core | Bundles | Local Authentication + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.core + + + org.osgi + osgi.cmpn + + + + diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/GenericUser.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/GenericUser.java similarity index 92% rename from bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/GenericUser.java rename to bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/GenericUser.java index f0a0ef89b..ffac37d86 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/GenericUser.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/GenericUser.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.auth; +package org.openhab.core.auth.local; import java.util.HashSet; import java.util.Set; @@ -63,4 +63,8 @@ public String getUID() { public Set getRoles() { return roles; } + + public String toString() { + return name + " (" + roles + ")"; + } } diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/ManagedUser.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/ManagedUser.java similarity index 98% rename from bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/ManagedUser.java rename to bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/ManagedUser.java index 3f132200d..a429d5194 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/ManagedUser.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/ManagedUser.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.auth; +package org.openhab.core.auth.local; import java.util.ArrayList; import java.util.HashSet; @@ -19,6 +19,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.auth.local.User; /** * A {@link User} sourced from a managed {@link UserProvider}. diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/PendingToken.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/PendingToken.java similarity index 98% rename from bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/PendingToken.java rename to bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/PendingToken.java index 30342775f..108a86e29 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/PendingToken.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/PendingToken.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.auth; +package org.openhab.core.auth.local; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/User.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/User.java similarity index 92% rename from bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/User.java rename to bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/User.java index 7f11c9b58..fd78c1a44 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/User.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/User.java @@ -10,12 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.auth; +package org.openhab.core.auth.local; import java.security.Principal; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.auth.Role; import org.openhab.core.common.registry.Identifiable; /** diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserApiToken.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserApiToken.java similarity index 98% rename from bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserApiToken.java rename to bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserApiToken.java index 401ea9ebb..5a4194218 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserApiToken.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserApiToken.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.auth; +package org.openhab.core.auth.local; import java.util.Date; diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserProvider.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserProvider.java similarity index 94% rename from bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserProvider.java rename to bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserProvider.java index 9faba4d6e..38c8c8175 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserProvider.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserProvider.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.auth; +package org.openhab.core.auth.local; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.common.registry.Provider; diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserRegistry.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserRegistry.java similarity index 58% rename from bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserRegistry.java rename to bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserRegistry.java index dbdb14b4f..d8076f3a9 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserRegistry.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserRegistry.java @@ -10,12 +10,22 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.auth; +package org.openhab.core.auth.local; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import java.util.Base64; +import java.util.Optional; import java.util.Set; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.auth.AuthenticationProvider; import org.openhab.core.common.registry.Registry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An interface for a generic {@link Registry} of {@link User} entities. User registries can also be used as @@ -27,6 +37,12 @@ @NonNullByDefault public interface UserRegistry extends Registry, AuthenticationProvider { + int APITOKEN_ITERATIONS = 1024; + String APITOKEN_PREFIX = "oh"; + int PASSWORD_ITERATIONS = 65536; + int KEY_LENGTH = 512; + String ALGORITHM = "PBKDF2WithHmacSHA512"; + /** * Adds a new {@link User} in this registry. The implementation receives the clear text credentials and is * responsible for their secure storage (for instance by hashing the password), then return the newly created @@ -37,7 +53,7 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param roles the roles attributed to the new user * @return the new registered {@link User} instance */ - public User register(String username, String password, Set roles); + User register(String username, String password, Set roles); /** * Change the password for an {@link User} in this registry. The implementation receives the new password and is @@ -46,7 +62,7 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param username the username of the existing user * @param newPassword the new password */ - public void changePassword(User user, String newPassword); + void changePassword(User user, String newPassword); /** * Adds a new session to the user profile @@ -54,7 +70,7 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param user the user * @param session the session to add */ - public void addUserSession(User user, UserSession session); + void addUserSession(User user, UserSession session); /** * Removes the specified session from the user profile @@ -62,14 +78,14 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param user the user * @param session the session to remove */ - public void removeUserSession(User user, UserSession session); + void removeUserSession(User user, UserSession session); /** * Clears all sessions from the user profile * * @param user the user */ - public void clearSessions(User user); + void clearSessions(User user); /** * Adds a new API token to the user profile. The implementation is responsible for storing the token in a secure way @@ -80,7 +96,7 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param scope the scope this API token will be valid for * @return the string that can be used as a Bearer token to match the new API token */ - public String addUserApiToken(User user, String name, String scope); + String addUserApiToken(User user, String name, String scope); /** * Removes the specified API token from the user profile @@ -88,5 +104,25 @@ public interface UserRegistry extends Registry, AuthenticationProv * @param user the user * @param apiToken the API token */ - public void removeUserApiToken(User user, UserApiToken apiToken); + void removeUserApiToken(User user, UserApiToken apiToken); + + static Optional hash(String password, String salt, int iterations) { + char[] chars = password.toCharArray(); + byte[] bytes = salt.getBytes(); + + PBEKeySpec spec = new PBEKeySpec(chars, bytes, iterations, KEY_LENGTH); + + Arrays.fill(chars, Character.MIN_VALUE); + + try { + SecretKeyFactory fac = SecretKeyFactory.getInstance(ALGORITHM); + byte[] securePassword = fac.generateSecret(spec).getEncoded(); + return Optional.of(Base64.getEncoder().encodeToString(securePassword)); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + LoggerFactory.getLogger(UserRegistry.class).error("Exception encountered while hashing", e); + return Optional.empty(); + } finally { + spec.clearPassword(); + } + } } diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserSession.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserSession.java similarity index 99% rename from bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserSession.java rename to bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserSession.java index 674908f1d..98f4a139d 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UserSession.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/UserSession.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.auth; +package org.openhab.core.auth.local; import java.util.Date; diff --git a/bundles/org.opensmarthouse.core.auth.password/NOTICE b/bundles/org.opensmarthouse.core.auth.password/NOTICE new file mode 100755 index 000000000..3e53bd427 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.password/NOTICE @@ -0,0 +1,18 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +Elements of this bundle are copyright to the following -: + +* The Eclipse Foundation, having come from the Eclipse SmartHome project +* The openHAB project + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.auth.password/pom.xml b/bundles/org.opensmarthouse.core.auth.password/pom.xml new file mode 100755 index 000000000..dc6049f54 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.password/pom.xml @@ -0,0 +1,23 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.auth.password + + OpenSmartHouse Core | Bundles | Password Credentials + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth + + + + diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java index 15804ee37..9ebe3c0be 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java @@ -12,8 +12,6 @@ */ package org.openhab.core.auth; -import java.util.Arrays; -import java.util.HashSet; import java.util.Set; /** @@ -22,7 +20,7 @@ * Each authentication must at least point to some identity (username), roles, and may also be valid for a specific * scope only. * - * @author Łukasz Dywicki - Initial contribution + * @author Łukasz Dywicki - Initial contribution, item level security * @author Kai Kreuzer - Added JavaDoc and switched from array to Set * @author Yannick Schaus - Add scope */ @@ -31,6 +29,7 @@ public class Authentication { private String username; private Set roles; private String scope; + private Set items; /** * no-args constructor required by gson @@ -39,6 +38,7 @@ protected Authentication() { this.username = null; this.roles = null; this.scope = null; + this.items = null; } /** @@ -48,8 +48,7 @@ protected Authentication() { * @param roles a variable list of roles that the user possesses. */ public Authentication(String username, String... roles) { - this.username = username; - this.roles = new HashSet<>(Arrays.asList(roles)); + this(username, roles, ""); } /** @@ -60,8 +59,21 @@ public Authentication(String username, String... roles) { * @param scope a scope this authentication is valid for */ public Authentication(String username, String[] roles, String scope) { - this(username, roles); + this(username, roles, scope, new String[] {"*"}); + } + + /** + * Creates a new instance with a specific scope + * + * @param username name of the user associated to this authentication instance + * @param roles a variable list of roles that the user possesses. + * @param scope a scope this authentication is valid for + */ + public Authentication(String username, String[] roles, String scope, String[] items) { + this.username = username; + this.roles = Set.of(roles); this.scope = scope; + this.items = Set.of(items); } /** @@ -90,4 +102,13 @@ public Set getRoles() { public String getScope() { return scope; } + + /** + * Retrieves the items this authentication is valid for. + * + * @return an set of items (might be empty) + */ + public Set getItems() { + return items; + } } diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationManager.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationManager.java index 7c90034e5..c64677b0e 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationManager.java +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationManager.java @@ -28,5 +28,5 @@ public interface AuthenticationManager { * AuthenticationException. * @throws AuthenticationException when none of available authentication methods succeeded. */ - Authentication authenticate(Credentials credentials) throws AuthenticationException; + AuthenticationResult authenticate(Credentials credentials) throws AuthenticationException; } diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationProvider.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationProvider.java index dc951710d..8d95bf2fd 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationProvider.java +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationProvider.java @@ -30,7 +30,7 @@ public interface AuthenticationProvider { * @return null if credentials were not valid for this provider * @throws AuthenticationException if authentication failed due to credentials mismatch. */ - Authentication authenticate(Credentials credentials) throws AuthenticationException; + AuthenticationResult authenticate(Credentials credentials) throws AuthenticationException; /** * Additional method to verify if given authentication provider can handle given type of credentials. diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationResult.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationResult.java new file mode 100755 index 000000000..f8980924d --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationResult.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth; + +import java.security.Principal; + +/** + * Type describing successful authentication attempt. + * + * @author Łukasz Dywicki - Initial contribution + */ +public class AuthenticationResult { + + private final Principal principal; + private final String scheme; + private final Authentication authentication; + + public AuthenticationResult(Principal principal, String scheme, Authentication authentication) { + this.principal = principal; + this.scheme = scheme; + this.authentication = authentication; + } + + public Principal getPrincipal() { + return principal; + } + + public String getScheme() { + return scheme; + } + + public Authentication getAuthentication() { + return authentication; + } + +} diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Credentials.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Credentials.java index d4ffd323a..ee97f79af 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Credentials.java +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Credentials.java @@ -19,4 +19,6 @@ */ public interface Credentials { + String getScheme(); + } diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UsernamePasswordCredentials.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UsernamePasswordCredentials.java index 8205d6c53..40e328cf0 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UsernamePasswordCredentials.java +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UsernamePasswordCredentials.java @@ -22,6 +22,7 @@ public class UsernamePasswordCredentials implements Credentials { private final String username; private final String password; + private final String scheme; /** * Creates a new instance @@ -30,8 +31,20 @@ public class UsernamePasswordCredentials implements Credentials { * @param password password of the user */ public UsernamePasswordCredentials(String username, String password) { + this(username, password, "basic"); + } + + /** + * Creates a new instance with given scheme. + * + * @param username name of the user + * @param password password of the user + * @param scheme login scheme + */ + public UsernamePasswordCredentials(String username, String password, String scheme) { this.username = username; this.password = password; + this.scheme = scheme; } /** @@ -52,8 +65,13 @@ public String getPassword() { return password; } + @Override + public String getScheme() { + return scheme; + } + @Override public String toString() { - return username + ":" + password.replaceAll(".", "*"); + return scheme + "(" + username + ":[protected])"; } } diff --git a/bundles/org.opensmarthouse.core.io.auth/NOTICE b/bundles/org.opensmarthouse.core.io.auth/NOTICE new file mode 100755 index 000000000..bed08ed3f --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.auth/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.io.auth/pom.xml b/bundles/org.opensmarthouse.core.io.auth/pom.xml new file mode 100755 index 000000000..2c638b061 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.auth/pom.xml @@ -0,0 +1,23 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.io.auth + + OpenSmartHouse Core | Bundles | IO Authentication + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth + + + + diff --git a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/CredentialsExtractor.java b/bundles/org.opensmarthouse.core.io.auth/src/main/java/org/openhab/core/io/auth/CredentialsExtractor.java similarity index 95% rename from bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/CredentialsExtractor.java rename to bundles/org.opensmarthouse.core.io.auth/src/main/java/org/openhab/core/io/auth/CredentialsExtractor.java index 613791ed9..3fa5f4c03 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/CredentialsExtractor.java +++ b/bundles/org.opensmarthouse.core.io.auth/src/main/java/org/openhab/core/io/auth/CredentialsExtractor.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.http.auth; +package org.openhab.core.io.auth; import java.util.Optional; diff --git a/bundles/org.opensmarthouse.core.io.console.user/pom.xml b/bundles/org.opensmarthouse.core.io.console.user/pom.xml index 0f2aa46e7..886894d85 100644 --- a/bundles/org.opensmarthouse.core.io.console.user/pom.xml +++ b/bundles/org.opensmarthouse.core.io.console.user/pom.xml @@ -14,6 +14,10 @@ OpenSmartHouse Core | Bundles | Console | User + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.local + org.opensmarthouse.core.bundles org.opensmarthouse.core.io.console diff --git a/bundles/org.opensmarthouse.core.io.console.user/src/main/java/org/openhab/core/io/console/internal/extension/UserConsoleCommandExtension.java b/bundles/org.opensmarthouse.core.io.console.user/src/main/java/org/openhab/core/io/console/internal/extension/UserConsoleCommandExtension.java index 1ffab9c47..b2744497e 100644 --- a/bundles/org.opensmarthouse.core.io.console.user/src/main/java/org/openhab/core/io/console/internal/extension/UserConsoleCommandExtension.java +++ b/bundles/org.opensmarthouse.core.io.console.user/src/main/java/org/openhab/core/io/console/internal/extension/UserConsoleCommandExtension.java @@ -17,10 +17,10 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.auth.ManagedUser; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserApiToken; -import org.openhab.core.auth.UserRegistry; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.User; +import org.openhab.core.auth.local.UserApiToken; +import org.openhab.core.auth.local.UserRegistry; import org.openhab.core.io.console.Console; import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension; import org.openhab.core.io.console.extensions.ConsoleCommandExtension; diff --git a/bundles/org.opensmarthouse.core.io.http.auth.apitoken/NOTICE b/bundles/org.opensmarthouse.core.io.http.auth.apitoken/NOTICE new file mode 100755 index 000000000..3e53bd427 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.auth.apitoken/NOTICE @@ -0,0 +1,18 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +Elements of this bundle are copyright to the following -: + +* The Eclipse Foundation, having come from the Eclipse SmartHome project +* The openHAB project + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.io.http.auth.apitoken/pom.xml b/bundles/org.opensmarthouse.core.io.http.auth.apitoken/pom.xml new file mode 100755 index 000000000..bbb8c778f --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.auth.apitoken/pom.xml @@ -0,0 +1,31 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.io.http.auth.apitoken + + OpenSmartHouse Core | Bundles | HTTP/Apitoken Authentication + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.auth + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.http.facade + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.apitoken + + + + diff --git a/bundles/org.opensmarthouse.core.io.http.auth.apitoken/src/main/java/org/openhab/core/io/http/auth/apitoken/internal/ApitokenCredentialsExtractor.java b/bundles/org.opensmarthouse.core.io.http.auth.apitoken/src/main/java/org/openhab/core/io/http/auth/apitoken/internal/ApitokenCredentialsExtractor.java new file mode 100644 index 000000000..7e3b7579f --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.auth.apitoken/src/main/java/org/openhab/core/io/http/auth/apitoken/internal/ApitokenCredentialsExtractor.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.http.auth.apitoken.internal; + +import java.util.Optional; +import org.openhab.core.auth.Credentials; +import org.openhab.core.io.auth.CredentialsExtractor; +import org.openhab.core.io.http.facade.HttpRequestDelegate; +import org.osgi.service.component.annotations.Component; +import org.openhab.core.auth.apitoken.UserApiTokenCredentials; + +/** + * + * @author Łukasz Dywicki - Extracted from {@link org.openhab.core.io.rest.auth.internal.AuthFilter} + */ +@Component(property = { "context=org.openhab.core.io.http.facade.HttpRequestDelegate" }) +public class ApitokenCredentialsExtractor implements CredentialsExtractor { + + private static final String ALT_AUTH_HEADER = "X-OPENHAB-TOKEN"; + private static final String API_TOKEN_PREFIX = "oh."; + + @Override + public Optional retrieveCredentials(HttpRequestDelegate requestContext) { + return requestContext.getHeader(ALT_AUTH_HEADER) + .filter(token -> token.startsWith(API_TOKEN_PREFIX)) + .map(token -> new UserApiTokenCredentials(token)); + } +} diff --git a/bundles/org.opensmarthouse.core.io.http.auth.basic/pom.xml b/bundles/org.opensmarthouse.core.io.http.auth.basic/pom.xml index d92f0f284..3646f8d20 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth.basic/pom.xml +++ b/bundles/org.opensmarthouse.core.io.http.auth.basic/pom.xml @@ -14,10 +14,17 @@ OpenSmartHouse Core | Bundles | HTTP Authentication Basic + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.auth + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.http.facade + org.opensmarthouse.core.bundles org.opensmarthouse.core.io.http.auth - ${project.version} diff --git a/bundles/org.opensmarthouse.core.io.http.auth.basic/src/main/java/org/openhab/core/io/http/auth/basic/internal/BasicCredentialsExtractor.java b/bundles/org.opensmarthouse.core.io.http.auth.basic/src/main/java/org/openhab/core/io/http/auth/basic/internal/BasicCredentialsExtractor.java index 2fc4a0a71..ab76ebcb6 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth.basic/src/main/java/org/openhab/core/io/http/auth/basic/internal/BasicCredentialsExtractor.java +++ b/bundles/org.opensmarthouse.core.io.http.auth.basic/src/main/java/org/openhab/core/io/http/auth/basic/internal/BasicCredentialsExtractor.java @@ -19,25 +19,26 @@ import org.openhab.core.auth.Credentials; import org.openhab.core.auth.UsernamePasswordCredentials; -import org.openhab.core.io.http.auth.CredentialsExtractor; +import org.openhab.core.io.auth.CredentialsExtractor; +import org.openhab.core.io.http.facade.HttpRequestDelegate; import org.osgi.service.component.annotations.Component; /** * Extract user name and password from incoming request. * - * @author Łukasz Dywicki - Initial contribution. + * @author Łukasz Dywicki - Initial contribution, migration to Http request facade. */ -@Component(property = { "context=javax.servlet.http.HttpServletRequest" }) -public class BasicCredentialsExtractor implements CredentialsExtractor { +@Component(property = { "context=org.openhab.core.io.http.facade.HttpRequestDelegate" }) +public class BasicCredentialsExtractor implements CredentialsExtractor { @Override - public Optional retrieveCredentials(HttpServletRequest request) { - String authenticationHeader = request.getHeader("Authorization"); - - if (authenticationHeader == null) { - return Optional.empty(); - } + public Optional retrieveCredentials(HttpRequestDelegate request) { + return request.getAuthorizationHeader() + .filter(header -> header.contains(" ")) + .flatMap(this::process); + } + private Optional process(String authenticationHeader) { String[] tokens = authenticationHeader.split(" "); if (tokens.length == 2) { String authType = tokens[0]; diff --git a/bundles/org.opensmarthouse.core.io.http.auth.jwt/NOTICE b/bundles/org.opensmarthouse.core.io.http.auth.jwt/NOTICE new file mode 100755 index 000000000..3e53bd427 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.auth.jwt/NOTICE @@ -0,0 +1,18 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +Elements of this bundle are copyright to the following -: + +* The Eclipse Foundation, having come from the Eclipse SmartHome project +* The openHAB project + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.io.http.auth.jwt/pom.xml b/bundles/org.opensmarthouse.core.io.http.auth.jwt/pom.xml new file mode 100755 index 000000000..b5bb15f89 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.auth.jwt/pom.xml @@ -0,0 +1,31 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.io.http.auth.jwt + + OpenSmartHouse Core | Bundles | HTTP/JWT Authentication + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.auth + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.http.facade + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.jwt + + + + diff --git a/bundles/org.opensmarthouse.core.io.http.auth.jwt/src/main/java/org/openhab/core/io/http/auth/jwt/internal/JwtCredentialsExtractor.java b/bundles/org.opensmarthouse.core.io.http.auth.jwt/src/main/java/org/openhab/core/io/http/auth/jwt/internal/JwtCredentialsExtractor.java new file mode 100644 index 000000000..2dc6beaf0 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.auth.jwt/src/main/java/org/openhab/core/io/http/auth/jwt/internal/JwtCredentialsExtractor.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.http.auth.jwt.internal; + +import java.util.Optional; +import org.openhab.core.auth.Credentials; +import org.openhab.core.auth.jwt.JWTCredentials; +import org.openhab.core.io.auth.CredentialsExtractor; +import org.openhab.core.io.http.facade.HttpRequestDelegate; +import org.osgi.service.component.annotations.Component; + +/** + * An extractor of request credentials which rely on token sent in authorization header. + * + * @author Łukasz Dywicki - Extracted from {@link org.openhab.core.io.rest.auth.internal.AuthFilter} + */ +@Component(property = { "context=org.openhab.core.io.http.facade.HttpRequestDelegate" }) +public class JwtCredentialsExtractor implements CredentialsExtractor { + + @Override + public Optional retrieveCredentials(HttpRequestDelegate request) { + return request.getAuthorizationHeader() + .filter(header -> header.contains(" ")) + .flatMap(this::process); + } + + private Optional process(String authenticationHeader) { + String[] tokens = authenticationHeader.split(" "); + if (tokens.length == 2) { + if ("bearer".equalsIgnoreCase(tokens[0])) { + return Optional.of(new JWTCredentials(tokens[1])); + } + } + + return Optional.empty(); + } +} diff --git a/bundles/org.opensmarthouse.core.io.http.auth/pom.xml b/bundles/org.opensmarthouse.core.io.http.auth/pom.xml index a5ccff547..52374cf39 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth/pom.xml +++ b/bundles/org.opensmarthouse.core.io.http.auth/pom.xml @@ -18,6 +18,14 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.auth + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.auth + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.http.facade + org.opensmarthouse.core.bundles org.opensmarthouse.core.io.http @@ -26,6 +34,10 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.i18n + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.local + org.eclipse.jetty diff --git a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java index 217ec6962..ad156cd84 100644 --- a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java +++ b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java @@ -26,15 +26,17 @@ import java.util.UUID; import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.auth.Authentication; import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.auth.AuthenticationManager; import org.openhab.core.auth.AuthenticationProvider; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserRegistry; +import org.openhab.core.auth.AuthenticationResult; +import org.openhab.core.auth.local.User; +import org.openhab.core.auth.local.UserRegistry; import org.openhab.core.auth.UsernamePasswordCredentials; import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.BundleContext; @@ -60,7 +62,7 @@ public abstract class AbstractAuthPageServlet extends HttpServlet { protected HttpService httpService; protected UserRegistry userRegistry; - protected AuthenticationProvider authProvider; + protected AuthenticationManager authManager; protected LocaleProvider localeProvider; protected @Nullable Instant lastAuthenticationFailure; protected int authenticationFailureCount = 0; @@ -70,11 +72,11 @@ public abstract class AbstractAuthPageServlet extends HttpServlet { protected String pageTemplate; public AbstractAuthPageServlet(BundleContext bundleContext, @Reference HttpService httpService, - @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider, + @Reference UserRegistry userRegistry, @Reference AuthenticationManager authManager, @Reference LocaleProvider localeProvider) { this.httpService = httpService; this.userRegistry = userRegistry; - this.authProvider = authProvider; + this.authManager = authManager; this.localeProvider = localeProvider; pageTemplate = ""; @@ -130,12 +132,12 @@ protected User login(String username, String password) throws AuthenticationExce } // Authenticate the user with the supplied credentials - UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password); - Authentication auth = authProvider.authenticate(credentials); - logger.debug("Login successful: {}", auth.getUsername()); + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password, HttpServletRequest.FORM_AUTH); + AuthenticationResult auth = authManager.authenticate(credentials); + logger.debug("Login successful: {}", auth.getPrincipal()); lastAuthenticationFailure = null; authenticationFailureCount = 0; - User user = userRegistry.get(auth.getUsername()); + User user = userRegistry.get(auth.getAuthentication().getUsername()); if (user == null) { throw new AuthenticationException("User not found"); } diff --git a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java index 45577f354..654712ca3 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java +++ b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthenticationHandler.java @@ -25,11 +25,13 @@ import org.openhab.core.auth.Authentication; import org.openhab.core.auth.AuthenticationException; import org.openhab.core.auth.AuthenticationManager; +import org.openhab.core.auth.AuthenticationResult; import org.openhab.core.auth.Credentials; import org.openhab.core.io.http.Handler; import org.openhab.core.io.http.HandlerContext; import org.openhab.core.io.http.HandlerPriorities; -import org.openhab.core.io.http.auth.CredentialsExtractor; +import org.openhab.core.io.http.facade.HttpRequestDelegate; +import org.openhab.core.io.auth.CredentialsExtractor; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Modified; @@ -54,7 +56,7 @@ public class AuthenticationHandler implements Handler { private final Logger logger = LoggerFactory.getLogger(AuthenticationHandler.class); - private final List> extractors = new CopyOnWriteArrayList<>(); + private final List> extractors = new CopyOnWriteArrayList<>(); private AuthenticationManager authenticationManager; @@ -77,14 +79,14 @@ public void handle(final HttpServletRequest request, final HttpServletResponse r } int found = 0, failed = 0; - for (CredentialsExtractor extractor : extractors) { - Optional extracted = extractor.retrieveCredentials(request); + for (CredentialsExtractor extractor : extractors) { + Optional extracted = extractor.retrieveCredentials(new ServletRequestDelegate(request)); if (extracted.isPresent()) { found++; Credentials credentials = extracted.get(); try { - Authentication authentication = authenticationManager.authenticate(credentials); - request.setAttribute(Authentication.class.getName(), authentication); + AuthenticationResult authentication = authenticationManager.authenticate(credentials); + request.setAttribute(Authentication.class.getName(), authentication.getAuthentication()); context.execute(request, response); return; } catch (AuthenticationException e) { @@ -167,12 +169,12 @@ public void unsetAuthenticationManager(AuthenticationManager authenticationManag this.authenticationManager = null; } - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, target = "(context=javax.servlet.http.HttpServletRequest)") - public void addCredentialsExtractor(CredentialsExtractor extractor) { + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, target = "(context=org.openhab.core.io.http.facade.HttpRequestDelegate)") + public void addCredentialsExtractor(CredentialsExtractor extractor) { this.extractors.add(extractor); } - public void removeCredentialsExtractor(CredentialsExtractor extractor) { + public void removeCredentialsExtractor(CredentialsExtractor extractor) { this.extractors.remove(extractor); } } diff --git a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java index d53eb5605..b1c26a228 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java +++ b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AuthorizePageServlet.java @@ -26,12 +26,12 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.http.HttpStatus; import org.openhab.core.auth.AuthenticationException; -import org.openhab.core.auth.AuthenticationProvider; -import org.openhab.core.auth.ManagedUser; -import org.openhab.core.auth.PendingToken; +import org.openhab.core.auth.AuthenticationManager; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.PendingToken; import org.openhab.core.auth.Role; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserRegistry; +import org.openhab.core.auth.local.User; +import org.openhab.core.auth.local.UserRegistry; import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Activate; @@ -64,9 +64,9 @@ public class AuthorizePageServlet extends AbstractAuthPageServlet { @Activate public AuthorizePageServlet(BundleContext bundleContext, @Reference HttpService httpService, - @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider, + @Reference UserRegistry userRegistry, @Reference AuthenticationManager authManager, @Reference LocaleProvider localeProvider) { - super(bundleContext, httpService, userRegistry, authProvider, localeProvider); + super(bundleContext, httpService, userRegistry, authManager, localeProvider); try { httpService.registerServlet("/auth", this, null, null); } catch (NamespaceException | ServletException e) { diff --git a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ChangePasswordPageServlet.java b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ChangePasswordPageServlet.java index fa0dcfdd7..abc8efa09 100644 --- a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ChangePasswordPageServlet.java +++ b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ChangePasswordPageServlet.java @@ -21,10 +21,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.auth.AuthenticationManager; import org.openhab.core.auth.AuthenticationProvider; -import org.openhab.core.auth.ManagedUser; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserRegistry; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.User; +import org.openhab.core.auth.local.UserRegistry; import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Activate; @@ -52,9 +53,9 @@ public class ChangePasswordPageServlet extends AbstractAuthPageServlet { @Activate public ChangePasswordPageServlet(BundleContext bundleContext, @Reference HttpService httpService, - @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider, + @Reference UserRegistry userRegistry, @Reference AuthenticationManager authManager, @Reference LocaleProvider localeProvider) { - super(bundleContext, httpService, userRegistry, authProvider, localeProvider); + super(bundleContext, httpService, userRegistry, authManager, localeProvider); try { httpService.registerServlet("/changePassword", this, null, null); } catch (NamespaceException | ServletException e) { diff --git a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java index 2575386cb..c87d4c3f5 100644 --- a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java +++ b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/CreateAPITokenPageServlet.java @@ -21,10 +21,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.auth.AuthenticationManager; import org.openhab.core.auth.AuthenticationProvider; -import org.openhab.core.auth.ManagedUser; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserRegistry; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.User; +import org.openhab.core.auth.local.UserRegistry; import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Activate; @@ -52,9 +53,9 @@ public class CreateAPITokenPageServlet extends AbstractAuthPageServlet { @Activate public CreateAPITokenPageServlet(BundleContext bundleContext, @Reference HttpService httpService, - @Reference UserRegistry userRegistry, @Reference AuthenticationProvider authProvider, + @Reference UserRegistry userRegistry, @Reference AuthenticationManager authManager, @Reference LocaleProvider localeProvider) { - super(bundleContext, httpService, userRegistry, authProvider, localeProvider); + super(bundleContext, httpService, userRegistry, authManager, localeProvider); try { httpService.registerServlet("/createApiToken", this, null, null); } catch (NamespaceException | ServletException e) { diff --git a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ServletRequestDelegate.java b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ServletRequestDelegate.java new file mode 100644 index 000000000..28b465af9 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ServletRequestDelegate.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.http.auth.internal; + +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; +import org.openhab.core.io.http.facade.HttpRequestDelegate; + +/** + * Servlet request wrapper. + * + * @author Łukasz Dywicki - Initial contribution + */ +public class ServletRequestDelegate implements HttpRequestDelegate { + + private final HttpServletRequest request; + + public ServletRequestDelegate(HttpServletRequest request) { + this.request = request; + } + + @Override + public Optional getHeader(String headerName) { + return Optional.ofNullable(request.getHeader(headerName)); + } + +} diff --git a/bundles/org.opensmarthouse.core.io.http.facade/NOTICE b/bundles/org.opensmarthouse.core.io.http.facade/NOTICE new file mode 100755 index 000000000..bed08ed3f --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.facade/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.io.http.facade/pom.xml b/bundles/org.opensmarthouse.core.io.http.facade/pom.xml new file mode 100755 index 000000000..ebb44490a --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.facade/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.io.http.facade + + OpenSmartHouse Core | Bundles | IO HTTP Facade + Server side HTTP api facade + + diff --git a/bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/HttpRequestDelegate.java b/bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/HttpRequestDelegate.java new file mode 100644 index 000000000..ed10a1b0c --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/HttpRequestDelegate.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.http.facade; + +import java.util.Optional; + +/** + * A thin abstraction layer over HTTP request to bridge servlet and JAX-RS api. + * + * @author Łukasz Dywicki - Initial contribution + */ +public interface HttpRequestDelegate { + + Optional getHeader(String headerName); + + default Optional getAuthorizationHeader() { + return getHeader("Authorization"); + } +} \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml b/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml new file mode 100755 index 000000000..463c32158 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml @@ -0,0 +1,48 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.io.rest.auth.local + + OpenSmartHouse Core | Bundles | REST | Local (o)Auth 2 Server + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.config + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.jwt + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.rest + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.rest.auth + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core + + + + javax.annotation + javax.annotation-api + + + org.bitbucket.b_c + jose4j + + + + diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtHelper.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/JwtHelper.java similarity index 98% rename from bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtHelper.java rename to bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/JwtHelper.java index 5212d063f..5c41fcac7 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtHelper.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/JwtHelper.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth.local.internal; import java.io.BufferedReader; import java.io.File; @@ -39,7 +39,7 @@ import org.jose4j.lang.JoseException; import org.openhab.core.auth.Authentication; import org.openhab.core.auth.AuthenticationException; -import org.openhab.core.auth.User; +import org.openhab.core.auth.local.User; import org.opensmarthouse.core.OpenSmartHouse; import org.osgi.service.component.annotations.Component; import org.slf4j.Logger; diff --git a/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/LocalJwtAuthenticationProvider.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/LocalJwtAuthenticationProvider.java new file mode 100644 index 000000000..c45d782f6 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/LocalJwtAuthenticationProvider.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.auth.local.internal; + +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.auth.AuthenticationProvider; +import org.openhab.core.auth.AuthenticationResult; +import org.openhab.core.auth.Credentials; +import org.openhab.core.auth.jwt.JWTCredentials; +import org.openhab.core.auth.local.GenericUser; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +@Component +public class LocalJwtAuthenticationProvider implements AuthenticationProvider { + + private final JwtHelper jwtHelper; + + @Activate + public LocalJwtAuthenticationProvider(@Reference JwtHelper jwtHelper) { + this.jwtHelper = jwtHelper; + } + + @Override + public AuthenticationResult authenticate(Credentials credentials) throws AuthenticationException { + if (!(credentials instanceof JWTCredentials)) { + throw new IllegalArgumentException("Unsupported credentials"); + } + + String token = ((JWTCredentials) credentials).getToken(); + Authentication authentication = jwtHelper.verifyAndParseJwtAccessToken(token); + return new AuthenticationResult( + new GenericUser(authentication.getUsername()), credentials.getScheme(), authentication + ); + } + + @Override + public boolean supports(Class type) { + return JWTCredentials.class.isAssignableFrom(type); + } + +} diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenEndpointException.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenEndpointException.java similarity index 97% rename from bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenEndpointException.java rename to bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenEndpointException.java index 088cd8666..fdb256f5a 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenEndpointException.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenEndpointException.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth.local.internal; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.auth.AuthenticationException; diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenResource.java similarity index 97% rename from bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java rename to bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenResource.java index d5a557ef8..6d267463c 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResource.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenResource.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth.local.internal; import java.net.URI; import java.security.MessageDigest; @@ -43,17 +43,17 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.jose4j.base64url.Base64Url; -import org.openhab.core.auth.ManagedUser; -import org.openhab.core.auth.PendingToken; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserApiToken; -import org.openhab.core.auth.UserRegistry; -import org.openhab.core.auth.UserSession; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.PendingToken; +import org.openhab.core.auth.local.User; +import org.openhab.core.auth.local.UserApiToken; +import org.openhab.core.auth.local.UserRegistry; +import org.openhab.core.auth.local.UserSession; import org.openhab.core.io.rest.JSONResponse; import org.openhab.core.io.rest.RESTConstants; import org.openhab.core.io.rest.RESTResource; import org.openhab.core.io.rest.Stream2JSONInputStream; -import org.openhab.core.io.rest.auth.internal.TokenEndpointException.ErrorType; +import org.openhab.core.io.rest.auth.local.internal.TokenEndpointException.ErrorType; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResponseDTO.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenResponseDTO.java similarity index 94% rename from bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResponseDTO.java rename to bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenResponseDTO.java index 82d4fd718..40f1e261a 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResponseDTO.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenResponseDTO.java @@ -10,9 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth.local.internal; -import org.openhab.core.auth.User; +import org.openhab.core.auth.local.User; /** * A DTO object for a successful token endpoint response, as per RFC 6749, Section 5.1. diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResponseErrorDTO.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenResponseErrorDTO.java similarity index 94% rename from bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResponseErrorDTO.java rename to bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenResponseErrorDTO.java index 263f607a4..530bdb23e 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/TokenResponseErrorDTO.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/TokenResponseErrorDTO.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth.local.internal; /** * A DTO object for an unsuccessful token endpoint response, as per RFC 6749, Section 5.2. diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserApiTokenDTO.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserApiTokenDTO.java similarity index 93% rename from bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserApiTokenDTO.java rename to bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserApiTokenDTO.java index 885391d1e..fcf15fb4b 100644 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserApiTokenDTO.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserApiTokenDTO.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth.local.internal; import java.util.Date; diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserDTO.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserDTO.java similarity index 88% rename from bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserDTO.java rename to bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserDTO.java index b713e6c11..011823105 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserDTO.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserDTO.java @@ -10,11 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth.local.internal; import java.util.Collection; -import org.openhab.core.auth.User; +import org.openhab.core.auth.local.User; /** * A DTO representing a {@link User}. diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserSecurityContext.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserSecurityContext.java similarity index 59% rename from bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserSecurityContext.java rename to bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserSecurityContext.java index 449718f86..13dfbe885 100644 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserSecurityContext.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserSecurityContext.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth.local.internal; import java.security.Principal; @@ -19,7 +19,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.auth.Authentication; -import org.openhab.core.auth.User; +import org.openhab.core.auth.local.User; +import org.openhab.core.io.rest.auth.AuthenticationSecurityContext; /** * This {@link SecurityContext} contains information about a user, roles and authorizations granted to a client @@ -30,31 +31,39 @@ @NonNullByDefault public class UserSecurityContext implements AuthenticationSecurityContext { - private User user; - private Authentication authentication; - private String authenticationScheme; + private final Principal principal; + private final Authentication authentication; + private final String authenticationScheme; + private final boolean secure; + + public UserSecurityContext(Principal principal, Authentication authentication, + String scheme) { + this(principal, authentication, scheme, false); + } /** - * Constructs a security context from an instance of {@link User} + * Constructs a security context from an instance of Principal. * - * @param user the user - * @param the related {@link Authentication} + * @param principal the principal + * @param authentication the related {@link Authentication} * @param authenticationScheme the scheme that was used to authenticate the user, e.g. "Basic" + * @param secure determine if request was done over secure channel - ie. HTTPs. */ - public UserSecurityContext(User user, Authentication authentication, String authenticationScheme) { - this.user = user; + public UserSecurityContext(Principal principal, Authentication authentication, String authenticationScheme, boolean secure) { this.authentication = authentication; + this.principal = principal; this.authenticationScheme = authenticationScheme; + this.secure = secure; } @Override public Principal getUserPrincipal() { - return user; + return principal; } @Override public boolean isUserInRole(@Nullable String role) { - return user.getRoles().contains(role); + return authentication.getRoles().contains(role); } @Override diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserSessionDTO.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserSessionDTO.java similarity index 94% rename from bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserSessionDTO.java rename to bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserSessionDTO.java index 07888c6ba..f0166c7c6 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/UserSessionDTO.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/UserSessionDTO.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth.local.internal; import java.util.Date; diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/pom.xml b/bundles/org.opensmarthouse.core.io.rest.auth/pom.xml index 739011eda..a60f66de1 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/pom.xml +++ b/bundles/org.opensmarthouse.core.io.rest.auth/pom.xml @@ -39,6 +39,17 @@ org.bitbucket.b_c jose4j + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.http.facade + 0.9.2-SNAPSHOT + compile + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.auth + compile + diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthenticationSecurityContext.java b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AuthenticationSecurityContext.java similarity index 89% rename from bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthenticationSecurityContext.java rename to bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AuthenticationSecurityContext.java index 28033bc76..e4b80aa70 100644 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthenticationSecurityContext.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/AuthenticationSecurityContext.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.io.rest.auth.internal; +package org.openhab.core.io.rest.auth; import javax.ws.rs.core.SecurityContext; @@ -27,5 +27,5 @@ public interface AuthenticationSecurityContext extends SecurityContext { * * @return the authentication instance */ - public Authentication getAuthentication(); + Authentication getAuthentication(); } diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/ApplicationSecurityContext.java b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/ApplicationSecurityContext.java new file mode 100755 index 000000000..20474b938 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/ApplicationSecurityContext.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.auth.internal; + +import java.security.Principal; +import javax.ws.rs.core.SecurityContext; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.auth.Authentication; +import org.openhab.core.io.rest.auth.AuthenticationSecurityContext; + +@NonNullByDefault +public class ApplicationSecurityContext implements AuthenticationSecurityContext { + + private Authentication authentication; + private final boolean secure; + private final String scheme; + + public ApplicationSecurityContext(Authentication authentication, boolean secure, String scheme) { + this.authentication = authentication; + this.secure = secure; + this.scheme = scheme; + } + + @Override + public Principal getUserPrincipal() { + return new Principal() { + @Override + public String getName() { + return authentication.getUsername(); + } + }; + } + + @Override + public boolean isUserInRole(@Nullable String role) { + return authentication.getRoles().contains(role); + } + + @Override + public boolean isSecure() { + return secure; + } + + @Override + public String getAuthenticationScheme() { + return scheme; + } + + @Override + public Authentication getAuthentication() { + return authentication; + } +} diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java index b7d351b26..537bf9b4a 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthFilter.java @@ -13,34 +13,35 @@ package org.openhab.core.io.rest.auth.internal; import java.io.IOException; -import java.util.Base64; +import java.util.List; import java.util.Map; - +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Priority; import javax.ws.rs.Priorities; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.PreMatching; -import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.ext.Provider; - import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.auth.Authentication; import org.openhab.core.auth.AuthenticationException; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserApiTokenCredentials; -import org.openhab.core.auth.UserRegistry; -import org.openhab.core.auth.UsernamePasswordCredentials; +import org.openhab.core.auth.AuthenticationManager; +import org.openhab.core.auth.AuthenticationResult; +import org.openhab.core.auth.Credentials; import org.openhab.core.config.core.ConfigurableService; -import org.openhab.core.io.rest.JSONResponse; +import org.openhab.core.io.auth.CredentialsExtractor; +import org.openhab.core.io.http.facade.HttpRequestDelegate; import org.openhab.core.io.rest.RESTConstants; import org.osgi.framework.Constants; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Modified; import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsExtension; @@ -65,21 +66,15 @@ public class AuthFilter implements ContainerRequestFilter { private final Logger logger = LoggerFactory.getLogger(AuthFilter.class); - private static final String ALT_AUTH_HEADER = "X-OPENHAB-TOKEN"; - private static final String API_TOKEN_PREFIX = "oh."; - protected static final String CONFIG_URI = "system:restauth"; - private static final String CONFIG_ALLOW_BASIC_AUTH = "allowBasicAuth"; - private static final String CONFIG_IMPLICIT_USER_ROLE = "implicitUserRole"; - private boolean allowBasicAuth = false; - private boolean implicitUserRole = true; + private boolean enabled = true; @Reference - private JwtHelper jwtHelper; + private AuthenticationManager authenticationManager; + + private List> extractors = new CopyOnWriteArrayList<>(); - @Reference - private UserRegistry userRegistry; @Activate protected void activate(Map config) { @@ -88,91 +83,56 @@ protected void activate(Map config) { @Modified protected void modified(@Nullable Map properties) { - if (properties != null) { - Object value = properties.get(CONFIG_ALLOW_BASIC_AUTH); - allowBasicAuth = value != null && "true".equals(value.toString()); - value = properties.get(CONFIG_IMPLICIT_USER_ROLE); - implicitUserRole = value == null || !"false".equals(value.toString()); - } - } - private SecurityContext authenticateBearerToken(String token) throws AuthenticationException { - if (token.startsWith(API_TOKEN_PREFIX)) { - UserApiTokenCredentials credentials = new UserApiTokenCredentials(token); - Authentication auth = userRegistry.authenticate(credentials); - User user = userRegistry.get(auth.getUsername()); - if (user == null) { - throw new AuthenticationException("User not found in registry"); - } - return new UserSecurityContext(user, auth, "ApiToken"); - } else { - Authentication auth = jwtHelper.verifyAndParseJwtAccessToken(token); - return new JwtSecurityContext(auth); - } - } - - private SecurityContext authenticateUsernamePassword(String username, String password) - throws AuthenticationException { - UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password); - Authentication auth = userRegistry.authenticate(credentials); - User user = userRegistry.get(auth.getUsername()); - if (user == null) { - throw new AuthenticationException("User not found in registry"); - } - return new UserSecurityContext(user, auth, "Basic"); } @Override public void filter(ContainerRequestContext requestContext) throws IOException { - try { - String altTokenHeader = requestContext.getHeaderString(ALT_AUTH_HEADER); - if (altTokenHeader != null) { - requestContext.setSecurityContext(authenticateBearerToken(altTokenHeader)); + if (this.enabled) { + if (authenticationManager == null) { + Response response = Response.status(Status.UNAUTHORIZED).entity("Failed to authenticate request.").build(); + requestContext.abortWith(response); return; } - String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); - if (authHeader != null) { - String[] authParts = authHeader.split(" "); - if (authParts.length == 2) { - if ("Bearer".equalsIgnoreCase(authParts[0])) { - requestContext.setSecurityContext(authenticateBearerToken(authParts[1])); + int found = 0, failed = 0; + for (CredentialsExtractor extractor : extractors) { + Optional extracted = extractor.retrieveCredentials(new JaxRsRequestDelegate(requestContext)); + if (extracted.isPresent()) { + found++; + Credentials credentials = extracted.get(); + try { + AuthenticationResult authentication = authenticationManager.authenticate(credentials); + requestContext.setSecurityContext(new ApplicationSecurityContext(authentication.getAuthentication(), false, authentication.getScheme())); return; - } else if ("Basic".equalsIgnoreCase(authParts[0])) { - try { - String[] decodedCredentials = new String(Base64.getDecoder().decode(authParts[1]), "UTF-8") - .split(":"); - if (decodedCredentials.length > 2) { - throw new AuthenticationException("Invalid Basic authentication credential format"); - } - switch (decodedCredentials.length) { - case 1: - requestContext.setSecurityContext(authenticateBearerToken(decodedCredentials[0])); - break; - case 2: - if (!allowBasicAuth) { - throw new AuthenticationException( - "Basic authentication with username/password is not allowed"); - } - requestContext.setSecurityContext( - authenticateUsernamePassword(decodedCredentials[0], decodedCredentials[1])); - } - - return; - } catch (AuthenticationException e) { - throw new AuthenticationException("Invalid Basic authentication credentials", e); + } catch (AuthenticationException e) { + failed++; + if (logger.isDebugEnabled()) { + logger.debug("Failed to authenticate using credentials {}", credentials, e); + } else { + logger.info("Failed to authenticate using credentials {}", credentials); } } } } - if (implicitUserRole) { + if (true) { requestContext.setSecurityContext(new AnonymousUserSecurityContext()); + return; } - } catch (AuthenticationException e) { - logger.warn("Unauthorized API request: {}", e.getMessage()); - requestContext.abortWith(JSONResponse.createErrorResponse(Status.UNAUTHORIZED, "Invalid credentials")); + Response response = Response.status(Status.UNAUTHORIZED).entity("Could not authenticate request. Found " + found + + " credentials in request out of which " + failed + " were invalid").build(); + requestContext.abortWith(response); } } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, target = "(context=org.openhab.core.io.http.facade.HttpRequestDelegate)") + public void addCredentialsExtractor(CredentialsExtractor extractor) { + this.extractors.add(extractor); + } + + public void removeCredentialsExtractor(CredentialsExtractor extractor) { + this.extractors.remove(extractor); + } } \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JaxRsRequestDelegate.java b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JaxRsRequestDelegate.java new file mode 100644 index 000000000..1640e5e0a --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JaxRsRequestDelegate.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.auth.internal; + +import java.util.Optional; +import javax.ws.rs.container.ContainerRequestContext; +import org.openhab.core.io.http.facade.HttpRequestDelegate; + +/** + * JAX-RS request wrapper. + * + * @author Łukasz Dywicki - Initial contribution + */ +public class JaxRsRequestDelegate implements HttpRequestDelegate { + + private final ContainerRequestContext request; + + public JaxRsRequestDelegate(ContainerRequestContext request) { + this.request = request; + } + + @Override + public Optional getHeader(String headerName) { + return Optional.ofNullable(request.getHeaderString(headerName)); + } + +} diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtSecurityContext.java b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtSecurityContext.java index ae47ec7ba..5e87692de 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtSecurityContext.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JwtSecurityContext.java @@ -19,7 +19,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.auth.Authentication; -import org.openhab.core.auth.GenericUser; +import org.openhab.core.io.rest.auth.AuthenticationSecurityContext; /** * This {@link SecurityContext} contains information about a user, roles and authorizations granted to a client as @@ -38,7 +38,12 @@ public JwtSecurityContext(Authentication authentication) { @Override public Principal getUserPrincipal() { - return new GenericUser(authentication.getUsername()); + return new Principal() { + @Override + public String getName() { + return authentication.getUsername(); + } + }; } @Override diff --git a/bundles/org.opensmarthouse.core.io.transport.mqtt/pom.xml b/bundles/org.opensmarthouse.core.io.transport.mqtt/pom.xml index 27504b049..b1adc3ff4 100755 --- a/bundles/org.opensmarthouse.core.io.transport.mqtt/pom.xml +++ b/bundles/org.opensmarthouse.core.io.transport.mqtt/pom.xml @@ -16,7 +16,6 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.config - ${project.version} com.hivemq @@ -30,7 +29,6 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.test - ${project.version} test diff --git a/bundles/org.opensmarthouse.core.karaf.jaas/NOTICE b/bundles/org.opensmarthouse.core.karaf.jaas/NOTICE new file mode 100755 index 000000000..3e53bd427 --- /dev/null +++ b/bundles/org.opensmarthouse.core.karaf.jaas/NOTICE @@ -0,0 +1,18 @@ +This content is produced and maintained by the OpenSmartHouse project. + +* Project home: https://opensmarthouse.org + +Elements of this bundle are copyright to the following -: + +* The Eclipse Foundation, having come from the Eclipse SmartHome project +* The openHAB project + +== Declared Project Licenses + +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/. + +== Source Code + +https://github.com/opensmarthouse/opensmarthouse-core diff --git a/bundles/org.opensmarthouse.core.karaf.jaas/pom.xml b/bundles/org.opensmarthouse.core.karaf.jaas/pom.xml new file mode 100755 index 000000000..48b9f9412 --- /dev/null +++ b/bundles/org.opensmarthouse.core.karaf.jaas/pom.xml @@ -0,0 +1,36 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.karaf.jaas + + OpenSmartHouse Core | Bundles | Karaf JAAS Integration + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.local + + + + org.apache.karaf.jaas + org.apache.karaf.jaas.modules + ${karaf.compile.version} + provided + + + org.apache.karaf.jaas + org.apache.karaf.jaas.boot + ${karaf.compile.version} + provided + + + + diff --git a/bundles/org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserBackingEngine.java b/bundles/org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal/ManagedUserBackingEngine.java similarity index 95% rename from bundles/org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserBackingEngine.java rename to bundles/org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal/ManagedUserBackingEngine.java index be5a78da1..dbd54f9a1 100755 --- a/bundles/org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserBackingEngine.java +++ b/bundles/org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal/ManagedUserBackingEngine.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.karaf.internal.jaas; +package org.openhab.core.karaf.jaas.internal; import java.security.Principal; import java.util.Collections; @@ -24,10 +24,10 @@ import org.apache.karaf.jaas.boot.principal.RolePrincipal; import org.apache.karaf.jaas.boot.principal.UserPrincipal; import org.apache.karaf.jaas.modules.BackingEngine; -import org.openhab.core.auth.ManagedUser; +import org.openhab.core.auth.local.ManagedUser; import org.openhab.core.auth.Role; -import org.openhab.core.auth.User; -import org.openhab.core.auth.UserRegistry; +import org.openhab.core.auth.local.User; +import org.openhab.core.auth.local.UserRegistry; /** * A Karaf backing engine for the {@link UserRegistry} diff --git a/bundles/org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserBackingEngineFactory.java b/bundles/org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal/ManagedUserBackingEngineFactory.java similarity index 93% rename from bundles/org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserBackingEngineFactory.java rename to bundles/org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal/ManagedUserBackingEngineFactory.java index 69c7d659d..7224764ed 100755 --- a/bundles/org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserBackingEngineFactory.java +++ b/bundles/org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal/ManagedUserBackingEngineFactory.java @@ -10,13 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.karaf.internal.jaas; +package org.openhab.core.karaf.jaas.internal; import java.util.Map; import org.apache.karaf.jaas.modules.BackingEngine; import org.apache.karaf.jaas.modules.BackingEngineFactory; -import org.openhab.core.auth.UserRegistry; +import org.openhab.core.auth.local.UserRegistry; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; diff --git a/bundles/org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserRealm.java b/bundles/org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal/ManagedUserRealm.java similarity index 91% rename from bundles/org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserRealm.java rename to bundles/org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal/ManagedUserRealm.java index 6552eb910..202e25f67 100755 --- a/bundles/org.opensmarthouse.core.karaf/src/main/java/org/openhab/core/karaf/internal/jaas/ManagedUserRealm.java +++ b/bundles/org.opensmarthouse.core.karaf.jaas/src/main/java/org/openhab/core/karaf/jaas/internal/ManagedUserRealm.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.karaf.internal.jaas; +package org.openhab.core.karaf.jaas.internal; import java.util.HashMap; import java.util.Map; @@ -20,8 +20,7 @@ import org.apache.karaf.jaas.boot.ProxyLoginModule; import org.apache.karaf.jaas.config.JaasRealm; -import org.apache.karaf.shell.api.action.lifecycle.Service; -import org.openhab.core.auth.UserRegistry; +import org.openhab.core.auth.local.UserRegistry; import org.osgi.service.component.annotations.Component; /** @@ -30,7 +29,6 @@ * @author Yannick Schaus - initial contribution */ @Component(service = JaasRealm.class) -@Service public class ManagedUserRealm implements JaasRealm { public static final String REALM_NAME = "openhab"; diff --git a/bundles/org.opensmarthouse.core.karaf/pom.xml b/bundles/org.opensmarthouse.core.karaf/pom.xml index 2e52ee457..1c4f105b2 100755 --- a/bundles/org.opensmarthouse.core.karaf/pom.xml +++ b/bundles/org.opensmarthouse.core.karaf/pom.xml @@ -49,12 +49,6 @@ ${karaf.compile.version} provided - - org.apache.karaf.jaas - org.apache.karaf.jaas.modules - ${karaf.compile.version} - provided - diff --git a/bundles/pom.xml b/bundles/pom.xml index 7a0f0244a..331028cee 100755 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -21,9 +21,16 @@ org.opensmarthouse.core.audio.core org.opensmarthouse.core.auth org.opensmarthouse.core.auth.core + org.opensmarthouse.core.auth.apitoken + org.opensmarthouse.core.auth.apitoken.provider org.opensmarthouse.core.auth.jaas + org.opensmarthouse.core.auth.jwt + org.opensmarthouse.core.auth.jwt.provider + org.opensmarthouse.core.auth.local + org.opensmarthouse.core.auth.local.provider org.opensmarthouse.core.auth.oauth2client org.opensmarthouse.core.auth.oauth2client.core + org.opensmarthouse.core.auth.password org.opensmarthouse.core.automation org.opensmarthouse.core.automation.module.media org.opensmarthouse.core.automation.module.script @@ -55,6 +62,7 @@ org.opensmarthouse.core.i18n org.opensmarthouse.core.i18n.core org.opensmarthouse.core.id + org.opensmarthouse.core.io.auth org.opensmarthouse.core.io.bin2json org.opensmarthouse.core.io.console org.opensmarthouse.core.io.console.eclipse @@ -63,13 +71,17 @@ org.opensmarthouse.core.io.console.user org.opensmarthouse.core.io.http org.opensmarthouse.core.io.http.auth + org.opensmarthouse.core.io.http.auth.apitoken org.opensmarthouse.core.io.http.auth.basic + org.opensmarthouse.core.io.http.auth.jwt + org.opensmarthouse.core.io.http.facade org.opensmarthouse.core.io.jetty.certificate org.opensmarthouse.core.io.monitor org.opensmarthouse.core.io.net org.opensmarthouse.core.io.rest org.opensmarthouse.core.io.rest.audio org.opensmarthouse.core.io.rest.auth + org.opensmarthouse.core.io.rest.auth.local org.opensmarthouse.core.io.rest.binding org.opensmarthouse.core.io.rest.channel org.opensmarthouse.core.io.rest.config @@ -101,6 +113,7 @@ org.opensmarthouse.core.item org.opensmarthouse.core.item.core org.opensmarthouse.core.karaf + org.opensmarthouse.core.karaf.jaas org.opensmarthouse.core.library.dimension org.opensmarthouse.core.library.item org.opensmarthouse.core.library.type diff --git a/features/karaf/opensmarthouse-core/src/main/feature/feature.xml b/features/karaf/opensmarthouse-core/src/main/feature/feature.xml index 7c1158beb..a098681a6 100755 --- a/features/karaf/opensmarthouse-core/src/main/feature/feature.xml +++ b/features/karaf/opensmarthouse-core/src/main/feature/feature.xml @@ -251,6 +251,16 @@ mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth/${project.version} + + opensmarthouse-core-auth + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.apitoken/${project.version} + + + + opensmarthouse-core-auth-apitoken + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.apitoken/${project.version} + + opensmarthouse-core-auth opensmarthouse-core-storage @@ -259,9 +269,33 @@ opensmarthouse-core-auth + opensmarthouse-core-auth-local mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.jaas/${project.version} + + opensmarthouse-core-auth + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.jwt/${project.version} + + + + opensmarthouse-tp;filter:="(feature=jose4j)" + opensmarthouse-tp-jose4j + opensmarthouse-core-auth-jwt + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.jwt.provider/${project.version} + + + + opensmarthouse-core-auth + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.local/${project.version} + + + + opensmarthouse-core-auth-local + opensmarthouse-core-auth-password + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.local.provider/${project.version} + + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.oauth2client/${project.version} @@ -275,6 +309,11 @@ mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.oauth2client.core/${project.version} + + opensmarthouse-core-auth + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.password/${project.version} + + opensmarthouse-tp-gson opensmarthouse-core @@ -445,6 +484,11 @@ mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.console.karaf/${project.version} + + opensmarthouse-core-auth + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.auth/${project.version} + + http mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.http/${project.version} @@ -452,12 +496,21 @@ opensmarthouse-tp-jax-rs + opensmarthouse-core-io-auth + opensmarthouse-core-io-http-facade opensmarthouse-core-io-http opensmarthouse-core-i18n opensmarthouse-core-auth-jaas mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.http.auth/${project.version} + + opensmarthouse-core-base + opensmarthouse-core-auth-apitoken + opensmarthouse-core-io-http-auth + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.http.auth.apitoken/${project.version} + + opensmarthouse-core-base mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.http.auth.basic/${project.version} @@ -468,13 +521,36 @@ + + opensmarthouse-core-base + opensmarthouse-core-io-http-auth + opensmarthouse-core-auth-jwt + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.http.auth.jwt/${project.version} + + + + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.http.facade/${project.version} + + opensmarthouse-core-auth-core + opensmarthouse-core-io-auth + opensmarthouse-core-io-http-facade opensmarthouse-core-io-rest opensmarthouse-core-config mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.rest.auth/${project.version} + + + + opensmarthouse-tp-jax-rs opensmarthouse-tp;filter:="(feature=jose4j)" opensmarthouse-tp-jose4j + + opensmarthouse-core-io-http-auth-jwt + opensmarthouse-core-io-rest-auth + opensmarthouse-core-auth-jwt + opensmarthouse-core-auth-local-provider + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.rest.auth.local/${project.version} @@ -824,11 +900,17 @@ opensmarthouse-tp-commons-net opensmarthouse-core-base opensmarthouse-core-auth-jaas + opensmarthouse-core-auth-jwt + opensmarthouse-core-auth-jwt-provider + opensmarthouse-core-auth-password + opensmarthouse-core-auth-local + opensmarthouse-core-auth-local-provider opensmarthouse-core-ephemeris opensmarthouse-core-io-console-karaf opensmarthouse-core-io-http opensmarthouse-core-io-http-auth opensmarthouse-core-io-rest-auth + opensmarthouse-core-io-rest-auth-local opensmarthouse-core-io-rest-audio opensmarthouse-core-io-rest-extension opensmarthouse-core-io-rest-sitemap From ee3494ce183e379aaa3bdbb00da9496346518670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Thu, 4 Feb 2021 00:36:44 +0100 Subject: [PATCH 02/14] Proof of concept support for authorization. Indirectly related to opensmarthouse/opensmarthouse-core#16. --- .../auth/AuthorizationManagerImpl.java | 78 +++++++++++++++++++ .../openhab/core/auth/local/GenericUser.java | 17 +++- .../openhab/core/auth/local/ManagedUser.java | 10 ++- .../org/openhab/core/auth/local/User.java | 9 ++- .../org/openhab/core/auth/Authentication.java | 19 ++--- .../auth/AuthenticationContextHolder.java | 30 +++++++ .../core/auth/AuthorizationException.java | 22 ++++++ .../core/auth/AuthorizationManager.java | 26 +++++++ .../auth/AuthorizationManagerFilters.java | 44 +++++++++++ .../MutableAuthenticationContextHolder.java | 24 ++++++ .../openhab/core/auth/NamedPermission.java | 52 +++++++++++++ .../org/openhab/core/auth/Permission.java | 19 +++++ .../core/auth/PermissionEvaluator.java | 32 ++++++++ .../org/openhab/core/auth/Permissions.java | 29 +++++++ .../auth/holder/ThreadLocalContextHolder.java | 46 +++++++++++ .../rest/auth/local/internal/JwtHelper.java | 12 ++- .../io/rest/auth/internal/AuthzFilter.java | 66 ++++++++++++++++ .../pom.xml | 4 + .../rest/core/internal/item/ItemResource.java | 35 ++++++++- .../pom.xml | 4 + .../persistence/PersistenceResource.java | 41 +++++++++- .../openhab/core/io/rest/sse/SseResource.java | 39 ++++++++-- .../io/rest/sse/internal/SseSinkItemInfo.java | 19 +++++ .../rest/sse/internal/SseSinkTopicInfo.java | 18 ++++- .../core/io/rest/Stream2JSONInputStream.java | 15 +++- .../org.opensmarthouse.core.item.auth/pom.xml | 26 +++++++ .../internal/ItemPermissionEvaluator.java | 67 ++++++++++++++++ bundles/pom.xml | 1 + .../src/main/feature/feature.xml | 9 +++ 29 files changed, 786 insertions(+), 27 deletions(-) create mode 100644 bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/AuthorizationManagerImpl.java create mode 100644 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationContextHolder.java create mode 100644 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationException.java create mode 100755 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationManager.java create mode 100644 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationManagerFilters.java create mode 100644 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/MutableAuthenticationContextHolder.java create mode 100644 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/NamedPermission.java create mode 100644 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Permission.java create mode 100644 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/PermissionEvaluator.java create mode 100644 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Permissions.java create mode 100644 bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/holder/ThreadLocalContextHolder.java create mode 100644 bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthzFilter.java create mode 100755 bundles/org.opensmarthouse.core.item.auth/pom.xml create mode 100644 bundles/org.opensmarthouse.core.item.auth/src/main/java/org/openhab/core/item/auth/internal/ItemPermissionEvaluator.java diff --git a/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/AuthorizationManagerImpl.java b/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/AuthorizationManagerImpl.java new file mode 100644 index 000000000..68a9fe9b8 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/AuthorizationManagerImpl.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.internal.auth; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.AuthorizationManager; +import org.openhab.core.auth.Permission; +import org.openhab.core.auth.PermissionEvaluator; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component +public class AuthorizationManagerImpl implements AuthorizationManager { + + private final Logger logger = LoggerFactory.getLogger(AuthorizationManagerImpl.class); + private List> evaluators = new CopyOnWriteArrayList<>(); + + @Override + public boolean hasPermission(Permission permission, T object, Authentication authentication) { + if (authentication == null) { + return false; + } + + for (PermissionEvaluator evaluator : evaluators) { + if (evaluator.supports(object.getClass(), permission)) { + if (!evaluator.hasPermission(permission, authentication, object)) { + logger.trace("Access denied to object {} ({}) for authentication {} reported by evaluator {}", + object, object.getClass().getName(), authentication, evaluator); + return false; + } + } + } + return true; + } + + public boolean hasPermission(Permission permission, String id, Class type, Authentication authentication) { + if (authentication == null) { + return false; + } + + for (PermissionEvaluator evaluator : evaluators) { + if (evaluator.supports(type, permission)) { + if (!evaluator.hasPermission(permission, authentication, (Class) type, id)) { + logger.trace("Access denied to object with id {} ({}) for authentication {} reported by evaluator {}", + id, type.getName(), authentication, evaluator); + return false; + } + } + } + return true; + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void addPermissionEvaluator(PermissionEvaluator evaluator) { + this.evaluators.add(evaluator); + } + + public void removePermissionEvaluator(PermissionEvaluator evaluator) { + this.evaluators.remove(evaluator); + } + +} diff --git a/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/GenericUser.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/GenericUser.java index ffac37d86..36c414ee2 100755 --- a/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/GenericUser.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/GenericUser.java @@ -12,6 +12,7 @@ */ package org.openhab.core.auth.local; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -28,16 +29,23 @@ public class GenericUser implements User { private String name; private Set roles; + private Set permissions; /** * Constructs a user attributed with a set of roles. * * @param name the username (account name) * @param roles the roles attributed to this user + * @param permissions the items user has access to */ - public GenericUser(String name, Set roles) { + public GenericUser(String name, Set roles, Set permissions) { this.name = name; this.roles = roles; + this.permissions = permissions; + } + + public GenericUser(String name, Set roles) { + this(name, roles, Collections.emptySet()); } /** @@ -64,7 +72,12 @@ public Set getRoles() { return roles; } + @Override + public Set getPermissions() { + return permissions; + } + public String toString() { - return name + " (" + roles + ")"; + return name + " (" + roles + " " + permissions + ")"; } } diff --git a/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/ManagedUser.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/ManagedUser.java index a429d5194..42d3e22d9 100755 --- a/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/ManagedUser.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/ManagedUser.java @@ -19,7 +19,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.auth.local.User; /** * A {@link User} sourced from a managed {@link UserProvider}. @@ -33,6 +32,7 @@ public class ManagedUser implements User { private String passwordHash; private String passwordSalt; private Set roles = new HashSet<>(); + private Set permissions = new HashSet<>(); private @Nullable PendingToken pendingToken = null; private List sessions = new ArrayList<>(); private List apiTokens = new ArrayList<>(); @@ -120,6 +120,14 @@ public void setRoles(Set roles) { this.roles = roles; } + public void setPermissions(Set permissions) { + this.permissions = permissions; + } + + public Set getPermissions() { + return permissions; + } + /** * Gets the pending token information for this user, if any. * diff --git a/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/User.java b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/User.java index fd78c1a44..2efd3eed7 100755 --- a/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/User.java +++ b/bundles/org.opensmarthouse.core.auth.local/src/main/java/org/openhab/core/auth/local/User.java @@ -33,5 +33,12 @@ public interface User extends Principal, Identifiable { * @see Role * @return role attributed to the user */ - public Set getRoles(); + Set getRoles(); + + /** + * Returns items which given user have access to. + * + * @return Item identifiers user has access to. + */ + Set getPermissions(); } diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java index 9ebe3c0be..e94de3aef 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java @@ -29,7 +29,7 @@ public class Authentication { private String username; private Set roles; private String scope; - private Set items; + private Set permissions; /** * no-args constructor required by gson @@ -38,7 +38,7 @@ protected Authentication() { this.username = null; this.roles = null; this.scope = null; - this.items = null; + this.permissions = null; } /** @@ -59,7 +59,7 @@ public Authentication(String username, String... roles) { * @param scope a scope this authentication is valid for */ public Authentication(String username, String[] roles, String scope) { - this(username, roles, scope, new String[] {"*"}); + this(username, roles, scope, new String[] {"*:*:*"}); } /** @@ -68,12 +68,13 @@ public Authentication(String username, String[] roles, String scope) { * @param username name of the user associated to this authentication instance * @param roles a variable list of roles that the user possesses. * @param scope a scope this authentication is valid for + * @param permissions permissions associated with authentication */ - public Authentication(String username, String[] roles, String scope, String[] items) { + public Authentication(String username, String[] roles, String scope, String[] permissions) { this.username = username; this.roles = Set.of(roles); this.scope = scope; - this.items = Set.of(items); + this.permissions = Set.of(permissions); } /** @@ -104,11 +105,11 @@ public String getScope() { } /** - * Retrieves the items this authentication is valid for. + * Retrieves the permissions this authentication has. * - * @return an set of items (might be empty) + * @return an set of permissions (might be empty) */ - public Set getItems() { - return items; + public Set getPermissions() { + return permissions; } } diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationContextHolder.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationContextHolder.java new file mode 100644 index 000000000..cc3ce07ef --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthenticationContextHolder.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth; + +import java.util.Optional; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The access layer to an authentication context during execution of an action. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public interface AuthenticationContextHolder { + + @Nullable + Authentication getAuthentication(); + + Optional fetchAuthentication(); + +} diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationException.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationException.java new file mode 100644 index 000000000..652166ec3 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationException.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth; + +/** + * The access layer to an authentication context during execution of an action. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class AuthorizationException extends Exception { + +} diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationManager.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationManager.java new file mode 100755 index 000000000..3e3b3ad14 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationManager.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth; + +/** + * Authorization manager is main entry point for security checks. + * + * @author Łukasz Dywicki - Initial contribution + */ +public interface AuthorizationManager { + + boolean hasPermission(Permission permission, T object, Authentication authentication); + + boolean hasPermission(Permission permission, String id, Class type, Authentication authentication); + +} diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationManagerFilters.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationManagerFilters.java new file mode 100644 index 000000000..101a4c6d7 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/AuthorizationManagerFilters.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +/** + * Helpers for filtering out collections and returning trimmed data. + * + * @author Łukasz Dywicki - Initial contribution + */ +public class AuthorizationManagerFilters { + + public static List filter(Authentication authentication, BiFunction check, List values) { + return values.stream().filter(value -> check.apply(authentication, value)) + .collect(Collectors.toList()); + } + + public static Set filter(Authentication authentication, BiFunction check, Set values) { + return values.stream().filter(value -> check.apply(authentication, value)) + .collect(Collectors.toSet()); + } + + public static Map filter(Authentication authentication, BiFunction, Boolean> check, Map values) { + return values.entrySet().stream().filter(entry -> check.apply(authentication, entry)) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + } + +} diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/MutableAuthenticationContextHolder.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/MutableAuthenticationContextHolder.java new file mode 100644 index 000000000..984ec0d36 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/MutableAuthenticationContextHolder.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth; + +/** + * Writeable holder for authentication context which should be used early in processing chain. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public interface MutableAuthenticationContextHolder { + + void setAuthentication(Authentication authentication); + +} diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/NamedPermission.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/NamedPermission.java new file mode 100644 index 000000000..2818820d6 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/NamedPermission.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth; + +import java.util.Objects; + +/** + * Simplest possible implementation of permission. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class NamedPermission implements Permission { + + private final String code; + + public NamedPermission(String code) { + this.code = code; + } + + @Override + public String getCode() { + return code; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Permission)) { + return false; + } + Permission that = (Permission) o; + return Objects.equals(getCode(), that.getCode()); + } + + @Override + public int hashCode() { + return Objects.hash(getCode()); + } + +} diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Permission.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Permission.java new file mode 100644 index 000000000..2514845d5 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Permission.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth; + +public interface Permission { + + String getCode(); + +} diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/PermissionEvaluator.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/PermissionEvaluator.java new file mode 100644 index 000000000..09e6db0fa --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/PermissionEvaluator.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth; + +public interface PermissionEvaluator { + + boolean supports(Class type, Permission permission); + + /** + * Decide if given value is accessible in given authentication context or not. + * + * @param permission Permission to be evaluated. + * @param authentication Authentication to verify. + * @param value Entity or resource to check. + * @return True if user can access given object. + */ + boolean hasPermission(Permission permission, Authentication authentication, T value); + + // same as above but uses type + id instead of entire object + boolean hasPermission(Permission permission, Authentication authentication, Class type, String id); + +} diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Permissions.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Permissions.java new file mode 100644 index 000000000..919471377 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Permissions.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth; + +/** + * Common permission definitions. + * + * @author Łukasz Dywicki - Initial contribution + */ +public interface Permissions { + + Permission ALL = new NamedPermission("*"); + Permission READ = new NamedPermission("read"); + Permission STATE = new NamedPermission("state"); + Permission COMMAND = new NamedPermission("command"); + Permission MANAGE = new NamedPermission("manage"); + + Permission PERSISTENCE = new NamedPermission("persistence"); +} diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/holder/ThreadLocalContextHolder.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/holder/ThreadLocalContextHolder.java new file mode 100644 index 000000000..81d8fa513 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/holder/ThreadLocalContextHolder.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth.holder; + +import java.util.Optional; +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.AuthenticationContextHolder; +import org.openhab.core.auth.MutableAuthenticationContextHolder; +import org.osgi.service.component.annotations.Component; + +/** + * Implementation of authentication context holder based on {@link ThreadLocal}. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component(service = {AuthenticationContextHolder.class, MutableAuthenticationContextHolder.class}) +public class ThreadLocalContextHolder implements AuthenticationContextHolder, MutableAuthenticationContextHolder { + + private final ThreadLocal authentication = new ThreadLocal<>(); + + @Override + public Authentication getAuthentication() { + return authentication.get(); + } + + @Override + public Optional fetchAuthentication() { + return Optional.ofNullable(authentication.get()); + } + + // this one should be visible only to few + public void setAuthentication(Authentication authentication) { + this.authentication.set(authentication); + } + +} diff --git a/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/JwtHelper.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/JwtHelper.java index 5c41fcac7..f9731cda0 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/JwtHelper.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/JwtHelper.java @@ -117,7 +117,9 @@ public String getJwtAccessToken(User user, String clientId, String scope, int to jwtClaims.setClaim("client_id", clientId); jwtClaims.setClaim("scope", scope); jwtClaims.setStringListClaim("role", - new ArrayList<>(user.getRoles() != null ? user.getRoles() : Collections.emptySet())); + new ArrayList<>(user.getRoles() != null ? user.getRoles() : Collections.emptySet())); + jwtClaims.setStringListClaim("permissions", + new ArrayList<>(user.getPermissions() != null ? user.getPermissions() : Collections.emptySet())); JsonWebSignature jws = new JsonWebSignature(); jws.setPayload(jwtClaims.toJson()); @@ -149,10 +151,16 @@ public Authentication verifyAndParseJwtAccessToken(String jwt) throws Authentica JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt); String username = jwtClaims.getSubject(); List roles = jwtClaims.getStringListClaimValue("role"); + List permissions = jwtClaims.getStringListClaimValue("permissions"); String scope = jwtClaims.getStringClaimValue("scope"); - return new Authentication(username, roles.toArray(new String[roles.size()]), scope); + return new Authentication(username, toArray(roles), scope, toArray(permissions)); } catch (InvalidJwtException | MalformedClaimException e) { throw new AuthenticationException("Error while processing JWT token", e); } } + + protected String[] toArray(List entries) { + return entries.toArray(new String[entries.size()]); + } + } diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthzFilter.java b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthzFilter.java new file mode 100644 index 000000000..d99422031 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/AuthzFilter.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.auth.internal; + +import java.io.IOException; +import java.lang.reflect.Method; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.ext.Provider; +import org.openhab.core.auth.MutableAuthenticationContextHolder; +import org.openhab.core.io.rest.RESTConstants; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect; +import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsExtension; + +/** + * A propagation of security context through {@link org.openhab.core.auth.AuthenticationContextHolder}. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component +@JaxrsExtension +@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")") +@Priority(Priorities.AUTHORIZATION) +@Provider +public class AuthzFilter implements ContainerRequestFilter, ContainerResponseFilter { + + private final MutableAuthenticationContextHolder authenticationContextHolder; + + @Activate + public AuthzFilter(@Reference MutableAuthenticationContextHolder authenticationContextHolder) { + this.authenticationContextHolder = authenticationContextHolder; + } + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + SecurityContext securityContext = requestContext.getSecurityContext(); + if (securityContext instanceof ApplicationSecurityContext) { + authenticationContextHolder.setAuthentication(((ApplicationSecurityContext) securityContext).getAuthentication()); + } + } + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { + authenticationContextHolder.setAuthentication(null); + } + +} \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.io.rest.item/pom.xml b/bundles/org.opensmarthouse.core.io.rest.item/pom.xml index 256265fa2..71fc17bf8 100755 --- a/bundles/org.opensmarthouse.core.io.rest.item/pom.xml +++ b/bundles/org.opensmarthouse.core.io.rest.item/pom.xml @@ -51,6 +51,10 @@ org.osgi osgi.enroute.hamcrest.wrapper + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.rest.auth + diff --git a/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index 0c0b70e84..5aeee8b42 100755 --- a/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -47,6 +47,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.AuthenticationContextHolder; +import org.openhab.core.auth.AuthorizationManager; +import org.openhab.core.auth.Permissions; import org.openhab.core.auth.Role; import org.openhab.core.events.EventPublisher; import org.openhab.core.io.rest.DTOMapper; @@ -164,6 +168,8 @@ private static void respectForwarded(final UriBuilder uriBuilder, final @Context private final ManagedItemProvider managedItemProvider; private final MetadataRegistry metadataRegistry; private final MetadataSelectorMatcher metadataSelectorMatcher; + private final AuthorizationManager authorizationManager; + private final AuthenticationContextHolder authenticationContextHolder; @Activate public ItemResource(// @@ -174,7 +180,9 @@ public ItemResource(// final @Reference LocaleService localeService, // final @Reference ManagedItemProvider managedItemProvider, final @Reference MetadataRegistry metadataRegistry, - final @Reference MetadataSelectorMatcher metadataSelectorMatcher) { + final @Reference MetadataSelectorMatcher metadataSelectorMatcher, + final @Reference AuthorizationManager authorizationManager, + final @Reference AuthenticationContextHolder authenticationContextHolder) { this.dtoMapper = dtoMapper; this.eventPublisher = eventPublisher; this.itemBuilderFactory = itemBuilderFactory; @@ -183,6 +191,8 @@ public ItemResource(// this.managedItemProvider = managedItemProvider; this.metadataRegistry = metadataRegistry; this.metadataSelectorMatcher = metadataSelectorMatcher; + this.authorizationManager = authorizationManager; + this.authenticationContextHolder = authenticationContextHolder; } private UriBuilder uriBuilder(final UriInfo uriInfo, final HttpHeaders httpHeaders) { @@ -209,7 +219,9 @@ public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHead final UriBuilder uriBuilder = uriBuilder(uriInfo, httpHeaders); uriBuilder.path("{itemName}"); + final Authentication authentication = authenticationContextHolder.getAuthentication(); Stream itemStream = getItems(type, tags).stream() // + .filter(item -> authorizationManager.hasPermission(Permissions.READ, item, authentication)) .map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder, locale)) // .peek(dto -> addMetadata(dto, namespaces, null)) // .peek(dto -> dto.editable = isEditable(dto.name)); @@ -228,6 +240,11 @@ public Response getItemData(final @Context UriInfo uriInfo, final @Context HttpH @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, @QueryParam("metadata") @Parameter(description = "metadata selector") @Nullable String namespaceSelector, @PathParam("itemname") @Parameter(description = "item name") String itemname) { + + if (!authorizationManager.hasPermission(Permissions.READ, itemname, Item.class, authenticationContextHolder.getAuthentication())) { + return Response.status(Status.UNAUTHORIZED).build(); + } + final Locale locale = localeService.getLocale(language); final Set namespaces = splitAndFilterNamespaces(namespaceSelector, locale); @@ -262,6 +279,10 @@ private Set splitAndFilterNamespaces(@Nullable String namespaceSelector, @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class))), @ApiResponse(responseCode = "404", description = "Item not found") }) public Response getPlainItemState(@PathParam("itemname") @Parameter(description = "item name") String itemname) { + if (!authorizationManager.hasPermission(Permissions.STATE, itemname, Item.class, authenticationContextHolder.getAuthentication())) { + return Response.status(Status.UNAUTHORIZED).build(); + } + // get item Item item = getItem(itemname); @@ -287,6 +308,11 @@ public Response putItemState( @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, @PathParam("itemname") @Parameter(description = "item name") String itemname, @Parameter(description = "valid item state (e.g. ON, OFF)", required = true) String value) { + + if (!authorizationManager.hasPermission(Permissions.STATE, itemname, Item.class, authenticationContextHolder.getAuthentication())) { + return Response.status(Status.UNAUTHORIZED).build(); + } + final Locale locale = localeService.getLocale(language); // get Item @@ -321,6 +347,11 @@ public Response putItemState( @ApiResponse(responseCode = "400", description = "Item command null") }) public Response postItemCommand(@PathParam("itemname") @Parameter(description = "item name") String itemname, @Parameter(description = "valid item command (e.g. ON, OFF, UP, DOWN, REFRESH)", required = true) String value) { + + if (!authorizationManager.hasPermission(Permissions.COMMAND, itemname, Item.class, authenticationContextHolder.getAuthentication())) { + return Response.status(Status.UNAUTHORIZED).build(); + } + Item item = getItem(itemname); Command command = null; if (item != null) { @@ -718,7 +749,7 @@ private JsonObject buildStatusObject(String itemName, String status, @Nullable S /** * helper: Response to be sent to client if a Thing cannot be found * - * @param thingUID + * @param itemname * @return Response configured for 'item not found' */ private static Response getItemNotFoundResponse(String itemname) { diff --git a/bundles/org.opensmarthouse.core.io.rest.persistence/pom.xml b/bundles/org.opensmarthouse.core.io.rest.persistence/pom.xml index 6f18bd31f..9e7bc22f3 100755 --- a/bundles/org.opensmarthouse.core.io.rest.persistence/pom.xml +++ b/bundles/org.opensmarthouse.core.io.rest.persistence/pom.xml @@ -26,6 +26,10 @@ javax.annotation javax.annotation-api + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.rest.auth + diff --git a/bundles/org.opensmarthouse.core.io.rest.persistence/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java b/bundles/org.opensmarthouse.core.io.rest.persistence/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java index bb8b317e8..e6e193775 100755 --- a/bundles/org.opensmarthouse.core.io.rest.persistence/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java +++ b/bundles/org.opensmarthouse.core.io.rest.persistence/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Locale; +import java.util.Set; +import java.util.function.BiFunction; import javax.annotation.security.RolesAllowed; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -37,6 +39,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.AuthenticationContextHolder; +import org.openhab.core.auth.AuthorizationManager; +import org.openhab.core.auth.AuthorizationManagerFilters; +import org.openhab.core.auth.Permissions; import org.openhab.core.auth.Role; import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.io.rest.JSONResponse; @@ -53,6 +60,7 @@ import org.openhab.core.persistence.FilterCriteria.Ordering; import org.openhab.core.persistence.HistoricItem; import org.openhab.core.persistence.ModifiablePersistenceService; +import org.openhab.core.persistence.PersistenceItemInfo; import org.openhab.core.persistence.PersistenceService; import org.openhab.core.persistence.PersistenceServiceRegistry; import org.openhab.core.persistence.QueryablePersistenceService; @@ -115,17 +123,23 @@ public class PersistenceResource implements RESTResource { private final LocaleService localeService; private final PersistenceServiceRegistry persistenceServiceRegistry; private final TimeZoneProvider timeZoneProvider; + private final AuthorizationManager authorizationManager; + private final AuthenticationContextHolder authenticationContextHolder; @Activate public PersistenceResource( // final @Reference ItemRegistry itemRegistry, // final @Reference LocaleService localeService, final @Reference PersistenceServiceRegistry persistenceServiceRegistry, - final @Reference TimeZoneProvider timeZoneProvider) { + final @Reference TimeZoneProvider timeZoneProvider, + final @Reference AuthorizationManager authorizationManager, + final @Reference AuthenticationContextHolder authenticationContextHolder) { this.itemRegistry = itemRegistry; this.localeService = localeService; this.persistenceServiceRegistry = persistenceServiceRegistry; this.timeZoneProvider = timeZoneProvider; + this.authorizationManager = authorizationManager; + this.authenticationContextHolder = authenticationContextHolder; } @GET @@ -172,6 +186,10 @@ public Response httpGetPersistenceItemData(@Context HttpHeaders headers, @Parameter(description = "Page number of data to return. This parameter will enable paging.") @QueryParam("page") int pageNumber, @Parameter(description = "The length of each page.") @QueryParam("pagelength") int pageLength, @Parameter(description = "Gets one value before and after the requested period.") @QueryParam("boundary") boolean boundary) { + + if (!authorizationManager.hasPermission(Permissions.PERSISTENCE, itemName, Item.class, authenticationContextHolder.getAuthentication())) { + return Response.status(Status.UNAUTHORIZED).build(); + } return getItemHistoryDTO(serviceId, itemName, startTime, endTime, pageNumber, pageLength, boundary); } @@ -184,6 +202,7 @@ public Response httpGetPersistenceItemData(@Context HttpHeaders headers, @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))), @ApiResponse(responseCode = "400", description = "Invalid filter parameters"), @ApiResponse(responseCode = "404", description = "Unknown persistence service") }) + public Response httpDeletePersistenceServiceItem(@Context HttpHeaders headers, @Parameter(description = "Id of the persistence service.", required = true) @QueryParam("serviceId") String serviceId, @Parameter(description = "The item name.") @PathParam("itemname") String itemName, @@ -361,7 +380,7 @@ private Response getItemHistoryDTO(@Nullable String serviceId, String itemName, /** * Gets a list of persistence services currently configured in the system * - * @return list of persistence services as {@link ServiceBean} + * @return list of persistence services as {@link PersistenceServiceDTO} */ private List getPersistenceServiceList(Locale locale) { List dtoList = new ArrayList<>(); @@ -407,7 +426,9 @@ private Response getServiceItemList(@Nullable String serviceId) { QueryablePersistenceService qService = (QueryablePersistenceService) service; - return JSONResponse.createResponse(Status.OK, qService.getItemInfo(), ""); + Set itemInfo = AuthorizationManagerFilters.filter(authenticationContextHolder.getAuthentication(), + new AuthorizationCheck(authorizationManager), qService.getItemInfo()); + return JSONResponse.createResponse(Status.OK, itemInfo, ""); } private Response deletePersistenceItemData(@Nullable String serviceId, String itemName, @Nullable String timeBegin, @@ -508,4 +529,18 @@ private Response putItemState(@Nullable String serviceId, String itemName, Strin mService.store(item, Date.from(dateTime.toInstant()), state); return Response.status(Status.OK).build(); } + + static class AuthorizationCheck implements BiFunction { + + private final AuthorizationManager authorizationManager; + + AuthorizationCheck(AuthorizationManager authorizationManager) { + this.authorizationManager = authorizationManager; + } + + @Override + public Boolean apply(Authentication auth, PersistenceItemInfo info) { + return authorizationManager.hasPermission(Permissions.PERSISTENCE, info.getName(), Item.class, auth); + } + } } \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java b/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java index 6aee746e9..d86c19bff 100755 --- a/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java +++ b/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java @@ -12,16 +12,20 @@ */ package org.openhab.core.io.rest.sse; +import static org.openhab.core.io.rest.sse.internal.SseSinkItemInfo.canAccessItem; import static org.openhab.core.io.rest.sse.internal.SseSinkItemInfo.hasConnectionId; import static org.openhab.core.io.rest.sse.internal.SseSinkItemInfo.tracksItem; +import static org.openhab.core.io.rest.sse.internal.SseSinkTopicInfo.hasItemAccess; import static org.openhab.core.io.rest.sse.internal.SseSinkTopicInfo.matchesTopic; import java.io.IOException; +import java.util.LinkedHashSet; import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.function.Predicate; import javax.annotation.security.RolesAllowed; import javax.inject.Singleton; import javax.servlet.http.HttpServletResponse; @@ -41,6 +45,10 @@ import javax.ws.rs.sse.SseEventSink; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.auth.AuthenticationContextHolder; +import org.openhab.core.auth.AuthorizationManager; +import org.openhab.core.auth.Permission; +import org.openhab.core.auth.Permissions; import org.openhab.core.auth.Role; import org.openhab.core.events.Event; import org.openhab.core.io.rest.RESTConstants; @@ -52,6 +60,8 @@ import org.openhab.core.io.rest.sse.internal.SseSinkTopicInfo; import org.openhab.core.io.rest.sse.internal.dto.EventDTO; import org.openhab.core.io.rest.sse.internal.util.SseUtil; +import org.openhab.core.items.Item; +import org.openhab.core.items.events.ItemEvent; import org.openhab.core.items.events.ItemStateChangedEvent; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -104,11 +114,16 @@ public class SseResource implements RESTResource, SsePublisher { private final SseBroadcaster itemStatesBroadcaster = new SseBroadcaster<>(); private final SseItemStatesEventBuilder itemStatesEventBuilder; private final SseBroadcaster topicBroadcaster = new SseBroadcaster<>(); + private final AuthorizationManager authorizationManager; + private final AuthenticationContextHolder authenticationContextHolder; private ExecutorService executorService; @Activate - public SseResource(@Reference SseItemStatesEventBuilder itemStatesEventBuilder) { + public SseResource(@Reference SseItemStatesEventBuilder itemStatesEventBuilder, @Reference + AuthorizationManager authorizationManager, @Reference AuthenticationContextHolder authenticationContextHolder) { + this.authorizationManager = authorizationManager; + this.authenticationContextHolder = authenticationContextHolder; this.executorService = Executors.newSingleThreadExecutor(); this.itemStatesEventBuilder = itemStatesEventBuilder; } @@ -163,7 +178,7 @@ public void listen(@Context final SseEventSink sseEventSink, @Context final Http return; } - topicBroadcaster.add(sseEventSink, new SseSinkTopicInfo(eventFilter)); + topicBroadcaster.add(sseEventSink, new SseSinkTopicInfo(eventFilter, authenticationContextHolder.getAuthentication())); addCommonResponseHeaders(response); } @@ -172,7 +187,13 @@ private void handleEventBroadcastTopic(Event event) { final EventDTO eventDTO = SseUtil.buildDTO(event); final OutboundSseEvent sseEvent = SseUtil.buildEvent(sse.newEventBuilder(), eventDTO); - topicBroadcaster.sendIf(sseEvent, matchesTopic(eventDTO.topic)); + Predicate predicate = (sink) -> true; + if (event instanceof ItemEvent) { + String item = ((ItemEvent) event).getItemName(); + predicate = hasItemAccess(authorizationManager, item); + } + + topicBroadcaster.sendIf(sseEvent, matchesTopic(eventDTO.topic).and(predicate)); } /** @@ -187,7 +208,7 @@ private void handleEventBroadcastTopic(Event event) { @Operation(operationId = "initNewStateTacker", summary = "Initiates a new item state tracker connection", responses = { @ApiResponse(responseCode = "200", description = "OK") }) public void getStateEvents(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response) { - final SseSinkItemInfo sinkItemInfo = new SseSinkItemInfo(); + final SseSinkItemInfo sinkItemInfo = new SseSinkItemInfo(authenticationContextHolder.getAuthentication()); itemStatesBroadcaster.add(sseEventSink, sinkItemInfo); addCommonResponseHeaders(response); @@ -216,6 +237,13 @@ public Object updateTrackedItems(@PathParam("connectionId") String connectionId, return Response.status(Status.NOT_FOUND).build(); } + Set subsribedItemNames = new LinkedHashSet<>(); + for (String item : itemNames) { + if (authorizationManager.hasPermission(Permissions.READ, item, Item.class, authenticationContextHolder.getAuthentication())) { + subsribedItemNames.add(item); + } + } + itemStateInfo.get().updateTrackedItems(itemNames); OutboundSseEvent itemStateEvent = itemStatesEventBuilder.buildEvent(sse.newEventBuilder(), itemNames); @@ -233,7 +261,8 @@ public Object updateTrackedItems(@PathParam("connectionId") String connectionId, */ public void handleEventBroadcastItemState(final ItemStateChangedEvent stateChangeEvent) { String itemName = stateChangeEvent.getItemName(); - boolean isTracked = itemStatesBroadcaster.getInfoIf(info -> true).anyMatch(tracksItem(itemName)); + boolean isTracked = itemStatesBroadcaster.getInfoIf(info -> true).anyMatch(tracksItem(itemName) + .and(canAccessItem(authorizationManager, itemName))); if (isTracked) { OutboundSseEvent event = itemStatesEventBuilder.buildEvent(sse.newEventBuilder(), Set.of(itemName)); if (event != null) { diff --git a/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseSinkItemInfo.java b/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseSinkItemInfo.java index 560b4d263..aa315cc7c 100644 --- a/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseSinkItemInfo.java +++ b/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseSinkItemInfo.java @@ -18,6 +18,11 @@ import java.util.function.Predicate; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.AuthorizationManager; +import org.openhab.core.auth.Permissions; +import org.openhab.core.items.Item; /** * The specific information we need to hold for a SSE sink which tracks item state updates. @@ -29,6 +34,11 @@ public class SseSinkItemInfo { private final String connectionId = UUID.randomUUID().toString(); private final Set trackedItems = new CopyOnWriteArraySet<>(); + private final @Nullable Authentication authentication; + + public SseSinkItemInfo(@Nullable Authentication authentication) { + this.authentication = authentication; + } /** * Gets the connection identifier of this {@link SseSinkItemInfo} @@ -56,4 +66,13 @@ public static Predicate hasConnectionId(String connectionId) { public static Predicate tracksItem(String itemName) { return info -> info.trackedItems.contains(itemName); } + + public static Predicate canAccessItem(AuthorizationManager manager, String itemName) { + return info -> { + if (info.authentication == null) { + return false; + } + return manager.hasPermission(Permissions.READ, itemName, Item.class, info.authentication); + }; + } } diff --git a/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseSinkTopicInfo.java b/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseSinkTopicInfo.java index 1d11be620..2f11deae9 100644 --- a/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseSinkTopicInfo.java +++ b/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/internal/SseSinkTopicInfo.java @@ -16,7 +16,12 @@ import java.util.function.Predicate; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.AuthorizationManager; +import org.openhab.core.auth.Permissions; import org.openhab.core.io.rest.sse.internal.util.SseUtil; +import org.openhab.core.items.Item; /** * The specific information we need to hold for a SSE sink which subscribes to event topics. @@ -27,12 +32,23 @@ public class SseSinkTopicInfo { private final List regexFilters; + private final @Nullable Authentication authentication; - public SseSinkTopicInfo(String topicFilter) { + public SseSinkTopicInfo(String topicFilter, @Nullable Authentication authentication) { this.regexFilters = SseUtil.convertToRegex(topicFilter); + this.authentication = authentication; } public static Predicate matchesTopic(final String topic) { return info -> info.regexFilters.stream().anyMatch(topic::matches); } + + public static Predicate hasItemAccess(final AuthorizationManager manager, final String item) { + return info -> { + if (info.authentication == null) { + return false; + } + return manager.hasPermission(Permissions.READ, item, Item.class, info.authentication); + }; + } } \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.io.rest/src/main/java/org/openhab/core/io/rest/Stream2JSONInputStream.java b/bundles/org.opensmarthouse.core.io.rest/src/main/java/org/openhab/core/io/rest/Stream2JSONInputStream.java index 50f9b6fca..09594bed8 100755 --- a/bundles/org.opensmarthouse.core.io.rest/src/main/java/org/openhab/core/io/rest/Stream2JSONInputStream.java +++ b/bundles/org.opensmarthouse.core.io.rest/src/main/java/org/openhab/core/io/rest/Stream2JSONInputStream.java @@ -17,6 +17,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Iterator; +import java.util.function.Predicate; import java.util.stream.Stream; import org.openhab.core.library.types.DateTimeType; @@ -41,6 +42,7 @@ public class Stream2JSONInputStream extends InputStream implements JSONInputStre private boolean firstIteratorElement; private final Gson gson = new GsonBuilder().setDateFormat(DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS).create(); + private Predicate predicate; /** * Creates a new {@link Stream2JSONInputStream} backed by the given {@link Stream} source. @@ -53,11 +55,22 @@ public Stream2JSONInputStream(Stream source) { throw new IllegalArgumentException("The source must not be null!"); } - iterator = source.map(e -> gson.toJson(e)).iterator(); + iterator = source.filter(this::filter).map(e -> gson.toJson(e)).iterator(); jsonElementStream = new ByteArrayInputStream(new byte[0]); firstIteratorElement = true; } + public void setFilter(Predicate predicate) { + this.predicate = predicate; + } + + private boolean filter(Object element) { + if (this.predicate == null) { + return true; + } + return predicate.test(element); + } + @Override public int read() throws IOException { int result = jsonElementStream.read(); diff --git a/bundles/org.opensmarthouse.core.item.auth/pom.xml b/bundles/org.opensmarthouse.core.item.auth/pom.xml new file mode 100755 index 000000000..f692dc760 --- /dev/null +++ b/bundles/org.opensmarthouse.core.item.auth/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.item.auth + + OpenSmartHouse Core | Bundles | Item | Auth + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.item + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth + + + + diff --git a/bundles/org.opensmarthouse.core.item.auth/src/main/java/org/openhab/core/item/auth/internal/ItemPermissionEvaluator.java b/bundles/org.opensmarthouse.core.item.auth/src/main/java/org/openhab/core/item/auth/internal/ItemPermissionEvaluator.java new file mode 100644 index 000000000..e64742ca1 --- /dev/null +++ b/bundles/org.opensmarthouse.core.item.auth/src/main/java/org/openhab/core/item/auth/internal/ItemPermissionEvaluator.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.item.auth.internal; + +import java.util.Set; +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.Permission; +import org.openhab.core.auth.PermissionEvaluator; +import org.openhab.core.auth.Permissions; +import org.openhab.core.auth.Role; +import org.openhab.core.items.Item; +import org.osgi.service.component.annotations.Component; + +@Component +public class ItemPermissionEvaluator implements PermissionEvaluator { + + @Override + public boolean supports(Class type, Permission permission) { + return Item.class.isAssignableFrom(type); + } + + @Override + public boolean hasPermission(Permission permission, Authentication authentication, Item value) { + if (authentication.getRoles().contains(Role.ADMIN)) { + return true; + } + + Set permissions = authentication.getPermissions(); + String item = value.getName(); + return evaluate(permission, permissions, item); + } + + @Override + public boolean hasPermission(Permission permission, Authentication authentication, Class type, String item) { + if (authentication.getRoles().contains(Role.ADMIN)) { + return true; + } + + Set permissions = authentication.getPermissions(); + return evaluate(permission, permissions, item); + } + + protected boolean evaluate(Permission permission, Set permissions, String item) { + if (permissions == null) { + return false; + } + + return isPermitted(permissions, permission, item) || + isPermitted(permissions, permission, "*") || + isPermitted(permissions, Permissions.ALL, item); + } + + private boolean isPermitted(Set permissions, Permission permission, String item) { + return permissions.contains(permission.getCode() + ":item:" + item); + } + +} diff --git a/bundles/pom.xml b/bundles/pom.xml index 331028cee..db8643d00 100755 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -111,6 +111,7 @@ org.opensmarthouse.core.io.transport.serial.rxtx.rfc2217 org.opensmarthouse.core.io.transport.upnp org.opensmarthouse.core.item + org.opensmarthouse.core.item.auth org.opensmarthouse.core.item.core org.opensmarthouse.core.karaf org.opensmarthouse.core.karaf.jaas diff --git a/features/karaf/opensmarthouse-core/src/main/feature/feature.xml b/features/karaf/opensmarthouse-core/src/main/feature/feature.xml index a098681a6..7a5589552 100755 --- a/features/karaf/opensmarthouse-core/src/main/feature/feature.xml +++ b/features/karaf/opensmarthouse-core/src/main/feature/feature.xml @@ -314,6 +314,12 @@ mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.password/${project.version} + + opensmarthouse-core-auth + opensmarthouse-core-item + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.item.auth/${project.version} + + opensmarthouse-tp-gson opensmarthouse-core @@ -572,6 +578,7 @@ opensmarthouse-core-library-item opensmarthouse-core-io-rest + opensmarthouse-core-auth opensmarthouse-core-registry opensmarthouse-core-config opensmarthouse-core-thing @@ -582,6 +589,7 @@ opensmarthouse-tp-javax-inject opensmarthouse-core-io-rest + opensmarthouse-core-auth opensmarthouse-core-item opensmarthouse-core-transform mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.rest.sse/${project.version} @@ -1082,6 +1090,7 @@ opensmarthouse-core-io-rest + opensmarthouse-core-auth opensmarthouse-core-persistence mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.rest.persistence/${project.version} From 45f2f1495e9243f78e0e408c2ff62e05f056a894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Fri, 12 Feb 2021 12:05:59 +0100 Subject: [PATCH 03/14] Better handling of SSE security. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moved out password credentials out of core package (they are not mandatory kind of creds any more). Added session cookie authentication. Signed-off-by: Łukasz Dywicki --- bom/opensmarthouse/pom.xml | 15 ++++ .../pom.xml | 23 ++++++ .../core/auth/cookie/CookieCredentials.java | 45 +++++++++++ .../org.opensmarthouse.core.auth.jaas/pom.xml | 4 + .../internal/JaasAuthenticationProvider.java | 2 +- .../pom.xml | 4 + .../local/core/internal/UserRegistryImpl.java | 2 +- .../core/internal/UserRegistryImplTest.java | 2 +- .../UsernamePasswordCredentials.java | 4 +- .../org/openhab/core/auth/Authentication.java | 16 +++- .../pom.xml | 4 + .../internal/BasicCredentialsExtractor.java | 2 +- .../pom.xml | 35 +++++++++ .../internal/CookieCredentialsExtractor.java | 42 ++++++++++ .../pom.xml | 4 + .../internal/AbstractAuthPageServlet.java | 3 +- .../auth/internal/ServletRequestDelegate.java | 8 ++ .../openhab/core/io/http/facade/Cookie.java | 38 +++++++++ .../io/http/facade/HttpRequestDelegate.java | 2 + .../pom.xml | 4 + .../SessionCookieAuthenticationProvider.java | 77 +++++++++++++++++++ .../auth/internal/JaxRsRequestDelegate.java | 9 +++ .../persistence/PersistenceResource.java | 4 +- .../pom.xml | 4 + .../openhab/core/io/rest/sse/SseResource.java | 43 ++++++++--- bundles/pom.xml | 2 + .../src/main/feature/feature.xml | 16 ++++ 27 files changed, 392 insertions(+), 22 deletions(-) create mode 100755 bundles/org.opensmarthouse.core.auth.cookie/pom.xml create mode 100644 bundles/org.opensmarthouse.core.auth.cookie/src/main/java/org/openhab/core/auth/cookie/CookieCredentials.java rename bundles/{org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth => org.opensmarthouse.core.auth.password/src/main/java/org/openhab/core/auth/password}/UsernamePasswordCredentials.java (95%) create mode 100755 bundles/org.opensmarthouse.core.io.http.auth.cookie/pom.xml create mode 100755 bundles/org.opensmarthouse.core.io.http.auth.cookie/src/main/java/org/openhab/core/io/http/auth/cookie/internal/CookieCredentialsExtractor.java create mode 100644 bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/Cookie.java create mode 100644 bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/SessionCookieAuthenticationProvider.java diff --git a/bom/opensmarthouse/pom.xml b/bom/opensmarthouse/pom.xml index f0c3f1b7a..54ca87cca 100755 --- a/bom/opensmarthouse/pom.xml +++ b/bom/opensmarthouse/pom.xml @@ -227,6 +227,16 @@ org.opensmarthouse.core.auth.apitoken ${project.version} + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.apitoken.provider + ${project.version} + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.cookie + ${project.version} + org.opensmarthouse.core.bundles org.opensmarthouse.core.auth.core @@ -267,6 +277,11 @@ org.opensmarthouse.core.auth.oauth2client.core ${project.version} + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.password + ${project.version} + org.opensmarthouse.core.bundles org.opensmarthouse.core.io.net diff --git a/bundles/org.opensmarthouse.core.auth.cookie/pom.xml b/bundles/org.opensmarthouse.core.auth.cookie/pom.xml new file mode 100755 index 000000000..ec91b97f7 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.cookie/pom.xml @@ -0,0 +1,23 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.auth.cookie + + OpenSmartHouse Core | Bundles | Cookie Credentials + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth + + + + diff --git a/bundles/org.opensmarthouse.core.auth.cookie/src/main/java/org/openhab/core/auth/cookie/CookieCredentials.java b/bundles/org.opensmarthouse.core.auth.cookie/src/main/java/org/openhab/core/auth/cookie/CookieCredentials.java new file mode 100644 index 000000000..cfffe7d59 --- /dev/null +++ b/bundles/org.opensmarthouse.core.auth.cookie/src/main/java/org/openhab/core/auth/cookie/CookieCredentials.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.auth.cookie; + +import org.openhab.core.auth.Credentials; + +/** + * Credentials which represent key/value pair coming from HTTP cookie. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class CookieCredentials implements Credentials { + + private final String name; + private final String value; + + public CookieCredentials(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + @Override + public String getScheme() { + return "cookie"; + } + +} diff --git a/bundles/org.opensmarthouse.core.auth.jaas/pom.xml b/bundles/org.opensmarthouse.core.auth.jaas/pom.xml index 2a5b4aa0e..68befadbc 100755 --- a/bundles/org.opensmarthouse.core.auth.jaas/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.jaas/pom.xml @@ -18,6 +18,10 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.auth + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.password + org.opensmarthouse.core.bundles org.opensmarthouse.core.auth.local diff --git a/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java b/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java index d3e9844f7..aa737c9aa 100755 --- a/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java +++ b/bundles/org.opensmarthouse.core.auth.jaas/src/main/java/org/openhab/core/auth/jaas/internal/JaasAuthenticationProvider.java @@ -35,7 +35,7 @@ import org.openhab.core.auth.AuthenticationResult; import org.openhab.core.auth.Credentials; import org.openhab.core.auth.local.GenericUser; -import org.openhab.core.auth.UsernamePasswordCredentials; +import org.openhab.core.auth.password.UsernamePasswordCredentials; import org.openhab.core.config.core.ConfigurableService; import org.osgi.framework.Constants; import org.osgi.service.component.annotations.Activate; diff --git a/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml b/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml index 8f8e131d9..d0a445ca3 100755 --- a/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml @@ -18,6 +18,10 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.auth.local + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.password + org.opensmarthouse.core.bundles org.opensmarthouse.core.auth.apitoken diff --git a/bundles/org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal/UserRegistryImpl.java b/bundles/org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal/UserRegistryImpl.java index f4c651aaa..d736f5989 100755 --- a/bundles/org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal/UserRegistryImpl.java +++ b/bundles/org.opensmarthouse.core.auth.local.provider/src/main/java/org/openhab/core/auth/local/core/internal/UserRegistryImpl.java @@ -34,11 +34,11 @@ import org.openhab.core.auth.local.GenericUser; import org.openhab.core.auth.local.ManagedUser; import org.openhab.core.auth.local.User; -import org.openhab.core.auth.UsernamePasswordCredentials; import org.openhab.core.auth.local.UserApiToken; import org.openhab.core.auth.local.UserProvider; import org.openhab.core.auth.local.UserRegistry; import org.openhab.core.auth.local.UserSession; +import org.openhab.core.auth.password.UsernamePasswordCredentials; import org.openhab.core.common.registry.AbstractRegistry; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Activate; diff --git a/bundles/org.opensmarthouse.core.auth.local.provider/src/test/java/org/openhab/core/auth/local/core/internal/UserRegistryImplTest.java b/bundles/org.opensmarthouse.core.auth.local.provider/src/test/java/org/openhab/core/auth/local/core/internal/UserRegistryImplTest.java index 2551c1393..b466b7843 100644 --- a/bundles/org.opensmarthouse.core.auth.local.provider/src/test/java/org/openhab/core/auth/local/core/internal/UserRegistryImplTest.java +++ b/bundles/org.opensmarthouse.core.auth.local.provider/src/test/java/org/openhab/core/auth/local/core/internal/UserRegistryImplTest.java @@ -28,7 +28,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.core.auth.local.ManagedUser; import org.openhab.core.auth.local.User; -import org.openhab.core.auth.UsernamePasswordCredentials; +import org.openhab.core.auth.password.UsernamePasswordCredentials; import org.openhab.core.auth.local.UserSession; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceEvent; diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UsernamePasswordCredentials.java b/bundles/org.opensmarthouse.core.auth.password/src/main/java/org/openhab/core/auth/password/UsernamePasswordCredentials.java similarity index 95% rename from bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UsernamePasswordCredentials.java rename to bundles/org.opensmarthouse.core.auth.password/src/main/java/org/openhab/core/auth/password/UsernamePasswordCredentials.java index 40e328cf0..80644e6b9 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/UsernamePasswordCredentials.java +++ b/bundles/org.opensmarthouse.core.auth.password/src/main/java/org/openhab/core/auth/password/UsernamePasswordCredentials.java @@ -10,7 +10,9 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.core.auth; +package org.openhab.core.auth.password; + +import org.openhab.core.auth.Credentials; /** * Credentials which represent user name and password. diff --git a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java index e94de3aef..cf691c5e8 100755 --- a/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java +++ b/bundles/org.opensmarthouse.core.auth/src/main/java/org/openhab/core/auth/Authentication.java @@ -71,10 +71,22 @@ public Authentication(String username, String[] roles, String scope) { * @param permissions permissions associated with authentication */ public Authentication(String username, String[] roles, String scope, String[] permissions) { + this(username, Set.of(roles), scope, Set.of(permissions)); + } + + /** + * Creates a new instance with a specific scope + * + * @param username name of the user associated to this authentication instance + * @param roles a variable list of roles that the user possesses. + * @param scope a scope this authentication is valid for + * @param permissions permissions associated with authentication + */ + public Authentication(String username, Set roles, String scope, Set permissions) { this.username = username; - this.roles = Set.of(roles); + this.roles = roles; this.scope = scope; - this.permissions = Set.of(permissions); + this.permissions = permissions; } /** diff --git a/bundles/org.opensmarthouse.core.io.http.auth.basic/pom.xml b/bundles/org.opensmarthouse.core.io.http.auth.basic/pom.xml index 3646f8d20..0ea5f3a1c 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth.basic/pom.xml +++ b/bundles/org.opensmarthouse.core.io.http.auth.basic/pom.xml @@ -14,6 +14,10 @@ OpenSmartHouse Core | Bundles | HTTP Authentication Basic + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.password + org.opensmarthouse.core.bundles org.opensmarthouse.core.io.auth diff --git a/bundles/org.opensmarthouse.core.io.http.auth.basic/src/main/java/org/openhab/core/io/http/auth/basic/internal/BasicCredentialsExtractor.java b/bundles/org.opensmarthouse.core.io.http.auth.basic/src/main/java/org/openhab/core/io/http/auth/basic/internal/BasicCredentialsExtractor.java index ab76ebcb6..c72874052 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth.basic/src/main/java/org/openhab/core/io/http/auth/basic/internal/BasicCredentialsExtractor.java +++ b/bundles/org.opensmarthouse.core.io.http.auth.basic/src/main/java/org/openhab/core/io/http/auth/basic/internal/BasicCredentialsExtractor.java @@ -18,7 +18,7 @@ import javax.servlet.http.HttpServletRequest; import org.openhab.core.auth.Credentials; -import org.openhab.core.auth.UsernamePasswordCredentials; +import org.openhab.core.auth.password.UsernamePasswordCredentials; import org.openhab.core.io.auth.CredentialsExtractor; import org.openhab.core.io.http.facade.HttpRequestDelegate; import org.osgi.service.component.annotations.Component; diff --git a/bundles/org.opensmarthouse.core.io.http.auth.cookie/pom.xml b/bundles/org.opensmarthouse.core.io.http.auth.cookie/pom.xml new file mode 100755 index 000000000..20e4a8357 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.auth.cookie/pom.xml @@ -0,0 +1,35 @@ + + + + 4.0.0 + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.reactor.bundles + 0.9.2-SNAPSHOT + + + org.opensmarthouse.core.io.http.auth.cookie + + OpenSmartHouse Core | Bundles | HTTP Cookie Authentication + + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.cookie + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.auth + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.http.facade + + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.http.auth + + + + diff --git a/bundles/org.opensmarthouse.core.io.http.auth.cookie/src/main/java/org/openhab/core/io/http/auth/cookie/internal/CookieCredentialsExtractor.java b/bundles/org.opensmarthouse.core.io.http.auth.cookie/src/main/java/org/openhab/core/io/http/auth/cookie/internal/CookieCredentialsExtractor.java new file mode 100755 index 000000000..cfb64689e --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.auth.cookie/src/main/java/org/openhab/core/io/http/auth/cookie/internal/CookieCredentialsExtractor.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.http.auth.cookie.internal; + +import java.util.Optional; + +import org.openhab.core.auth.Credentials; +import org.openhab.core.auth.cookie.CookieCredentials; +import org.openhab.core.io.auth.CredentialsExtractor; +import org.openhab.core.io.http.facade.Cookie; +import org.openhab.core.io.http.facade.HttpRequestDelegate; +import org.osgi.service.component.annotations.Component; + +/** + * Extract session information from cookie inincoming request. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component(property = { "context=org.openhab.core.io.http.facade.HttpRequestDelegate" }) +public class CookieCredentialsExtractor implements CredentialsExtractor { + + public static final String SESSIONID_COOKIE_NAME = "X-OPENHAB-SESSIONID"; + + @Override + public Optional retrieveCredentials(HttpRequestDelegate request) { + return request.getCookie(SESSIONID_COOKIE_NAME).map(this::process); + } + + private CookieCredentials process(Cookie cookie) { + return new CookieCredentials(cookie.getName(), cookie.getValue()); + } +} diff --git a/bundles/org.opensmarthouse.core.io.http.auth/pom.xml b/bundles/org.opensmarthouse.core.io.http.auth/pom.xml index 52374cf39..2ac561417 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth/pom.xml +++ b/bundles/org.opensmarthouse.core.io.http.auth/pom.xml @@ -18,6 +18,10 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.auth + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.password + org.opensmarthouse.core.bundles org.opensmarthouse.core.io.auth diff --git a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java index ad156cd84..5d27bb49c 100644 --- a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java +++ b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/AbstractAuthPageServlet.java @@ -33,11 +33,10 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.auth.AuthenticationException; import org.openhab.core.auth.AuthenticationManager; -import org.openhab.core.auth.AuthenticationProvider; import org.openhab.core.auth.AuthenticationResult; import org.openhab.core.auth.local.User; import org.openhab.core.auth.local.UserRegistry; -import org.openhab.core.auth.UsernamePasswordCredentials; +import org.openhab.core.auth.password.UsernamePasswordCredentials; import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Reference; diff --git a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ServletRequestDelegate.java b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ServletRequestDelegate.java index 28b465af9..a20b97794 100644 --- a/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ServletRequestDelegate.java +++ b/bundles/org.opensmarthouse.core.io.http.auth/src/main/java/org/openhab/core/io/http/auth/internal/ServletRequestDelegate.java @@ -12,8 +12,10 @@ */ package org.openhab.core.io.http.auth.internal; +import java.util.Arrays; import java.util.Optional; import javax.servlet.http.HttpServletRequest; +import org.openhab.core.io.http.facade.Cookie; import org.openhab.core.io.http.facade.HttpRequestDelegate; /** @@ -34,4 +36,10 @@ public Optional getHeader(String headerName) { return Optional.ofNullable(request.getHeader(headerName)); } + @Override + public Optional getCookie(String cookieName) { + return Arrays.stream(request.getCookies()).filter(cookie -> cookieName.equals(cookie.getName())) + .map(cookie -> new Cookie(cookie.getName(), cookie.getValue())) + .findFirst(); + } } diff --git a/bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/Cookie.java b/bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/Cookie.java new file mode 100644 index 000000000..1e684dc2e --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/Cookie.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.http.facade; + +/** + * An unified (facade) version of http cookie. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class Cookie { + + private final String name; + private final String value; + + public Cookie(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + +} diff --git a/bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/HttpRequestDelegate.java b/bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/HttpRequestDelegate.java index ed10a1b0c..675042045 100644 --- a/bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/HttpRequestDelegate.java +++ b/bundles/org.opensmarthouse.core.io.http.facade/src/main/java/org/openhab/core/io/http/facade/HttpRequestDelegate.java @@ -26,4 +26,6 @@ public interface HttpRequestDelegate { default Optional getAuthorizationHeader() { return getHeader("Authorization"); } + + Optional getCookie(String cookieName); } \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml b/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml index 463c32158..e84be7cc6 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml @@ -18,6 +18,10 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.config + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.auth.cookie + org.opensmarthouse.core.bundles org.opensmarthouse.core.auth.jwt diff --git a/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/SessionCookieAuthenticationProvider.java b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/SessionCookieAuthenticationProvider.java new file mode 100644 index 000000000..8e214e268 --- /dev/null +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/src/main/java/org/openhab/core/io/rest/auth/local/internal/SessionCookieAuthenticationProvider.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2019-2020 Contributors to the OpenSmartHouse project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.io.rest.auth.local.internal; + +import org.openhab.core.auth.Authentication; +import org.openhab.core.auth.AuthenticationException; +import org.openhab.core.auth.AuthenticationProvider; +import org.openhab.core.auth.AuthenticationResult; +import org.openhab.core.auth.Credentials; +import org.openhab.core.auth.cookie.CookieCredentials; +import org.openhab.core.auth.local.GenericUser; +import org.openhab.core.auth.local.ManagedUser; +import org.openhab.core.auth.local.User; +import org.openhab.core.auth.local.UserRegistry; +import org.openhab.core.auth.local.UserSession; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * An authentication provider which rely on session cookie and try bo bind it with one of existing users. + * + * This logic (possibly in similar shape) originally was located in JAXRS authentication filter. + * The advantage of this provider is possibility to use shared cookie authentication both for servlet + * and REST resources. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component +public class SessionCookieAuthenticationProvider implements AuthenticationProvider { + + private final UserRegistry registry; + + @Activate + public SessionCookieAuthenticationProvider(@Reference UserRegistry registry) { + this.registry = registry; + } + + @Override + public AuthenticationResult authenticate(Credentials credentials) throws AuthenticationException { + if (!(credentials instanceof CookieCredentials)) { + throw new IllegalArgumentException("Unsupported credentials"); + } + + String session = ((CookieCredentials) credentials).getValue(); + for (User user : registry.getAll()) { + if (user instanceof ManagedUser) { + ManagedUser managed = (ManagedUser) user; + for (UserSession userSession : managed.getSessions()) { + if (session.equals(userSession.getSessionId())) { + return new AuthenticationResult(new GenericUser(user.getName()), credentials.getScheme(), + new Authentication(user.getName(), user.getRoles(), null, user.getPermissions()) + ); + } + } + } + } + + throw new AuthenticationException("Failed to authenticate cookie"); + } + + @Override + public boolean supports(Class type) { + return CookieCredentials.class.isAssignableFrom(type); + } + +} diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JaxRsRequestDelegate.java b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JaxRsRequestDelegate.java index 1640e5e0a..6e1092fe8 100644 --- a/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JaxRsRequestDelegate.java +++ b/bundles/org.opensmarthouse.core.io.rest.auth/src/main/java/org/openhab/core/io/rest/auth/internal/JaxRsRequestDelegate.java @@ -14,6 +14,7 @@ import java.util.Optional; import javax.ws.rs.container.ContainerRequestContext; +import org.openhab.core.io.http.facade.Cookie; import org.openhab.core.io.http.facade.HttpRequestDelegate; /** @@ -34,4 +35,12 @@ public Optional getHeader(String headerName) { return Optional.ofNullable(request.getHeaderString(headerName)); } + @Override + public Optional getCookie(String cookieName) { + if (request.getCookies().containsKey(cookieName)) { + javax.ws.rs.core.Cookie cookie = request.getCookies().get(cookieName); + return Optional.of(new Cookie(cookie.getName(), cookie.getValue())); + } + return Optional.empty(); + } } diff --git a/bundles/org.opensmarthouse.core.io.rest.persistence/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java b/bundles/org.opensmarthouse.core.io.rest.persistence/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java index e6e193775..d50250e77 100755 --- a/bundles/org.opensmarthouse.core.io.rest.persistence/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java +++ b/bundles/org.opensmarthouse.core.io.rest.persistence/src/main/java/org/openhab/core/io/rest/core/internal/persistence/PersistenceResource.java @@ -187,7 +187,7 @@ public Response httpGetPersistenceItemData(@Context HttpHeaders headers, @Parameter(description = "The length of each page.") @QueryParam("pagelength") int pageLength, @Parameter(description = "Gets one value before and after the requested period.") @QueryParam("boundary") boolean boundary) { - if (!authorizationManager.hasPermission(Permissions.PERSISTENCE, itemName, Item.class, authenticationContextHolder.getAuthentication())) { + if (!authorizationManager.hasPermission(Permissions.READ, itemName, Item.class, authenticationContextHolder.getAuthentication())) { return Response.status(Status.UNAUTHORIZED).build(); } return getItemHistoryDTO(serviceId, itemName, startTime, endTime, pageNumber, pageLength, boundary); @@ -540,7 +540,7 @@ static class AuthorizationCheck implements BiFunctionjunit junit + + org.opensmarthouse.core.bundles + org.opensmarthouse.core.io.rest.auth + diff --git a/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java b/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java index d86c19bff..bc177108a 100755 --- a/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java +++ b/bundles/org.opensmarthouse.core.io.rest.sse/src/main/java/org/openhab/core/io/rest/sse/SseResource.java @@ -40,20 +40,23 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.SecurityContext; import javax.ws.rs.sse.OutboundSseEvent; import javax.ws.rs.sse.Sse; import javax.ws.rs.sse.SseEventSink; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.auth.Authentication; import org.openhab.core.auth.AuthenticationContextHolder; import org.openhab.core.auth.AuthorizationManager; -import org.openhab.core.auth.Permission; import org.openhab.core.auth.Permissions; import org.openhab.core.auth.Role; import org.openhab.core.events.Event; import org.openhab.core.io.rest.RESTConstants; import org.openhab.core.io.rest.RESTResource; import org.openhab.core.io.rest.SseBroadcaster; +import org.openhab.core.io.rest.auth.AuthenticationSecurityContext; import org.openhab.core.io.rest.sse.internal.SseItemStatesEventBuilder; import org.openhab.core.io.rest.sse.internal.SsePublisher; import org.openhab.core.io.rest.sse.internal.SseSinkItemInfo; @@ -171,14 +174,14 @@ private void addCommonResponseHeaders(final HttpServletResponse response) { @Operation(operationId = "getEvents", summary = "Get all events.", responses = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Topic is empty or contains invalid characters") }) - public void listen(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response, + public void listen(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response, @Context SecurityContext securityContext, @QueryParam("topics") @Parameter(description = "topics") String eventFilter) { if (!SseUtil.isValidTopicFilter(eventFilter)) { response.setStatus(Status.BAD_REQUEST.getStatusCode()); return; } - topicBroadcaster.add(sseEventSink, new SseSinkTopicInfo(eventFilter, authenticationContextHolder.getAuthentication())); + topicBroadcaster.add(sseEventSink, new SseSinkTopicInfo(eventFilter, retrieveAuthentication(securityContext))); addCommonResponseHeaders(response); } @@ -207,8 +210,8 @@ private void handleEventBroadcastTopic(Event event) { @Produces(MediaType.SERVER_SENT_EVENTS) @Operation(operationId = "initNewStateTacker", summary = "Initiates a new item state tracker connection", responses = { @ApiResponse(responseCode = "200", description = "OK") }) - public void getStateEvents(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response) { - final SseSinkItemInfo sinkItemInfo = new SseSinkItemInfo(authenticationContextHolder.getAuthentication()); + public void getStateEvents(@Context final SseEventSink sseEventSink, @Context final HttpServletResponse response, @Context SecurityContext securityContext) { + final SseSinkItemInfo sinkItemInfo = new SseSinkItemInfo(retrieveAuthentication(securityContext)); itemStatesBroadcaster.add(sseEventSink, sinkItemInfo); addCommonResponseHeaders(response); @@ -229,7 +232,7 @@ public void getStateEvents(@Context final SseEventSink sseEventSink, @Context fi @Operation(operationId = "updateItemListForStateUpdates", summary = "Changes the list of items a SSE connection will receive state updates to.", responses = { @ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Unknown connectionId") }) - public Object updateTrackedItems(@PathParam("connectionId") String connectionId, + public Object updateTrackedItems(@PathParam("connectionId") String connectionId, @Context SecurityContext securityContext, @Parameter(description = "items") Set itemNames) { Optional itemStateInfo = itemStatesBroadcaster.getInfoIf(hasConnectionId(connectionId)) .findFirst(); @@ -237,16 +240,17 @@ public Object updateTrackedItems(@PathParam("connectionId") String connectionId, return Response.status(Status.NOT_FOUND).build(); } - Set subsribedItemNames = new LinkedHashSet<>(); + Set subscribedItemNames = new LinkedHashSet<>(); + @Nullable Authentication authentication = retrieveAuthentication(securityContext); for (String item : itemNames) { - if (authorizationManager.hasPermission(Permissions.READ, item, Item.class, authenticationContextHolder.getAuthentication())) { - subsribedItemNames.add(item); + if (authorizationManager.hasPermission(Permissions.READ, item, Item.class, authentication)) { + subscribedItemNames.add(item); } } itemStateInfo.get().updateTrackedItems(itemNames); - OutboundSseEvent itemStateEvent = itemStatesEventBuilder.buildEvent(sse.newEventBuilder(), itemNames); + OutboundSseEvent itemStateEvent = itemStatesEventBuilder.buildEvent(sse.newEventBuilder(), subscribedItemNames); if (itemStateEvent != null) { itemStatesBroadcaster.sendIf(itemStateEvent, hasConnectionId(connectionId)); } @@ -262,7 +266,8 @@ public Object updateTrackedItems(@PathParam("connectionId") String connectionId, public void handleEventBroadcastItemState(final ItemStateChangedEvent stateChangeEvent) { String itemName = stateChangeEvent.getItemName(); boolean isTracked = itemStatesBroadcaster.getInfoIf(info -> true).anyMatch(tracksItem(itemName) - .and(canAccessItem(authorizationManager, itemName))); + .and(canAccessItem(authorizationManager, itemName)) + ); if (isTracked) { OutboundSseEvent event = itemStatesEventBuilder.buildEvent(sse.newEventBuilder(), Set.of(itemName)); if (event != null) { @@ -270,4 +275,20 @@ public void handleEventBroadcastItemState(final ItemStateChangedEvent stateChang } } } + + /** + * The security context propagated through ThreadLocal is sometimes lost hence use of this + * helper method which allows attaching of security context managed by request container itself. + * + * @param securityContext Request security context. + * @return Authentication or null if it cannot be found. + */ + @Nullable + private Authentication retrieveAuthentication(SecurityContext securityContext) { + return Optional.ofNullable(securityContext) + .filter(AuthenticationSecurityContext.class::isInstance) + .map(AuthenticationSecurityContext.class::cast) + .map(AuthenticationSecurityContext::getAuthentication) + .orElse(authenticationContextHolder.getAuthentication()); + } } \ No newline at end of file diff --git a/bundles/pom.xml b/bundles/pom.xml index db8643d00..ee7e5776e 100755 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -23,6 +23,7 @@ org.opensmarthouse.core.auth.core org.opensmarthouse.core.auth.apitoken org.opensmarthouse.core.auth.apitoken.provider + org.opensmarthouse.core.auth.cookie org.opensmarthouse.core.auth.jaas org.opensmarthouse.core.auth.jwt org.opensmarthouse.core.auth.jwt.provider @@ -73,6 +74,7 @@ org.opensmarthouse.core.io.http.auth org.opensmarthouse.core.io.http.auth.apitoken org.opensmarthouse.core.io.http.auth.basic + org.opensmarthouse.core.io.http.auth.cookie org.opensmarthouse.core.io.http.auth.jwt org.opensmarthouse.core.io.http.facade org.opensmarthouse.core.io.jetty.certificate diff --git a/features/karaf/opensmarthouse-core/src/main/feature/feature.xml b/features/karaf/opensmarthouse-core/src/main/feature/feature.xml index 7a5589552..8896656d2 100755 --- a/features/karaf/opensmarthouse-core/src/main/feature/feature.xml +++ b/features/karaf/opensmarthouse-core/src/main/feature/feature.xml @@ -261,6 +261,11 @@ mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.apitoken/${project.version} + + opensmarthouse-core-auth + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.cookie/${project.version} + + opensmarthouse-core-auth opensmarthouse-core-storage @@ -270,6 +275,7 @@ opensmarthouse-core-auth opensmarthouse-core-auth-local + opensmarthouse-core-auth-password mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.auth.jaas/${project.version} @@ -517,6 +523,13 @@ mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.http.auth.apitoken/${project.version} + + opensmarthouse-core-base + opensmarthouse-core-auth-cookie + opensmarthouse-core-io-http-auth + mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.http.auth.cookie/${project.version} + + opensmarthouse-core-base mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.http.auth.basic/${project.version} @@ -554,6 +567,7 @@ opensmarthouse-core-io-http-auth-jwt opensmarthouse-core-io-rest-auth + opensmarthouse-core-auth-cookie opensmarthouse-core-auth-jwt opensmarthouse-core-auth-local-provider mvn:org.opensmarthouse.core.bundles/org.opensmarthouse.core.io.rest.auth.local/${project.version} @@ -589,6 +603,8 @@ opensmarthouse-tp-javax-inject opensmarthouse-core-io-rest + opensmarthouse-core-io-rest-auth + opensmarthouse-core-io-http-auth-cookie opensmarthouse-core-auth opensmarthouse-core-item opensmarthouse-core-transform From e33032a30af72b02d53b29c8f6fd75e6d2883a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Sat, 22 May 2021 01:32:29 +0200 Subject: [PATCH 04/14] Update to 0.9.3-SNAPSHOT. --- .../org.opensmarthouse.core.auth.apitoken.provider/pom.xml | 2 +- bundles/org.opensmarthouse.core.auth.apitoken/pom.xml | 2 +- bundles/org.opensmarthouse.core.auth.cookie/pom.xml | 2 +- bundles/org.opensmarthouse.core.auth.jwt.provider/pom.xml | 2 +- bundles/org.opensmarthouse.core.auth.jwt/pom.xml | 2 +- bundles/org.opensmarthouse.core.auth.local.provider/pom.xml | 2 +- bundles/org.opensmarthouse.core.auth.local/pom.xml | 2 +- bundles/org.opensmarthouse.core.auth.password/pom.xml | 2 +- bundles/org.opensmarthouse.core.io.auth/pom.xml | 2 +- .../org.opensmarthouse.core.io.http.auth.apitoken/pom.xml | 2 +- bundles/org.opensmarthouse.core.io.http.auth.cookie/pom.xml | 2 +- bundles/org.opensmarthouse.core.io.http.auth.jwt/pom.xml | 2 +- bundles/org.opensmarthouse.core.io.http.facade/pom.xml | 2 +- bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml | 2 +- bundles/org.opensmarthouse.core.io.rest.auth/pom.xml | 2 +- bundles/org.opensmarthouse.core.item.auth/pom.xml | 2 +- bundles/org.opensmarthouse.core.karaf.jaas/pom.xml | 2 +- pom.xml | 5 +++++ 18 files changed, 22 insertions(+), 17 deletions(-) diff --git a/bundles/org.opensmarthouse.core.auth.apitoken.provider/pom.xml b/bundles/org.opensmarthouse.core.auth.apitoken.provider/pom.xml index 52538c120..e557fa637 100755 --- a/bundles/org.opensmarthouse.core.auth.apitoken.provider/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.apitoken.provider/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.auth.apitoken.provider diff --git a/bundles/org.opensmarthouse.core.auth.apitoken/pom.xml b/bundles/org.opensmarthouse.core.auth.apitoken/pom.xml index 942cd4684..fbde0b1c5 100755 --- a/bundles/org.opensmarthouse.core.auth.apitoken/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.apitoken/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.auth.apitoken diff --git a/bundles/org.opensmarthouse.core.auth.cookie/pom.xml b/bundles/org.opensmarthouse.core.auth.cookie/pom.xml index ec91b97f7..5e93d4f76 100755 --- a/bundles/org.opensmarthouse.core.auth.cookie/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.cookie/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.auth.cookie diff --git a/bundles/org.opensmarthouse.core.auth.jwt.provider/pom.xml b/bundles/org.opensmarthouse.core.auth.jwt.provider/pom.xml index df4128ac3..88a6fda77 100755 --- a/bundles/org.opensmarthouse.core.auth.jwt.provider/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.jwt.provider/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.auth.jwt.provider diff --git a/bundles/org.opensmarthouse.core.auth.jwt/pom.xml b/bundles/org.opensmarthouse.core.auth.jwt/pom.xml index 4c71c29dd..12a52df9a 100755 --- a/bundles/org.opensmarthouse.core.auth.jwt/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.jwt/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.auth.jwt diff --git a/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml b/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml index d0a445ca3..8cbaac1c1 100755 --- a/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.local.provider/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.auth.local.provider diff --git a/bundles/org.opensmarthouse.core.auth.local/pom.xml b/bundles/org.opensmarthouse.core.auth.local/pom.xml index 864d77497..d6870c582 100755 --- a/bundles/org.opensmarthouse.core.auth.local/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.local/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.auth.local diff --git a/bundles/org.opensmarthouse.core.auth.password/pom.xml b/bundles/org.opensmarthouse.core.auth.password/pom.xml index dc6049f54..6543a52d9 100755 --- a/bundles/org.opensmarthouse.core.auth.password/pom.xml +++ b/bundles/org.opensmarthouse.core.auth.password/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.auth.password diff --git a/bundles/org.opensmarthouse.core.io.auth/pom.xml b/bundles/org.opensmarthouse.core.io.auth/pom.xml index 2c638b061..432bcea7e 100755 --- a/bundles/org.opensmarthouse.core.io.auth/pom.xml +++ b/bundles/org.opensmarthouse.core.io.auth/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.io.auth diff --git a/bundles/org.opensmarthouse.core.io.http.auth.apitoken/pom.xml b/bundles/org.opensmarthouse.core.io.http.auth.apitoken/pom.xml index bbb8c778f..e6971de37 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth.apitoken/pom.xml +++ b/bundles/org.opensmarthouse.core.io.http.auth.apitoken/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.io.http.auth.apitoken diff --git a/bundles/org.opensmarthouse.core.io.http.auth.cookie/pom.xml b/bundles/org.opensmarthouse.core.io.http.auth.cookie/pom.xml index 20e4a8357..c1fa0498d 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth.cookie/pom.xml +++ b/bundles/org.opensmarthouse.core.io.http.auth.cookie/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.io.http.auth.cookie diff --git a/bundles/org.opensmarthouse.core.io.http.auth.jwt/pom.xml b/bundles/org.opensmarthouse.core.io.http.auth.jwt/pom.xml index b5bb15f89..09473249d 100755 --- a/bundles/org.opensmarthouse.core.io.http.auth.jwt/pom.xml +++ b/bundles/org.opensmarthouse.core.io.http.auth.jwt/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.io.http.auth.jwt diff --git a/bundles/org.opensmarthouse.core.io.http.facade/pom.xml b/bundles/org.opensmarthouse.core.io.http.facade/pom.xml index ebb44490a..04305d13e 100755 --- a/bundles/org.opensmarthouse.core.io.http.facade/pom.xml +++ b/bundles/org.opensmarthouse.core.io.http.facade/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.io.http.facade diff --git a/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml b/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml index e84be7cc6..5c32d2d85 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml +++ b/bundles/org.opensmarthouse.core.io.rest.auth.local/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.io.rest.auth.local diff --git a/bundles/org.opensmarthouse.core.io.rest.auth/pom.xml b/bundles/org.opensmarthouse.core.io.rest.auth/pom.xml index a60f66de1..a03f44ac8 100755 --- a/bundles/org.opensmarthouse.core.io.rest.auth/pom.xml +++ b/bundles/org.opensmarthouse.core.io.rest.auth/pom.xml @@ -42,7 +42,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.io.http.facade - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT compile diff --git a/bundles/org.opensmarthouse.core.item.auth/pom.xml b/bundles/org.opensmarthouse.core.item.auth/pom.xml index f692dc760..f5a1b0015 100755 --- a/bundles/org.opensmarthouse.core.item.auth/pom.xml +++ b/bundles/org.opensmarthouse.core.item.auth/pom.xml @@ -5,7 +5,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.item.auth diff --git a/bundles/org.opensmarthouse.core.karaf.jaas/pom.xml b/bundles/org.opensmarthouse.core.karaf.jaas/pom.xml index 48b9f9412..c4c66e6e0 100755 --- a/bundles/org.opensmarthouse.core.karaf.jaas/pom.xml +++ b/bundles/org.opensmarthouse.core.karaf.jaas/pom.xml @@ -6,7 +6,7 @@ org.opensmarthouse.core.bundles org.opensmarthouse.core.reactor.bundles - 0.9.2-SNAPSHOT + 0.9.3-SNAPSHOT org.opensmarthouse.core.karaf.jaas diff --git a/pom.xml b/pom.xml index 5a820054d..e940b733c 100755 --- a/pom.xml +++ b/pom.xml @@ -427,6 +427,11 @@ Import-Package: \\ build-helper-maven-plugin ${mojohaus-buildhelper-plugin.version} + + org.apache.karaf.tooling + karaf-maven-plugin + ${karaf.tooling.version} + com.mycila From cae6d2c4767f10484e0ac047ec13118a99757309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Sat, 22 May 2021 01:35:07 +0200 Subject: [PATCH 05/14] Solve troubles with chained X-Forwarded headers. X-Forwarded-Proto can be a list ie. https, http when two proxy servers are used. This patch makes sure that generated response uses first host/values. --- .../core/io/rest/core/internal/item/ItemResource.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index 5aeee8b42..1a93d27a0 100755 --- a/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -149,13 +149,21 @@ public class ItemResource implements RESTResource { */ private static void respectForwarded(final UriBuilder uriBuilder, final @Context HttpHeaders httpHeaders) { Optional.ofNullable(httpHeaders.getHeaderString("X-Forwarded-Host")).ifPresent(host -> { + if (host.contains(",")) { + host = host.split(",")[0]; + } final String[] parts = host.split(":"); uriBuilder.host(parts[0]); if (parts.length > 1) { uriBuilder.port(Integer.parseInt(parts[1])); } }); - Optional.ofNullable(httpHeaders.getHeaderString("X-Forwarded-Proto")).ifPresent(uriBuilder::scheme); + Optional.ofNullable(httpHeaders.getHeaderString("X-Forwarded-Proto")).map(scheme -> { + if (scheme.contains(",")) { + return scheme.split(",")[0]; + } + return scheme; + }).ifPresent(uriBuilder::scheme); } private final Logger logger = LoggerFactory.getLogger(ItemResource.class); From 91b25e78e3049f88436799bb58a368077497616d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Tue, 17 Aug 2021 13:10:34 +0200 Subject: [PATCH 06/14] Adjust userdata and log locations. --- .../framework/src/main/resources/resources/bin/oh2_dir_layout | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/karaf/framework/src/main/resources/resources/bin/oh2_dir_layout b/features/karaf/framework/src/main/resources/resources/bin/oh2_dir_layout index 679620d4c..b685235b9 100644 --- a/features/karaf/framework/src/main/resources/resources/bin/oh2_dir_layout +++ b/features/karaf/framework/src/main/resources/resources/bin/oh2_dir_layout @@ -16,11 +16,11 @@ if [ -z ${OSH_RUNTIME} ]; then fi if [ -z ${OSH_USERDATA} ]; then - export OSH_USERDATA="${OSH_HOME}/data" + export OSH_USERDATA="${OSH_HOME}/userdata" fi if [ -z ${OSH_LOGDIR} ]; then - export OSH_LOGDIR="${OSH_USERDATA}/log" + export OSH_LOGDIR="${OSH_RUNTIME}/log" fi if [ -z ${OSH_BACKUPS} ]; then From f4ce35fa20ef09248d068a823fa0723630d2c0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Tue, 7 Sep 2021 21:44:23 +0200 Subject: [PATCH 07/14] Make sure ProfileTypeBuilder is an class. Currently twisting it into interface causes runtime incompatibility within OH. --- .../ProfileTypeBuilderFactoryImpl.java | 4 +- .../profiles/ProfileTypeBuilderImpl.java | 25 ++--- .../thing/profiles/ProfileTypeBuilder.java | 92 +++++++++++++++++-- 3 files changed, 91 insertions(+), 30 deletions(-) diff --git a/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/ProfileTypeBuilderFactoryImpl.java b/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/ProfileTypeBuilderFactoryImpl.java index 4d6ded5ce..4ae5ba80e 100644 --- a/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/ProfileTypeBuilderFactoryImpl.java +++ b/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/ProfileTypeBuilderFactoryImpl.java @@ -31,12 +31,12 @@ public final class ProfileTypeBuilderFactoryImpl implements ProfileTypeBuilderFa @Override public ProfileTypeBuilder newState(ProfileTypeUID profileTypeUID, String label) { - return new ProfileTypeBuilderImpl.StateProfileTypeBuilder(profileTypeUID, label); + return ProfileTypeBuilder.newState(profileTypeUID, label); } @Override public ProfileTypeBuilder newTrigger(ProfileTypeUID profileTypeUID, String label) { - return new ProfileTypeBuilderImpl.TriggerProfileTypeBuilder(profileTypeUID, label); + return ProfileTypeBuilder.newTrigger(profileTypeUID, label); } } diff --git a/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/ProfileTypeBuilderImpl.java b/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/ProfileTypeBuilderImpl.java index ffadce4ac..638dd090d 100644 --- a/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/ProfileTypeBuilderImpl.java +++ b/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/internal/profiles/ProfileTypeBuilderImpl.java @@ -18,10 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.thing.profiles.ProfileType; -import org.openhab.core.thing.profiles.ProfileTypeBuilder; -import org.openhab.core.thing.profiles.ProfileTypeBuilderFactory; import org.openhab.core.thing.profiles.ProfileTypeUID; -import org.openhab.core.thing.profiles.StateProfile; import org.openhab.core.thing.profiles.StateProfileType; import org.openhab.core.thing.profiles.TriggerProfileType; import org.openhab.core.thing.type.ChannelTypeUID; @@ -36,7 +33,7 @@ * @param the concrete {@link ProfileType} sub-interface. */ @NonNullByDefault -public abstract class ProfileTypeBuilderImpl implements ProfileTypeBuilder { +public abstract class ProfileTypeBuilderImpl { protected final ProfileTypeUID profileTypeUID; protected final Collection supportedItemTypes = new HashSet<>(); @@ -49,38 +46,32 @@ public abstract class ProfileTypeBuilderImpl implements P this.label = label; } - @Override - public ProfileTypeBuilder withSupportedItemTypes(String... itemType) { + public ProfileTypeBuilderImpl withSupportedItemTypes(String... itemType) { supportedItemTypes.addAll(Arrays.asList(itemType)); return this; } - @Override - public ProfileTypeBuilder withSupportedItemTypes(Collection itemTypes) { + public ProfileTypeBuilderImpl withSupportedItemTypes(Collection itemTypes) { supportedItemTypes.addAll(itemTypes); return this; } - @Override - public ProfileTypeBuilder withSupportedChannelTypeUIDs(ChannelTypeUID... channelTypeUIDs) { + public ProfileTypeBuilderImpl withSupportedChannelTypeUIDs(ChannelTypeUID... channelTypeUIDs) { supportedChannelTypeUIDs.addAll(Arrays.asList(channelTypeUIDs)); return this; } - @Override - public ProfileTypeBuilder withSupportedChannelTypeUIDs(Collection channelTypeUIDs) { + public ProfileTypeBuilderImpl withSupportedChannelTypeUIDs(Collection channelTypeUIDs) { supportedChannelTypeUIDs.addAll(channelTypeUIDs); return this; } - @Override - public ProfileTypeBuilder withSupportedItemTypesOfChannel(String... supportedItemTypesOfChannel) { + public ProfileTypeBuilderImpl withSupportedItemTypesOfChannel(String... supportedItemTypesOfChannel) { this.supportedItemTypesOfChannel.addAll(Arrays.asList(supportedItemTypesOfChannel)); return this; } - @Override - public ProfileTypeBuilder withSupportedItemTypesOfChannel(Collection supportedItemTypesOfChannel) { + public ProfileTypeBuilderImpl withSupportedItemTypesOfChannel(Collection supportedItemTypesOfChannel) { this.supportedItemTypesOfChannel.addAll(supportedItemTypesOfChannel); return this; } @@ -91,7 +82,6 @@ public StateProfileTypeBuilder(ProfileTypeUID profileTypeUID, String label) { super(profileTypeUID, label); } - @Override public StateProfileType build() { return new StateProfileTypeImpl(profileTypeUID, label, supportedItemTypes, supportedItemTypesOfChannel); } @@ -103,7 +93,6 @@ public TriggerProfileTypeBuilder(ProfileTypeUID profileTypeUID, String label) { super(profileTypeUID, label); } - @Override public TriggerProfileType build() { return new TriggerProfileTypeImpl(profileTypeUID, label, supportedItemTypes, supportedChannelTypeUIDs); } diff --git a/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/profiles/ProfileTypeBuilder.java b/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/profiles/ProfileTypeBuilder.java index 3995bab16..841c9de7c 100644 --- a/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/profiles/ProfileTypeBuilder.java +++ b/bundles/org.opensmarthouse.core.thing/src/main/java/org/openhab/core/thing/profiles/ProfileTypeBuilder.java @@ -12,8 +12,13 @@ */ package org.openhab.core.thing.profiles; +import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; + import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.internal.profiles.StateProfileTypeImpl; +import org.openhab.core.thing.internal.profiles.TriggerProfileTypeImpl; import org.openhab.core.thing.type.ChannelTypeUID; /** @@ -22,12 +27,58 @@ * It can be used to obtain instances instead of implementing any of the interfaces derived from {@link ProfileType}. * * @author Simon Kaufmann - Initial contribution - * @author Łukasz Dywicki - Refactoring to interface, extraction of {@link ProfileTypeBuilderFactory}. * * @param the concrete {@link ProfileType} sub-interface. */ @NonNullByDefault -public interface ProfileTypeBuilder { +public final class ProfileTypeBuilder { + + @FunctionalInterface + private interface ProfileTypeFactory { + T create(ProfileTypeUID profileTypeUID, String label, Collection supportedItemTypes, + Collection supportedItemTypesOfChannel, Collection supportedChannelTypeUIDs); + } + + private final ProfileTypeFactory profileTypeFactory; + private final ProfileTypeUID profileTypeUID; + private final Collection supportedItemTypes = new HashSet<>(); + private final Collection supportedItemTypesOfChannel = new HashSet<>(); + private final Collection supportedChannelTypeUIDs = new HashSet<>(); + private final String label; + + private ProfileTypeBuilder(ProfileTypeUID profileTypeUID, String label, ProfileTypeFactory profileTypeFactory) { + this.profileTypeFactory = profileTypeFactory; + this.profileTypeUID = profileTypeUID; + this.label = label; + } + + /** + * Obtain a new builder for a {@link StateProfileType} instance. + * + * @param profileTypeUID the {@link ProfileTypeUID} + * @param label a human-readable label + * @return the new builder instance + */ + public static ProfileTypeBuilder newState(ProfileTypeUID profileTypeUID, String label) { + return new ProfileTypeBuilder<>(profileTypeUID, label, + (leProfileTypeUID, leLabel, leSupportedItemTypes, leSupportedItemTypesOfChannel, + leSupportedChannelTypeUIDs) -> new StateProfileTypeImpl(leProfileTypeUID, leLabel, + leSupportedItemTypes, leSupportedItemTypesOfChannel)); + } + + /** + * Obtain a new builder for a {@link TriggerProfileType} instance. + * + * @param profileTypeUID the {@link ProfileTypeUID} + * @param label a human-readable label + * @return the new builder instance + */ + public static ProfileTypeBuilder newTrigger(ProfileTypeUID profileTypeUID, String label) { + return new ProfileTypeBuilder<>(profileTypeUID, label, + (leProfileTypeUID, leLabel, leSupportedItemTypes, leSupportedItemTypesOfChannel, + leSupportedChannelTypeUIDs) -> new TriggerProfileTypeImpl(leProfileTypeUID, leLabel, + leSupportedItemTypes, leSupportedChannelTypeUIDs)); + } /** * Declare that the given item type(s) are supported by a profile of this type. @@ -35,15 +86,21 @@ public interface ProfileTypeBuilder { * @param itemType * @return the builder itself */ - ProfileTypeBuilder withSupportedItemTypes(String... itemType); + public ProfileTypeBuilder withSupportedItemTypes(String... itemType) { + supportedItemTypes.addAll(Arrays.asList(itemType)); + return this; + } /** * Declare that the given item type(s) are supported by a profile of this type. * - * @param itemTypes + * @param itemType * @return the builder itself */ - ProfileTypeBuilder withSupportedItemTypes(Collection itemTypes); + public ProfileTypeBuilder withSupportedItemTypes(Collection itemTypes) { + supportedItemTypes.addAll(itemTypes); + return this; + } /** * Declare that the given channel type(s) are supported by a profile of this type. @@ -51,7 +108,10 @@ public interface ProfileTypeBuilder { * @param channelTypeUIDs * @return the builder itself */ - ProfileTypeBuilder withSupportedChannelTypeUIDs(ChannelTypeUID... channelTypeUIDs); + public ProfileTypeBuilder withSupportedChannelTypeUIDs(ChannelTypeUID... channelTypeUIDs) { + supportedChannelTypeUIDs.addAll(Arrays.asList(channelTypeUIDs)); + return this; + } /** * Declare that the given channel type(s) are supported by a profile of this type. @@ -59,7 +119,10 @@ public interface ProfileTypeBuilder { * @param channelTypeUIDs * @return the builder itself */ - ProfileTypeBuilder withSupportedChannelTypeUIDs(Collection channelTypeUIDs); + public ProfileTypeBuilder withSupportedChannelTypeUIDs(Collection channelTypeUIDs) { + supportedChannelTypeUIDs.addAll(channelTypeUIDs); + return this; + } /** * Declare that channels with these item type(s) are compatible with profiles of this type. @@ -67,7 +130,10 @@ public interface ProfileTypeBuilder { * @param supportedItemTypesOfChannel item types on channel to which this profile type is compatible with * @return the builder itself */ - ProfileTypeBuilder withSupportedItemTypesOfChannel(String... supportedItemTypesOfChannel); + public ProfileTypeBuilder withSupportedItemTypesOfChannel(String... supportedItemTypesOfChannel) { + this.supportedItemTypesOfChannel.addAll(Arrays.asList(supportedItemTypesOfChannel)); + return this; + } /** * Declare that channels with these item type(s) are compatible with profiles of this type. @@ -75,13 +141,19 @@ public interface ProfileTypeBuilder { * @param supportedItemTypesOfChannel item types on channel to which this profile type is compatible with * @return the builder itself */ - ProfileTypeBuilder withSupportedItemTypesOfChannel(Collection supportedItemTypesOfChannel); + public ProfileTypeBuilder withSupportedItemTypesOfChannel(Collection supportedItemTypesOfChannel) { + this.supportedItemTypesOfChannel.addAll(supportedItemTypesOfChannel); + return this; + } /** * Create a profile type instance with the previously given parameters. * * @return the according subtype of {@link ProfileType} */ - T build(); + public T build() { + return profileTypeFactory.create(profileTypeUID, label, supportedItemTypes, supportedItemTypesOfChannel, + supportedChannelTypeUIDs); + } } From d9ea96e03e03c4336b21970ad861037e19a21276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Thu, 23 Sep 2021 23:49:41 +0200 Subject: [PATCH 08/14] Introduce SUB function. --- .../internal/items/GroupFunctionHelper.java | 16 +++- .../core/internal/items/function/Sub.java | 81 +++++++++++++++++++ .../core/internal/items/function/Sum.java | 19 ++++- .../items/function/dimensional/Sub.java | 73 +++++++++++++++++ .../items/function/dimensional/Sum.java | 19 ++++- .../function/ArithmeticGroupFunctionTest.java | 2 +- ...antityTypeArithmeticGroupFunctionTest.java | 9 ++- 7 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/Sub.java create mode 100644 bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/dimensional/Sub.java diff --git a/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/GroupFunctionHelper.java b/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/GroupFunctionHelper.java index c55f9b2f8..4964df99a 100755 --- a/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/GroupFunctionHelper.java +++ b/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/GroupFunctionHelper.java @@ -30,11 +30,13 @@ import org.openhab.core.internal.items.function.dimensional.Avg; import org.openhab.core.internal.items.function.dimensional.Max; import org.openhab.core.internal.items.function.dimensional.Min; +import org.openhab.core.internal.items.function.dimensional.Sub; import org.openhab.core.internal.items.function.dimensional.Sum; import org.openhab.core.items.GroupFunction; import org.openhab.core.items.Item; import org.openhab.core.items.dto.GroupFunctionDTO; import org.openhab.core.library.items.NumberItem; +import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.types.State; import org.openhab.core.types.TypeParser; @@ -103,7 +105,12 @@ private GroupFunction createDimensionGroupFunction(GroupFunctionDTO function, @N case "AVG": return new Avg(dimension); case "SUM": - return new Sum(dimension); + if (function.params != null && function.params.length == 1) { + return new Sum(dimension, OnOffType.from(function.params[0])); + } + return new Sum(dimension, OnOffType.ON); + case "SUB": + return new Sub(dimension); case "MIN": return new Min(dimension); case "MAX": @@ -161,7 +168,12 @@ private GroupFunction createDefaultGroupFunction(GroupFunctionDTO function, @Nul case "AVG": return new org.openhab.core.internal.items.function.Avg(); case "SUM": - return new org.openhab.core.internal.items.function.Sum(); + if (function.params != null && function.params.length == 1) { + return new org.openhab.core.internal.items.function.Sum(OnOffType.from(function.params[0])); + } + return new org.openhab.core.internal.items.function.Sum(OnOffType.ON); + case "SUB": + return new org.openhab.core.internal.items.function.Sub(); case "MIN": return new org.openhab.core.internal.items.function.Min(); case "MAX": diff --git a/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/Sub.java b/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/Sub.java new file mode 100644 index 000000000..cc31eacf4 --- /dev/null +++ b/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/Sub.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.internal.items.function; + +import java.math.BigDecimal; +import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.items.GroupFunction; +import org.openhab.core.items.Item; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * This subtracts values between members of the group. + * + * In this regard this group kind is dependant on the order its members are defined! + * + * @author Łukasz Dywicki - Initial contribution + */ +@NonNullByDefault +public class Sub implements GroupFunction { + + public Sub() { + } + + @Override + public State calculate(@Nullable Set items) { + if (items == null || items.isEmpty()) { + return UnDefType.UNDEF; + } + + BigDecimal sub = null; + for (Item item : items) { + DecimalType itemState = item.getStateAs(DecimalType.class); + if (itemState == null) { + // we got an empty value which means that we can't reliably calculate end value + // we break the loop here to avoid invalid results + return UnDefType.UNDEF; + } + + if (sub == null) { + sub = itemState.toBigDecimal(); + } else { + sub = sub.subtract(itemState.toBigDecimal()); + } + } + return new DecimalType(sub); + } + + @Override + public @Nullable T getStateAs(@Nullable Set items, Class stateClass) { + State state = calculate(items); + if (stateClass.isInstance(state)) { + return stateClass.cast(state); + } else { + return null; + } + } + + @Override + public State[] getParameters() { + return new State[0]; + } + + @Override + public String toString() { + return "SUB"; + } +} \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/Sum.java b/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/Sum.java index 411bc9789..e56c2e189 100644 --- a/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/Sum.java +++ b/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/Sum.java @@ -19,6 +19,8 @@ import org.openhab.core.items.GroupFunction; import org.openhab.core.items.Item; import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.UnDefType; import org.openhab.core.types.State; /** @@ -31,7 +33,10 @@ @NonNullByDefault public class Sum implements GroupFunction { - public Sum() { + private final OnOffType allMembers; + + public Sum(OnOffType allMembers) { + this.allMembers = allMembers; } @Override @@ -42,6 +47,10 @@ public State calculate(@Nullable Set items) { DecimalType itemState = item.getStateAs(DecimalType.class); if (itemState != null) { sum = sum.add(itemState.toBigDecimal()); + } else { + if (OnOffType.ON == allMembers) { + return UnDefType.UNDEF; + } } } } @@ -60,6 +69,12 @@ public State calculate(@Nullable Set items) { @Override public State[] getParameters() { - return new State[0]; + return new State[] { allMembers}; } + + @Override + public String toString() { + return "SUM(" + allMembers + ")"; + } + } \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/dimensional/Sub.java b/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/dimensional/Sub.java new file mode 100644 index 000000000..d69959f54 --- /dev/null +++ b/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/dimensional/Sub.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.internal.items.function.dimensional; + +import java.util.Set; +import javax.measure.Quantity; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.items.Item; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * Subtract value between members {@link QuantityType}. + * + * @author Łukas Dywicki - Initial contribution + */ +@NonNullByDefault +public class Sub extends DimensionalGroupFunction { + + public Sub(Class> dimension) { + super(dimension); + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public State calculate(@Nullable Set items) { + if (items == null || items.isEmpty()) { + return UnDefType.UNDEF; + } + + QuantityType sub = null; + for (Item item : items) { + if (isSameDimension(item)) { + QuantityType itemState = item.getStateAs(QuantityType.class); + if (itemState == null) { + // we got an empty value which means that we can't reliably calculate end value + // we break the loop here to avoid invalid results + return UnDefType.UNDEF; + } + + if (sub == null) { + sub = itemState; // initialise the sub from the first item + } else { + if (sub.getUnit().isCompatible(itemState.getUnit())) { + sub = sub.subtract(itemState); + } else { + // we got an incompatible value, we must break the loop here to avoid invalid results + return UnDefType.UNDEF; + } + } + } + } + + return sub; + } + + @Override + public String toString() { + return "SUB()"; + } +} \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/dimensional/Sum.java b/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/dimensional/Sum.java index 4ddae66ea..0c9d46051 100644 --- a/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/dimensional/Sum.java +++ b/bundles/org.opensmarthouse.core.item.core/src/main/java/org/openhab/core/internal/items/function/dimensional/Sum.java @@ -17,6 +17,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.items.Item; +import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; @@ -29,8 +30,11 @@ @NonNullByDefault public class Sum extends DimensionalGroupFunction { - public Sum(Class> dimension) { + private final OnOffType allMembers; + + public Sum(Class> dimension, OnOffType allMembers) { super(dimension); + this.allMembers = allMembers; } @Override @@ -50,6 +54,10 @@ public State calculate(@Nullable Set items) { } else if (sum.getUnit().isCompatible(itemState.getUnit())) { sum = sum.add(itemState); } + } else { + if (allMembers == OnOffType.ON) { + return UnDefType.UNDEF; + } } } } @@ -57,4 +65,13 @@ public State calculate(@Nullable Set items) { return sum != null ? sum : UnDefType.UNDEF; } + @Override + public State[] getParameters() { + return new State[] {allMembers}; + } + + @Override + public String toString() { + return "SUM(" + allMembers + ")"; + } } \ No newline at end of file diff --git a/bundles/org.opensmarthouse.core.item.core/src/test/java/org/openhab/core/internal/items/function/ArithmeticGroupFunctionTest.java b/bundles/org.opensmarthouse.core.item.core/src/test/java/org/openhab/core/internal/items/function/ArithmeticGroupFunctionTest.java index eeb480220..1b2c2d4bd 100755 --- a/bundles/org.opensmarthouse.core.item.core/src/test/java/org/openhab/core/internal/items/function/ArithmeticGroupFunctionTest.java +++ b/bundles/org.opensmarthouse.core.item.core/src/test/java/org/openhab/core/internal/items/function/ArithmeticGroupFunctionTest.java @@ -225,7 +225,7 @@ public void testSumFunction() { items.add(new TestItem("TestItem4", UnDefType.UNDEF)); items.add(new TestItem("TestItem5", new DecimalType("122.41"))); - function = new Sum(); + function = new Sum(OnOffType.OFF); State state = function.calculate(items); assertEquals(new DecimalType("234.95"), state); diff --git a/bundles/org.opensmarthouse.core.item.core/src/test/java/org/openhab/core/internal/items/function/QuantityTypeArithmeticGroupFunctionTest.java b/bundles/org.opensmarthouse.core.item.core/src/test/java/org/openhab/core/internal/items/function/QuantityTypeArithmeticGroupFunctionTest.java index f29ef8f06..499186919 100755 --- a/bundles/org.opensmarthouse.core.item.core/src/test/java/org/openhab/core/internal/items/function/QuantityTypeArithmeticGroupFunctionTest.java +++ b/bundles/org.opensmarthouse.core.item.core/src/test/java/org/openhab/core/internal/items/function/QuantityTypeArithmeticGroupFunctionTest.java @@ -38,6 +38,7 @@ import org.openhab.core.items.Item; import org.openhab.core.library.CoreItemFactory; import org.openhab.core.library.items.NumberItem; +import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; @@ -71,7 +72,7 @@ public void testSumFunctionQuantityType() { items.add(createNumberItem("TestItem4", Temperature.class, UnDefType.UNDEF)); items.add(createNumberItem("TestItem5", Temperature.class, new QuantityType<>("122.41 °C"))); - function = new Sum(Temperature.class); + function = new Sum(Temperature.class, OnOffType.OFF); State state = function.calculate(items); assertEquals(new QuantityType<>("234.95 °C"), state); @@ -85,7 +86,7 @@ public void testSumFunctionQuantityTypeDifferentUnits() { items.add(createNumberItem("TestItem4", Temperature.class, UnDefType.UNDEF)); items.add(createNumberItem("TestItem5", Temperature.class, new QuantityType<>("395.56 K"))); - function = new Sum(Temperature.class); + function = new Sum(Temperature.class, OnOffType.OFF); State state = function.calculate(items); assertEquals(new QuantityType<>("234.95 °C"), state); @@ -98,7 +99,7 @@ public void testSumFunctionQuantityTypeIncompatibleUnits() { items.add(createNumberItem("TestItem2", Temperature.class, UnDefType.NULL)); items.add(createNumberItem("TestItem3", Pressure.class, new QuantityType<>("192.2 hPa"))); - function = new Sum(Temperature.class); + function = new Sum(Temperature.class, OnOffType.OFF); State state = function.calculate(items); assertEquals(new QuantityType<>("23.54 °C"), state); @@ -244,7 +245,7 @@ public void testSumFunctionQuantityTypeWithGroups() { items.add(createNumberItem("TestItem1", Power.class, new QuantityType<>("5 W"))); items.add(createGroupItem("TestGroup1", Power.class, new QuantityType<>("5 W"))); - function = new Sum(Power.class); + function = new Sum(Power.class, OnOffType.OFF); State state = function.calculate(items); assertEquals(new QuantityType<>("10 W"), state); From 8930f3d3709f88b54ffcb8993c83b19e31d8c552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Thu, 23 Sep 2021 23:52:59 +0200 Subject: [PATCH 09/14] Actually call persistence filters if they're a predicates. --- .../persistence/PersistenceItemConfiguration.java | 5 +++-- .../persistence/internal/PersistenceManagerImpl.java | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceItemConfiguration.java b/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceItemConfiguration.java index 26e3cf586..aad266728 100755 --- a/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceItemConfiguration.java +++ b/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/PersistenceItemConfiguration.java @@ -60,7 +60,8 @@ public List getItems() { @Override public String toString() { return String.format("%s [items=%s, alias=%s, strategies=%s, filters=%s]", getClass().getSimpleName(), - Arrays.toString(items.toArray()), alias, Arrays.toString(strategies.toArray()), - Arrays.toString(filters.toArray())); + Arrays.toString(items.toArray()), alias, + strategies == null ? "" : Arrays.toString(strategies.toArray()), + filters == null ? "" : Arrays.toString(filters.toArray())); } } diff --git a/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java b/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java index 5f35e64d3..28681461c 100755 --- a/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java +++ b/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java @@ -25,6 +25,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.function.Predicate; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.common.NamedThreadFactory; @@ -38,6 +39,7 @@ import org.openhab.core.items.StateChangeListener; import org.openhab.core.persistence.FilterCriteria; import org.openhab.core.persistence.HistoricItem; +import org.openhab.core.persistence.PersistenceFilter; import org.openhab.core.persistence.PersistenceItemConfiguration; import org.openhab.core.persistence.PersistenceManager; import org.openhab.core.persistence.PersistenceService; @@ -189,6 +191,16 @@ private boolean hasStrategy(PersistenceServiceConfiguration config, PersistenceI */ private boolean appliesToItem(PersistenceItemConfiguration config, Item item) { for (PersistenceConfig itemCfg : config.getItems()) { + // handling of items excluded from certain configuration, a filter works as an removal criteria! + if (config.getFilters() != null) { + for (PersistenceFilter filter : config.getFilters()) { + if (filter instanceof Predicate) { + if (((Predicate) filter).test(item)) { + return false; + } + } + } + } if (itemCfg instanceof PersistenceAllConfig) { return true; } From 7b309c6e13a7e5ff3c88d69ef1d58403c1b900b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Thu, 23 Sep 2021 23:58:24 +0200 Subject: [PATCH 10/14] Reorganize UI providers a little bit. Make sure that UI components can be provided through other suppliers than system storage. --- .../core/ui/components/UIProvider.java | 24 ++++++++ .../components/UIComponentProvider.java | 8 ++- .../UIComponentRegistryFactoryImpl.java | 60 ++++++++++++++++--- .../components/UIComponentRegistryImpl.java | 31 +++++++--- .../provider/ManagedPageProvider.java | 32 ++++++++++ .../provider/ManagedWidgetProvider.java | 32 ++++++++++ 6 files changed, 172 insertions(+), 15 deletions(-) create mode 100755 bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/components/UIProvider.java create mode 100644 bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/provider/ManagedPageProvider.java create mode 100644 bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/provider/ManagedWidgetProvider.java diff --git a/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/components/UIProvider.java b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/components/UIProvider.java new file mode 100755 index 000000000..0d55fbbc0 --- /dev/null +++ b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/components/UIProvider.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.ui.components; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.common.registry.Provider; +import org.openhab.core.common.registry.Registry; + +@NonNullByDefault +public interface UIProvider extends Provider { + + String getNamespace(); + +} diff --git a/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentProvider.java b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentProvider.java index 297ef9e8b..892a03787 100644 --- a/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentProvider.java +++ b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentProvider.java @@ -23,6 +23,7 @@ import org.openhab.core.storage.Storage; import org.openhab.core.storage.StorageService; import org.openhab.core.ui.components.RootUIComponent; +import org.openhab.core.ui.components.UIProvider; /** * A namespace-specific {@link ManagedProvider} for UI components. @@ -31,7 +32,7 @@ */ @NonNullByDefault public class UIComponentProvider extends AbstractProvider - implements ManagedProvider { + implements ManagedProvider, UIProvider { private String namespace; private volatile Storage storage; @@ -48,6 +49,11 @@ public UIComponentProvider(String namespace, StorageService storageService) { this.getClass().getClassLoader()); } + @Override + public String getNamespace() { + return namespace; + } + @Override public Collection getAll() { List components = new ArrayList<>(); diff --git a/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentRegistryFactoryImpl.java b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentRegistryFactoryImpl.java index 66f49f5a2..62a125943 100644 --- a/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentRegistryFactoryImpl.java +++ b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentRegistryFactoryImpl.java @@ -13,33 +13,79 @@ package org.openhab.core.ui.internal.components; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.core.common.registry.ManagedProvider; +import org.openhab.core.common.registry.Provider; import org.openhab.core.storage.StorageService; +import org.openhab.core.ui.components.RootUIComponent; import org.openhab.core.ui.components.UIComponentRegistryFactory; +import org.openhab.core.ui.components.UIProvider; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; /** - * Implementation for a {@link UIComponentRegistryFactory} using a {@link StorageService} and a - * {@link UIComponentProvider}. + * Implementation for a {@link UIComponentRegistryFactory} using a set of {@link UIComponentProvider}. * * @author Yannick Schaus - Initial contribution + * @author Łukasz Dywicki - Removed explicit dependency on storage providers. */ @Component(service = UIComponentRegistryFactory.class, immediate = true) public class UIComponentRegistryFactoryImpl implements UIComponentRegistryFactory { - Map registries = new HashMap<>(); - - @Reference - StorageService storageService; + Map registries = new ConcurrentHashMap<>(); + Map> providers = new ConcurrentHashMap<>(); @Override public UIComponentRegistryImpl getRegistry(String namespace) { UIComponentRegistryImpl registry = registries.get(namespace); if (registry == null) { - registry = new UIComponentRegistryImpl(namespace, storageService); + Set namespaceProviders = this.providers.get(namespace); + ManagedProvider<@NonNull RootUIComponent, @NonNull String> managedProvider = null; + if (namespaceProviders != null) { + for (UIProvider provider : namespaceProviders) { + if (provider instanceof ManagedProvider) { + managedProvider = (ManagedProvider<@NonNull RootUIComponent, @NonNull String>) provider; + break; + } + } + } + registry = new UIComponentRegistryImpl(namespace, managedProvider, namespaceProviders); registries.put(namespace, registry); } return registry; } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + void addProvider(UIProvider provider) { + if (registries.containsKey(provider.getNamespace())) { + registries.get(provider.getNamespace()).addProvider(provider); + } + registerProvider(provider); + } + + void removeProvider(UIProvider provider) { + if (registries.containsKey(provider.getNamespace())) { + registries.get(provider.getNamespace()).removeProvider(provider); + } + deregisterProvider(provider); + } + + private void registerProvider(UIProvider provider) { + if (!providers.containsKey(provider.getNamespace())) { + providers.put(provider.getNamespace(), new HashSet<>()); + } + providers.get(provider.getNamespace()).add(provider); + } + + private void deregisterProvider(UIProvider provider) { + if (providers.containsKey(provider.getNamespace())) { + providers.get(provider.getNamespace()).remove(provider); + } + } } diff --git a/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentRegistryImpl.java b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentRegistryImpl.java index 5d2c9ad4c..dfd1d7a25 100644 --- a/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentRegistryImpl.java +++ b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/UIComponentRegistryImpl.java @@ -12,11 +12,16 @@ */ package org.openhab.core.ui.internal.components; +import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.common.registry.AbstractRegistry; +import org.openhab.core.common.registry.ManagedProvider; +import org.openhab.core.common.registry.Provider; import org.openhab.core.storage.StorageService; import org.openhab.core.ui.components.RootUIComponent; import org.openhab.core.ui.components.UIComponentRegistry; +import org.openhab.core.ui.components.UIProvider; /** * Implementation of a {@link UIComponentRegistry} using a {@link UIComponentProvider}. @@ -29,20 +34,32 @@ public class UIComponentRegistryImpl extends AbstractRegistry managedProvider, @Nullable Set providers) { super(null); this.namespace = namespace; - this.storageService = storageService; - UIComponentProvider provider = new UIComponentProvider(namespace, storageService); - addProvider(provider); - setManagedProvider(provider); + + if (managedProvider != null) { + setManagedProvider(managedProvider); + } + if (providers != null && !providers.isEmpty()) { + for (Provider provider : providers) { + addProvider(provider); + } + } + } + + public void addProvider(Provider provider) { + super.addProvider(provider); } + + public void removeProvider(Provider provider) { + super.removeProvider(provider); + } + } diff --git a/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/provider/ManagedPageProvider.java b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/provider/ManagedPageProvider.java new file mode 100644 index 000000000..8a5c20dec --- /dev/null +++ b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/provider/ManagedPageProvider.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.ui.internal.components.provider; + +import org.openhab.core.common.registry.ManagedProvider; +import org.openhab.core.common.registry.Provider; +import org.openhab.core.storage.StorageService; +import org.openhab.core.ui.components.UIProvider; +import org.openhab.core.ui.internal.components.UIComponentProvider; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +@Component(service = {Provider.class, ManagedProvider.class, UIProvider.class}) +public class ManagedPageProvider extends UIComponentProvider { + + @Activate + public ManagedPageProvider(@Reference StorageService storageService) { + super("ui:page", storageService); + } + +} diff --git a/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/provider/ManagedWidgetProvider.java b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/provider/ManagedWidgetProvider.java new file mode 100644 index 000000000..497712135 --- /dev/null +++ b/bundles/org.opensmarthouse.core.ui/src/main/java/org/openhab/core/ui/internal/components/provider/ManagedWidgetProvider.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.ui.internal.components.provider; + +import org.openhab.core.common.registry.ManagedProvider; +import org.openhab.core.common.registry.Provider; +import org.openhab.core.storage.StorageService; +import org.openhab.core.ui.components.UIProvider; +import org.openhab.core.ui.internal.components.UIComponentProvider; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +@Component(service = {Provider.class, ManagedProvider.class, UIProvider.class}) +public class ManagedWidgetProvider extends UIComponentProvider { + + @Activate + public ManagedWidgetProvider(@Reference StorageService storageService) { + super("ui:widget", storageService); + } + +} From 9aad8b71249ee51f9b47ffddcb2abf2ad6c1abdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Fri, 24 Sep 2021 12:44:03 +0200 Subject: [PATCH 11/14] Get rid of ancient plugin in favor of dedicated maven build time property. --- pom.xml | 38 ++++---------------------------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/pom.xml b/pom.xml index e940b733c..b4b1365fe 100755 --- a/pom.xml +++ b/pom.xml @@ -532,19 +532,6 @@ Import-Package: \\ - - - org.commonjava.maven.plugins - directory-maven-plugin - [0.3.1,) - - highest-basedir - - - - - - @@ -555,10 +542,10 @@ Import-Package: \\ sat-plugin ${sat.version} - ${basedirRoot}/tools/static-code-analysis/pmd/suppressions.properties - ${basedirRoot}/tools/static-code-analysis/checkstyle/ruleset.properties - ${basedirRoot}/tools/static-code-analysis/checkstyle/suppressions.xml - ${basedirRoot}/tools/static-code-analysis/spotbugs/suppressions.xml + ${maven.multiModuleProjectDirectory}/tools/static-code-analysis/pmd/suppressions.properties + ${maven.multiModuleProjectDirectory}/tools/static-code-analysis/checkstyle/ruleset.properties + ${maven.multiModuleProjectDirectory}/tools/static-code-analysis/checkstyle/suppressions.xml + ${maven.multiModuleProjectDirectory}/tools/static-code-analysis/spotbugs/suppressions.xml @@ -668,23 +655,6 @@ Import-Package: \\ - - org.commonjava.maven.plugins - directory-maven-plugin - 0.3.1 - - - directories - - highest-basedir - - initialize - - basedirRoot - - - - org.apache.maven.plugins maven-enforcer-plugin From 6051ac7c1c91d4a59f3d21ed1038dba919df4c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Sat, 25 Sep 2021 00:44:42 +0200 Subject: [PATCH 12/14] Make sure configuration key-value ordering is retained. --- .../java/org/openhab/core/config/core/Configuration.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/org.opensmarthouse.core.config/src/main/java/org/openhab/core/config/core/Configuration.java b/bundles/org.opensmarthouse.core.config/src/main/java/org/openhab/core/config/core/Configuration.java index 876b4b432..94ba22aab 100755 --- a/bundles/org.opensmarthouse.core.config/src/main/java/org/openhab/core/config/core/Configuration.java +++ b/bundles/org.opensmarthouse.core.config/src/main/java/org/openhab/core/config/core/Configuration.java @@ -19,8 +19,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -72,7 +72,7 @@ public Configuration(@Nullable Map properties) { * @param alreadyNormalized flag if the properties are already normalized */ private Configuration(final Map properties, final boolean alreadyNormalized) { - this.properties = synchronizedMap(alreadyNormalized ? new HashMap<>(properties) : normalizeTypes(properties)); + this.properties = synchronizedMap(alreadyNormalized ? new LinkedHashMap<>(properties) : normalizeTypes(properties)); } public T as(Class configurationClass) { @@ -118,7 +118,7 @@ public Collection values() { public Map getProperties() { synchronized (properties) { - return Collections.unmodifiableMap(new HashMap<>(properties)); + return Collections.unmodifiableMap(new LinkedHashMap<>(properties)); } } From 0f0311f3e312f3e8f10cc77d6834e47638b0d1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Sun, 26 Sep 2021 00:17:29 +0200 Subject: [PATCH 13/14] Trace logging for persistence manager. --- .../core/persistence/internal/PersistenceManagerImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java b/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java index 28681461c..c9532b976 100755 --- a/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java +++ b/bundles/org.opensmarthouse.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceManagerImpl.java @@ -151,6 +151,7 @@ private void handleStateEvent(Item item, boolean onlyChanges) { if (hasStrategy(config, itemConfig, onlyChanges ? PersistenceStrategy.Globals.CHANGE : PersistenceStrategy.Globals.UPDATE)) { if (appliesToItem(itemConfig, item)) { + logger.trace("Persistence config {} causes storing of item {} state {}", itemConfig.getAlias(), item.getName(), item.getState()); persistenceServices.get(serviceName).store(item, itemConfig.getAlias()); } } @@ -195,6 +196,7 @@ private boolean appliesToItem(PersistenceItemConfiguration config, Item item) { if (config.getFilters() != null) { for (PersistenceFilter filter : config.getFilters()) { if (filter instanceof Predicate) { + logger.trace("Checking filter {}", filter); if (((Predicate) filter).test(item)) { return false; } From 9e39f63e06b6eda5623b1b6592e7e4b6b476df15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Dywicki?= Date: Wed, 20 Oct 2021 14:17:37 +0200 Subject: [PATCH 14/14] Support for dynamic translation of item labels. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Łukasz Dywicki --- .../core/internal/i18n/I18nProviderImpl.java | 24 ++++++++ .../i18n/LanguageResourceBundleManager.java | 5 +- .../i18n/ResourceTranslationProvider.java | 55 +++++++++++++++++++ .../rest/core/internal/item/ItemResource.java | 8 ++- 4 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 bundles/org.opensmarthouse.core.i18n/src/main/java/org/openhab/core/i18n/ResourceTranslationProvider.java diff --git a/bundles/org.opensmarthouse.core.i18n.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java b/bundles/org.opensmarthouse.core.i18n.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java index 6b4c92e35..56d1dddf2 100755 --- a/bundles/org.opensmarthouse.core.i18n.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java +++ b/bundles/org.opensmarthouse.core.i18n.core/src/main/java/org/openhab/core/internal/i18n/I18nProviderImpl.java @@ -18,11 +18,13 @@ import java.time.DateTimeException; import java.time.ZoneId; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.ResourceBundle; +import java.util.concurrent.CopyOnWriteArrayList; import javax.measure.Quantity; import javax.measure.Unit; import javax.measure.quantity.Angle; @@ -40,6 +42,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocationProvider; +import org.openhab.core.i18n.ResourceTranslationProvider; import org.openhab.core.i18n.TimeZoneProvider; import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.UnitProvider; @@ -57,6 +60,9 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,6 +84,7 @@ * @author Markus Rathgeb - Initial contribution * @author Stefan Triller - Initial contribution * @author Erdoan Hadzhiyusein - Added time zone + * @author Łukasz Dywicki - Added support for dynamic translations */ @Component(immediate = true, configurationPid = I18nProviderImpl.CONFIGURATION_PID, property = { Constants.SERVICE_PID + "=org.openhab.i18n", // @@ -101,6 +108,7 @@ public class I18nProviderImpl // TranslationProvider private final ResourceBundleTracker resourceBundleTracker; + private final List translationProviders = new CopyOnWriteArrayList<>(); // LocationProvider static final String LOCATION = "location"; @@ -146,6 +154,15 @@ protected synchronized void modified(Map config) { setMeasurementSystem(measurementSystem); } + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + void addResourceTranslationProvider(ResourceTranslationProvider translationProvider) { + this.translationProviders.add(translationProvider); + } + + void removeResourceTranslationProvider(ResourceTranslationProvider translationProvider) { + this.translationProviders.remove(translationProvider); + } + private void setMeasurementSystem(@Nullable String measurementSystem) { SystemOfUnits oldMeasurementSystem = this.measurementSystem; @@ -299,6 +316,13 @@ public Locale getLocale() { @Override public @Nullable String getText(@Nullable Bundle bundle, @Nullable String key, @Nullable String defaultText, @Nullable Locale locale) { + for (ResourceTranslationProvider provider : translationProviders) { + String text = provider.getText(key, locale); + if (text != null) { + return text; + } + } + LanguageResourceBundleManager languageResource = this.resourceBundleTracker.getLanguageResource(bundle); if (languageResource != null) { diff --git a/bundles/org.opensmarthouse.core.i18n.core/src/main/java/org/openhab/core/internal/i18n/LanguageResourceBundleManager.java b/bundles/org.opensmarthouse.core.i18n.core/src/main/java/org/openhab/core/internal/i18n/LanguageResourceBundleManager.java index 9426091c3..a2dd186d2 100755 --- a/bundles/org.opensmarthouse.core.i18n.core/src/main/java/org/openhab/core/internal/i18n/LanguageResourceBundleManager.java +++ b/bundles/org.opensmarthouse.core.i18n.core/src/main/java/org/openhab/core/internal/i18n/LanguageResourceBundleManager.java @@ -22,6 +22,7 @@ import java.util.ResourceBundle.Control; import org.openhab.core.common.osgi.ResourceBundleClassLoader; +import org.openhab.core.i18n.ResourceTranslationProvider; import org.openhab.core.i18n.LocaleProvider; import org.osgi.framework.Bundle; @@ -37,7 +38,7 @@ * @author Michael Grammling - Initial contribution * @author Markus Rathgeb - Add locale provider support */ -public class LanguageResourceBundleManager { +public class LanguageResourceBundleManager implements ResourceTranslationProvider { /** The directory within the bundle where the resource files are searched. */ protected static final String RESOURCE_DIRECTORY = "/OH-INF/i18n"; @@ -135,6 +136,7 @@ private List determineResourceNames() { * * @return the translated text, or null if the key could not be translated */ + @Override public String getText(String resource, String key, Locale locale) { if ((key != null) && (!key.isEmpty())) { Locale effectiveLocale = locale != null ? locale : localeProvider.getLocale(); @@ -167,6 +169,7 @@ public String getText(String resource, String key, Locale locale) { * * @return the translated text, or null if the key could not be translated */ + @Override public String getText(String key, Locale locale) { return getText(null, key, locale); } diff --git a/bundles/org.opensmarthouse.core.i18n/src/main/java/org/openhab/core/i18n/ResourceTranslationProvider.java b/bundles/org.opensmarthouse.core.i18n/src/main/java/org/openhab/core/i18n/ResourceTranslationProvider.java new file mode 100644 index 000000000..bcc28c57c --- /dev/null +++ b/bundles/org.opensmarthouse.core.i18n/src/main/java/org/openhab/core/i18n/ResourceTranslationProvider.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.i18n; + +import java.util.Locale; + +/** + * Interface for resource/translation provider which can be registered dynamically as a service. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public interface ResourceTranslationProvider { + + /** + * Returns a translation for the specified key in the specified locale (language) by only + * considering the specified resource section. The resource is equal to a base name and + * therefore it is mapped to one translation package (all files which belong to the base + * name). + *

+ * If no translation could be found, {@code null} is returned. If the location is not specified, the default + * location is used. + * + * @param resource the resource to be used for look-up (could be null or empty) + * @param key the key to be translated (could be null or empty) + * @param locale the locale (language) to be used (could be null) + * + * @return the translated text, or null if the key could not be translated + */ + String getText(String resource, String key, Locale locale); + + /** + * Returns a translation for the specified key in the specified locale (language) + * by considering all resources in the according bundle. + *

+ * If no translation could be found, {@code null} is returned. If the location is not specified, the default + * location is used. + * + * @param key the key to be translated (could be null or empty) + * @param locale the locale (language) to be used (could be null) + * + * @return the translated text, or null if the key could not be translated + */ + String getText(String key, Locale locale); + +} diff --git a/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index 1a93d27a0..34b192e0b 100755 --- a/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.opensmarthouse.core.io.rest.item/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -53,6 +53,7 @@ import org.openhab.core.auth.Permissions; import org.openhab.core.auth.Role; import org.openhab.core.events.EventPublisher; +import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.io.rest.DTOMapper; import org.openhab.core.io.rest.JSONResponse; import org.openhab.core.io.rest.LocaleService; @@ -173,6 +174,7 @@ private static void respectForwarded(final UriBuilder uriBuilder, final @Context private final ItemBuilderFactory itemBuilderFactory; private final ItemRegistry itemRegistry; private final LocaleService localeService; + private final TranslationProvider translationProvider; private final ManagedItemProvider managedItemProvider; private final MetadataRegistry metadataRegistry; private final MetadataSelectorMatcher metadataSelectorMatcher; @@ -186,6 +188,7 @@ public ItemResource(// final @Reference ItemBuilderFactory itemBuilderFactory, // final @Reference ItemRegistry itemRegistry, // final @Reference LocaleService localeService, // + final @Reference TranslationProvider translationProvider, // final @Reference ManagedItemProvider managedItemProvider, final @Reference MetadataRegistry metadataRegistry, final @Reference MetadataSelectorMatcher metadataSelectorMatcher, @@ -196,6 +199,7 @@ public ItemResource(// this.itemBuilderFactory = itemBuilderFactory; this.itemRegistry = itemRegistry; this.localeService = localeService; + this.translationProvider = translationProvider; this.managedItemProvider = managedItemProvider; this.metadataRegistry = metadataRegistry; this.metadataSelectorMatcher = metadataSelectorMatcher; @@ -232,7 +236,8 @@ public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHead .filter(item -> authorizationManager.hasPermission(Permissions.READ, item, authentication)) .map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder, locale)) // .peek(dto -> addMetadata(dto, namespaces, null)) // - .peek(dto -> dto.editable = isEditable(dto.name)); + .peek(dto -> dto.editable = isEditable(dto.name)) + .peek(dto -> dto.label = translationProvider.getText(null, "item." + dto.name, dto.label, locale)); itemStream = dtoMapper.limitToFields(itemStream, fields); return Response.ok(new Stream2JSONInputStream(itemStream)).build(); } @@ -264,6 +269,7 @@ public Response getItemData(final @Context UriInfo uriInfo, final @Context HttpH EnrichedItemDTO dto = EnrichedItemDTOMapper.map(item, true, null, uriBuilder(uriInfo, httpHeaders), locale); addMetadata(dto, namespaces, null); dto.editable = isEditable(dto.name); + dto.label = translationProvider.getText(null, "item." + dto.name, dto.label, locale); return JSONResponse.createResponse(Status.OK, dto, null); } else { return getItemNotFoundResponse(itemname);