Skip to content

Commit a6313ae

Browse files
cdjacksonsplatchcweitkampSonic-Amigakaikreuzer
authored
OH updates (#8)
Updated to bring inline with OH3 Co-authored-by: Łukasz Dywicki <[email protected]> Co-authored-by: Chris Jackson <[email protected]> Co-authored-by: Christoph Weitkamp <[email protected]> Co-authored-by: Sonic-Amiga <[email protected]> Co-authored-by: Kai Kreuzer <[email protected]> Co-authored-by: Laurent Garnier <[email protected]> Co-authored-by: Yannick Schaus <[email protected]> Co-authored-by: silamon <[email protected]> Co-authored-by: Wouter Born <[email protected]> Co-authored-by: Jonathan Gilbert <[email protected]> Co-authored-by: Marcel <[email protected]> Co-authored-by: Markus Rathgeb <[email protected]> Co-authored-by: Christoph Hofmann <[email protected]> Co-authored-by: Philipp Waller <[email protected]> Co-authored-by: Holger Hees <[email protected]>
1 parent 03864ee commit a6313ae

File tree

537 files changed

+19773
-2631
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

537 files changed

+19773
-2631
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.java text=auto
2+
.xml text=auto

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@ Californium.properties
4646

4747
generated/
4848

49+
backport-patches/

bom/opensmarthouse-dependencies/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@
251251
<dependency>
252252
<groupId>org.jmdns</groupId>
253253
<artifactId>jmdns</artifactId>
254-
<version>3.5.5</version>
254+
<version>3.5.6</version>
255255
<scope>compile</scope>
256256
</dependency>
257257

bom/opensmarthouse-test/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,31 @@
2121
<groupId>org.hamcrest</groupId>
2222
<artifactId>hamcrest-library</artifactId>
2323
<version>2.2</version>
24+
<scope>test</scope>
2425
</dependency>
2526
<dependency>
2627
<groupId>org.junit.jupiter</groupId>
2728
<artifactId>junit-jupiter-api</artifactId>
2829
<version>5.6.2</version>
30+
<scope>test</scope>
2931
</dependency>
3032
<dependency>
3133
<groupId>org.junit.jupiter</groupId>
3234
<artifactId>junit-jupiter-params</artifactId>
3335
<version>5.6.2</version>
36+
<scope>test</scope>
3437
</dependency>
3538
<dependency>
3639
<groupId>biz.aQute.bnd</groupId>
3740
<artifactId>biz.aQute.tester.junit-platform</artifactId>
3841
<version>5.1.2</version>
42+
<scope>test</scope>
3943
</dependency>
4044
<dependency>
4145
<groupId>org.mockito</groupId>
4246
<artifactId>mockito-junit-jupiter</artifactId>
4347
<version>3.4.6</version>
48+
<scope>test</scope>
4449
</dependency>
4550

4651
<dependency>

bom/opensmarthouse/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@
9797
<artifactId>org.opensmarthouse.core.audio.core</artifactId>
9898
<version>${project.version}</version>
9999
</dependency>
100+
<dependency>
101+
<groupId>org.opensmarthouse.core.bundles</groupId>
102+
<artifactId>org.opensmarthouse.core.cache</artifactId>
103+
<version>${project.version}</version>
104+
</dependency>
100105
<dependency>
101106
<groupId>org.opensmarthouse.core.bundles</groupId>
102107
<artifactId>org.opensmarthouse.core.ephemeris</artifactId>

bundles/org.opensmarthouse.core.audio.core/src/main/java/org/openhab/core/audio/internal/AudioServlet.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import org.openhab.core.audio.AudioHTTPServer;
3535
import org.openhab.core.audio.AudioStream;
3636
import org.openhab.core.audio.FixedLengthAudioStream;
37-
import org.openhab.core.io.http.servlet.SmartHomeServlet;
37+
import org.openhab.core.io.http.servlet.OpenHABServlet;
3838
import org.osgi.service.component.annotations.Activate;
3939
import org.osgi.service.component.annotations.Component;
4040
import org.osgi.service.component.annotations.Deactivate;
@@ -49,7 +49,7 @@
4949
*/
5050
@NonNullByDefault
5151
@Component
52-
public class AudioServlet extends SmartHomeServlet implements AudioHTTPServer {
52+
public class AudioServlet extends OpenHABServlet implements AudioHTTPServer {
5353

5454
private static final long serialVersionUID = -3364664035854567854L;
5555

@@ -132,15 +132,20 @@ private String substringBefore(String str, String separator) {
132132
}
133133

134134
@Override
135-
protected void doGet(@NonNullByDefault({}) HttpServletRequest req, @NonNullByDefault({}) HttpServletResponse resp)
136-
throws ServletException, IOException {
135+
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
137136
removeTimedOutStreams();
138137

139-
final String streamId = substringBefore(substringAfterLast(req.getRequestURI(), "/"), ".");
138+
String requestURI = req.getRequestURI();
139+
if (requestURI == null) {
140+
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "requestURI is null");
141+
return;
142+
}
143+
144+
final String streamId = substringBefore(substringAfterLast(requestURI, "/"), ".");
140145

141146
try (final InputStream stream = prepareInputStream(streamId, resp)) {
142147
if (stream == null) {
143-
logger.debug("Received request for invalid stream id at {}", req.getRequestURI());
148+
logger.debug("Received request for invalid stream id at {}", requestURI);
144149
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
145150
} else {
146151
stream.transferTo(resp.getOutputStream());

bundles/org.opensmarthouse.core.audio/.classpath

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
44
<attributes>
55
<attribute name="maven.pomderived" value="true"/>
6+
<attribute name="annotationpath" value="target/dependency"/>
67
</attributes>
78
</classpathentry>
89
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
910
<attributes>
1011
<attribute name="maven.pomderived" value="true"/>
12+
<attribute name="annotationpath" value="target/dependency"/>
1113
</attributes>
1214
</classpathentry>
1315
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">

bundles/org.opensmarthouse.core.auth.core/pom.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,28 @@
2626
<groupId>org.osgi</groupId>
2727
<artifactId>osgi.cmpn</artifactId>
2828
</dependency>
29+
30+
<dependency>
31+
<groupId>org.osgi</groupId>
32+
<artifactId>osgi.enroute.hamcrest.wrapper</artifactId>
33+
</dependency>
34+
<dependency>
35+
<groupId>junit</groupId>
36+
<artifactId>junit</artifactId>
37+
</dependency>
38+
<dependency>
39+
<groupId>org.junit.jupiter</groupId>
40+
<artifactId>junit-jupiter-api</artifactId>
41+
</dependency>
42+
<dependency>
43+
<groupId>org.junit.jupiter</groupId>
44+
<artifactId>junit-jupiter-params</artifactId>
45+
</dependency>
46+
<dependency>
47+
<groupId>org.mockito</groupId>
48+
<artifactId>mockito-junit-jupiter</artifactId>
49+
</dependency>
50+
2951
</dependencies>
3052

3153
</project>

bundles/org.opensmarthouse.core.auth.core/src/main/java/org/openhab/core/internal/auth/UserRegistryImpl.java

Lines changed: 129 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@
3131
import org.openhab.core.auth.Credentials;
3232
import org.openhab.core.auth.ManagedUser;
3333
import org.openhab.core.auth.User;
34+
import org.openhab.core.auth.UserApiToken;
35+
import org.openhab.core.auth.UserApiTokenCredentials;
3436
import org.openhab.core.auth.UserProvider;
3537
import org.openhab.core.auth.UserRegistry;
38+
import org.openhab.core.auth.UserSession;
3639
import org.openhab.core.auth.UsernamePasswordCredentials;
3740
import org.openhab.core.common.registry.AbstractRegistry;
3841
import org.osgi.framework.BundleContext;
@@ -56,7 +59,9 @@ public class UserRegistryImpl extends AbstractRegistry<User, String, UserProvide
5659

5760
private final Logger logger = LoggerFactory.getLogger(UserRegistryImpl.class);
5861

59-
private static final int ITERATIONS = 65536;
62+
private static final int PASSWORD_ITERATIONS = 65536;
63+
private static final int APITOKEN_ITERATIONS = 1024;
64+
private static final String APITOKEN_PREFIX = "oh";
6065
private static final int KEY_LENGTH = 512;
6166
private static final String ALGORITHM = "PBKDF2WithHmacSHA512";
6267
private static final SecureRandom RAND = new SecureRandom();
@@ -87,7 +92,7 @@ protected void unsetManagedProvider(ManagedUserProvider managedProvider) {
8792
@Override
8893
public User register(String username, String password, Set<String> roles) {
8994
String passwordSalt = generateSalt(KEY_LENGTH / 8).get();
90-
String passwordHash = hashPassword(password, passwordSalt).get();
95+
String passwordHash = hash(password, passwordSalt, PASSWORD_ITERATIONS).get();
9196
ManagedUser user = new ManagedUser(username, passwordSalt, passwordHash);
9297
user.setRoles(new HashSet<>(roles));
9398
super.add(user);
@@ -106,11 +111,11 @@ private Optional<String> generateSalt(final int length) {
106111
return Optional.of(Base64.getEncoder().encodeToString(salt));
107112
}
108113

109-
private Optional<String> hashPassword(String password, String salt) {
114+
private Optional<String> hash(String password, String salt, int iterations) {
110115
char[] chars = password.toCharArray();
111116
byte[] bytes = salt.getBytes();
112117

113-
PBEKeySpec spec = new PBEKeySpec(chars, bytes, ITERATIONS, KEY_LENGTH);
118+
PBEKeySpec spec = new PBEKeySpec(chars, bytes, iterations, KEY_LENGTH);
114119

115120
Arrays.fill(chars, Character.MIN_VALUE);
116121

@@ -119,7 +124,7 @@ private Optional<String> hashPassword(String password, String salt) {
119124
byte[] securePassword = fac.generateSecret(spec).getEncoded();
120125
return Optional.of(Base64.getEncoder().encodeToString(securePassword));
121126
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
122-
logger.error("Exception encountered in hashPassword", e);
127+
logger.error("Exception encountered while hashing", e);
123128
return Optional.empty();
124129
} finally {
125130
spec.clearPassword();
@@ -128,23 +133,132 @@ private Optional<String> hashPassword(String password, String salt) {
128133

129134
@Override
130135
public Authentication authenticate(Credentials credentials) throws AuthenticationException {
131-
UsernamePasswordCredentials usernamePasswordCreds = (UsernamePasswordCredentials) credentials;
132-
User user = this.get(usernamePasswordCreds.getUsername());
133-
if (user == null) {
134-
throw new AuthenticationException("User not found: " + usernamePasswordCreds.getUsername());
136+
if (credentials instanceof UsernamePasswordCredentials) {
137+
UsernamePasswordCredentials usernamePasswordCreds = (UsernamePasswordCredentials) credentials;
138+
User user = get(usernamePasswordCreds.getUsername());
139+
if (user == null) {
140+
throw new AuthenticationException("User not found: " + usernamePasswordCreds.getUsername());
141+
}
142+
143+
ManagedUser managedUser = (ManagedUser) user;
144+
String hashedPassword = hash(usernamePasswordCreds.getPassword(), managedUser.getPasswordSalt(),
145+
PASSWORD_ITERATIONS).get();
146+
if (!hashedPassword.equals(managedUser.getPasswordHash())) {
147+
throw new AuthenticationException("Wrong password for user " + usernamePasswordCreds.getUsername());
148+
}
149+
150+
return new Authentication(managedUser.getName(), managedUser.getRoles().stream().toArray(String[]::new));
151+
} else if (credentials instanceof UserApiTokenCredentials) {
152+
UserApiTokenCredentials apiTokenCreds = (UserApiTokenCredentials) credentials;
153+
String[] apiTokenParts = apiTokenCreds.getApiToken().split("\\.");
154+
if (apiTokenParts.length != 3 || !APITOKEN_PREFIX.equals(apiTokenParts[0])) {
155+
throw new AuthenticationException("Invalid API token format");
156+
}
157+
for (User user : getAll()) {
158+
ManagedUser managedUser = (ManagedUser) user;
159+
for (UserApiToken userApiToken : managedUser.getApiTokens()) {
160+
// only check if the name in the token matches
161+
if (!userApiToken.getName().equals(apiTokenParts[1])) {
162+
continue;
163+
}
164+
String[] existingTokenHashAndSalt = userApiToken.getApiToken().split(":");
165+
String incomingTokenHash = hash(apiTokenCreds.getApiToken(), existingTokenHashAndSalt[1],
166+
APITOKEN_ITERATIONS).get();
167+
168+
if (incomingTokenHash.equals(existingTokenHashAndSalt[0])) {
169+
return new Authentication(managedUser.getName(),
170+
managedUser.getRoles().stream().toArray(String[]::new), userApiToken.getScope());
171+
}
172+
}
173+
}
174+
175+
throw new AuthenticationException("Unknown API token");
176+
}
177+
178+
throw new IllegalArgumentException("Invalid credential type");
179+
}
180+
181+
@Override
182+
public void changePassword(User user, String newPassword) {
183+
if (!(user instanceof ManagedUser)) {
184+
throw new IllegalArgumentException("User is not managed: " + user.getName());
185+
}
186+
187+
ManagedUser managedUser = (ManagedUser) user;
188+
String passwordSalt = generateSalt(KEY_LENGTH / 8).get();
189+
String passwordHash = hash(newPassword, passwordSalt, PASSWORD_ITERATIONS).get();
190+
managedUser.setPasswordSalt(passwordSalt);
191+
managedUser.setPasswordHash(passwordHash);
192+
update(user);
193+
}
194+
195+
@Override
196+
public void addUserSession(User user, UserSession session) {
197+
if (!(user instanceof ManagedUser)) {
198+
throw new IllegalArgumentException("User is not managed: " + user.getName());
199+
}
200+
201+
ManagedUser managedUser = (ManagedUser) user;
202+
managedUser.getSessions().add(session);
203+
update(user);
204+
}
205+
206+
@Override
207+
public void removeUserSession(User user, UserSession session) {
208+
if (!(user instanceof ManagedUser)) {
209+
throw new IllegalArgumentException("User is not managed: " + user.getName());
135210
}
211+
212+
ManagedUser managedUser = (ManagedUser) user;
213+
managedUser.getSessions().remove(session);
214+
update(user);
215+
}
216+
217+
@Override
218+
public void clearSessions(User user) {
136219
if (!(user instanceof ManagedUser)) {
137-
throw new AuthenticationException("User is not managed: " + usernamePasswordCreds.getUsername());
220+
throw new IllegalArgumentException("User is not managed: " + user.getName());
138221
}
139222

140223
ManagedUser managedUser = (ManagedUser) user;
141-
String hashedPassword = hashPassword(usernamePasswordCreds.getPassword(), managedUser.getPasswordSalt()).get();
142-
if (!hashedPassword.equals(managedUser.getPasswordHash())) {
143-
throw new AuthenticationException("Wrong password for user " + usernamePasswordCreds.getUsername());
224+
managedUser.getSessions().clear();
225+
update(user);
226+
}
227+
228+
@Override
229+
public String addUserApiToken(User user, String name, String scope) {
230+
if (!(user instanceof ManagedUser)) {
231+
throw new IllegalArgumentException("User is not managed: " + user.getName());
232+
}
233+
if (!name.matches("[a-zA-Z0-9]*")) {
234+
throw new IllegalArgumentException("API token name format invalid, alphanumeric characters only");
144235
}
145236

146-
Authentication authentication = new Authentication(managedUser.getName());
147-
return authentication;
237+
ManagedUser managedUser = (ManagedUser) user;
238+
String tokenSalt = generateSalt(KEY_LENGTH / 8).get();
239+
byte[] rnd = new byte[64];
240+
RAND.nextBytes(rnd);
241+
String token = APITOKEN_PREFIX + "." + name + "."
242+
+ Base64.getEncoder().encodeToString(rnd).replaceAll("(\\+|/|=)", "");
243+
String tokenHash = hash(token, tokenSalt, APITOKEN_ITERATIONS).get();
244+
245+
UserApiToken userApiToken = new UserApiToken(name, tokenHash + ":" + tokenSalt, scope);
246+
247+
managedUser.getApiTokens().add(userApiToken);
248+
update(user);
249+
250+
return token;
251+
}
252+
253+
@Override
254+
public void removeUserApiToken(User user, UserApiToken userApiToken) {
255+
if (!(user instanceof ManagedUser)) {
256+
throw new IllegalArgumentException("User is not managed: " + user.getName());
257+
}
258+
259+
ManagedUser managedUser = (ManagedUser) user;
260+
managedUser.getApiTokens().remove(userApiToken);
261+
update(user);
148262
}
149263

150264
@Override

0 commit comments

Comments
 (0)