diff --git a/pom.xml b/pom.xml index 68830f0..f07a6ad 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 4.3.1Secure-b + 4.3.1Secure-c jar HA Bridge @@ -63,7 +63,7 @@ com.sparkjava spark-core - 2.3 + 2.5.5 slf4j-simple diff --git a/src/main/java/com/bwssystems/HABridge/AuthFramework.java b/src/main/java/com/bwssystems/HABridge/AuthFramework.java new file mode 100644 index 0000000..543b424 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/AuthFramework.java @@ -0,0 +1,25 @@ +package com.bwssystems.HABridge; + +import spark.Request; + +public abstract class AuthFramework { + private static final String USER_SESSION_ID = "user"; + + public AuthFramework() { + // TODO Auto-generated constructor stub + } + + private void addAuthenticatedUser(Request request, User u) { + request.session().attribute(USER_SESSION_ID, u); + + } + + private void removeAuthenticatedUser(Request request) { + request.session().removeAttribute(USER_SESSION_ID); + + } + + private User getAuthenticatedUser(Request request) { + return request.session().attribute(USER_SESSION_ID); + } +} diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSecurity.java b/src/main/java/com/bwssystems/HABridge/BridgeSecurity.java index fca261c..3d13c59 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSecurity.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSecurity.java @@ -25,10 +25,12 @@ public class BridgeSecurity { (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12, }; private BridgeSecurityDescriptor securityDescriptor; + private boolean settingsChanged; public BridgeSecurity(char[] theKey, String theData) { habridgeKey = theKey; securityDescriptor = null; + settingsChanged = false; String anError = null; if(theData != null && !theData.isEmpty()) { try { @@ -56,20 +58,28 @@ public class BridgeSecurity { return securityDescriptor.isUseLinkButton(); } - public void setPassword(String aPassword) throws IOException { - if(aPassword != null) { - securityDescriptor.setUiPassword(String.valueOf(base64Decode(aPassword))); - securityDescriptor.setPasswordSet(true); - } else { - securityDescriptor.setUiPassword(null); - securityDescriptor.setPasswordSet(false); + public String setPassword(User aUser) throws IOException { + String error = null; + if(aUser != null) { + error = aUser.validate(); + if(error == null) { + User theUser = securityDescriptor.getUsers().get(aUser.getUsername()); + if(theUser != null) { + theUser.setPassword(aUser.getPassword()); + theUser.setPassword2(null); + settingsChanged = true; + } + } } - securityDescriptor.setSettingsChanged(true); + else + error = "invalid user object given"; + + return error; } public void setExecGarden(String theGarden) { securityDescriptor.setExecGarden(theGarden); - securityDescriptor.setSettingsChanged(true); + settingsChanged = true; } public String getExecGarden() { @@ -77,22 +87,43 @@ public class BridgeSecurity { } public void setUseLinkButton(boolean useThis) { securityDescriptor.setUseLinkButton(useThis); - securityDescriptor.setSettingsChanged(true); + settingsChanged = true; } - public boolean validatePassword(String targetPassword) throws IOException { - if(securityDescriptor.isPasswordSet()) { - if(securityDescriptor.getUiPassword().equals(String.valueOf(base64Decode(targetPassword)))) + public SecurityInfo getSecurityInfo() { + SecurityInfo theInfo = new SecurityInfo(); + theInfo.setExecGarden(getExecGarden()); + theInfo.setUseLinkButton(isUseLinkButton()); + theInfo.setSecure(isSecure()); + return theInfo; + } + public boolean validatePassword(User targetUser) throws IOException { + if(targetUser != null) { + User theUser = securityDescriptor.getUsers().get(targetUser.getUsername()); + if(theUser.getPassword() != null) { + theUser.setPassword2(targetUser.getPassword()); + if(theUser.validatePassword()) { + theUser.setPassword2(null); + return true; + } + } else { + log.warn("validating password when password is not set...."); return true; - } else { - log.warn("validating password when password is not set...."); - return true; + } } return false; } public boolean isSecure() { - return securityDescriptor.isPasswordSet(); + return securityDescriptor.isSecure(); + } + + public boolean isSettingsChanged() { + return settingsChanged; + } + + public void setSettingsChanged(boolean settingsChanged) { + this.settingsChanged = settingsChanged; } private String encrypt(String property) throws GeneralSecurityException, UnsupportedEncodingException { diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSecurityDescriptor.java b/src/main/java/com/bwssystems/HABridge/BridgeSecurityDescriptor.java index 0288d3a..380f46a 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSecurityDescriptor.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSecurityDescriptor.java @@ -1,33 +1,24 @@ package com.bwssystems.HABridge; +import java.util.Map; + public class BridgeSecurityDescriptor { - private String uiPassword; - private boolean passwordSet; + private Map users; private boolean useLinkButton; private String execGarden; - private boolean settingsChanged; + private boolean secureHueApi; public BridgeSecurityDescriptor() { super(); - this.setUiPassword(null); - this.setPasswordSet(false); this.setUseLinkButton(false); } - public String getUiPassword() { - return uiPassword; + public Map getUsers() { + return users; } - public void setUiPassword(String uiPassword) { - this.uiPassword = uiPassword; - } - - public boolean isPasswordSet() { - return passwordSet; - } - - public void setPasswordSet(boolean passwordSet) { - this.passwordSet = passwordSet; + public void setUsers(Map users) { + this.users = users; } public boolean isUseLinkButton() { @@ -46,11 +37,26 @@ public class BridgeSecurityDescriptor { this.execGarden = execGarden; } - public boolean isSettingsChanged() { - return settingsChanged; + public boolean isSecureHueApi() { + return secureHueApi; } - public void setSettingsChanged(boolean settingsChanged) { - this.settingsChanged = settingsChanged; + public void setSecureHueApi(boolean secureHueApi) { + this.secureHueApi = secureHueApi; + } + + public boolean isSecure() { + boolean secureFlag = false; + if(users != null && !users.isEmpty()) { + for (Map.Entry entry : users.entrySet()) + { + if(entry.getValue().getPassword() != null && !entry.getValue().getPassword().isEmpty()) { + secureFlag = true; + break; + } + } + } + return secureFlag; + } } diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java index 1028712..54d1f16 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java @@ -1,6 +1,7 @@ package com.bwssystems.HABridge; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; @@ -10,6 +11,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.PosixFilePermission; +import java.security.GeneralSecurityException; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; @@ -213,6 +215,18 @@ public class BridgeSettings extends BackupHandler { log.debug("Save HA Bridge settings."); Path configPath = Paths.get(theBridgeSettings.getConfigfile()); JsonTransformer aRenderer = new JsonTransformer(); + if(bridgeSecurity.isSettingsChanged()) { + try { + newBridgeSettings.setSecurityData(bridgeSecurity.getSecurityDescriptorData()); + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (GeneralSecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + bridgeSecurity.setSettingsChanged(false); + } String jsonValue = aRenderer.render(newBridgeSettings); configWriter(jsonValue, configPath); _loadConfig(configPath); diff --git a/src/main/java/com/bwssystems/HABridge/SecurityInfo.java b/src/main/java/com/bwssystems/HABridge/SecurityInfo.java new file mode 100644 index 0000000..8c7ce9b --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/SecurityInfo.java @@ -0,0 +1,33 @@ +package com.bwssystems.HABridge; + +public class SecurityInfo { + private boolean useLinkButton; + private String execGarden; + private boolean seucreHueApi; + private boolean isSecure; + + public boolean isUseLinkButton() { + return useLinkButton; + } + public void setUseLinkButton(boolean useLinkButton) { + this.useLinkButton = useLinkButton; + } + public String getExecGarden() { + return execGarden; + } + public void setExecGarden(String execGarden) { + this.execGarden = execGarden; + } + public boolean isSeucreHueApi() { + return seucreHueApi; + } + public void setSeucreHueApi(boolean seucreHueApi) { + this.seucreHueApi = seucreHueApi; + } + public boolean isSecure() { + return isSecure; + } + public void setSecure(boolean isSecure) { + this.isSecure = isSecure; + } +} diff --git a/src/main/java/com/bwssystems/HABridge/SystemControl.java b/src/main/java/com/bwssystems/HABridge/SystemControl.java index 0cdd456..38e69b6 100644 --- a/src/main/java/com/bwssystems/HABridge/SystemControl.java +++ b/src/main/java/com/bwssystems/HABridge/SystemControl.java @@ -31,7 +31,7 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.LoggingEvent; import ch.qos.logback.core.read.CyclicBufferAppender; -public class SystemControl { +public class SystemControl extends AuthFramework { private static final Logger log = LoggerFactory.getLogger(SystemControl.class); public static final String CYCLIC_BUFFER_APPENDER_NAME = "CYCLIC"; private LoggerContext lc; @@ -110,6 +110,13 @@ public class SystemControl { return theLogServiceMgr.getConfiguredLoggers(); }, new JsonTransformer()); + // http://ip_address:port/system/securityinfo gets the security info for the bridge + get (SYSTEM_CONTEXT + "/securityinfo", "application/json", (request, response) -> { + log.debug("Get security info"); + response.status(200); + return bridgeSettings.getBridgeSecurity().getSecurityInfo(); + }, new JsonTransformer()); + // http://ip_address:port/system/presslinkbutton CORS request options(SYSTEM_CONTEXT + "/presslinkbutton", "application/json", (request, response) -> { response.status(HttpStatus.SC_OK); @@ -128,6 +135,55 @@ public class SystemControl { return null; }, new JsonTransformer()); +// http://ip_address:port/system/setpassword CORS request + options(SYSTEM_CONTEXT + "/setpassword", "application/json", (request, response) -> { + response.status(HttpStatus.SC_OK); + response.header("Access-Control-Allow-Origin", request.headers("Origin")); + response.header("Access-Control-Allow-Methods", "GET, POST, PUT"); + response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers")); + response.header("Content-Type", "text/html; charset=utf-8"); + return ""; + }); +// http://ip_address:port/system/setpassword which sets a password for a given user + post(SYSTEM_CONTEXT + "/setpassword", "application/json", (request, response) -> { + log.debug("setpassword...."); + return null; + }, new JsonTransformer()); + +// http://ip_address:port/system/changesecurityinfo CORS request + options(SYSTEM_CONTEXT + "/changesecurityinfo", "application/json", (request, response) -> { + response.status(HttpStatus.SC_OK); + response.header("Access-Control-Allow-Origin", request.headers("Origin")); + response.header("Access-Control-Allow-Methods", "GET, POST, PUT"); + response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers")); + response.header("Content-Type", "text/html; charset=utf-8"); + return ""; + }); +// http://ip_address:port/system/changesecurityinfo which sets the security settings other than passwords and users + post(SYSTEM_CONTEXT + "/changesecurityinfo", "application/json", (request, response) -> { + log.debug("changesecurityinfo...."); + SecurityInfo theInfo = new Gson().fromJson(request.body(), SecurityInfo.class); + if(theInfo.getExecGarden() != null) + bridgeSettings.getBridgeSecurity().setExecGarden(theInfo.getExecGarden()); + bridgeSettings.getBridgeSecurity().setUseLinkButton(theInfo.isUseLinkButton()); + return null; + }, new JsonTransformer()); + +// http://ip_address:port/system/login CORS request + options(SYSTEM_CONTEXT + "/login", "application/json", (request, response) -> { + response.status(HttpStatus.SC_OK); + response.header("Access-Control-Allow-Origin", request.headers("Origin")); + response.header("Access-Control-Allow-Methods", "GET, POST, PUT"); + response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers")); + response.header("Content-Type", "text/html; charset=utf-8"); + return ""; + }); +// http://ip_address:port/system/login validates the login + post(SYSTEM_CONTEXT + "/login", "application/json", (request, response) -> { + log.debug("login...."); + return null; + }, new JsonTransformer()); + // http://ip_address:port/system/logmgmt/update CORS request options(SYSTEM_CONTEXT + "/logmgmt/update", "application/json", (request, response) -> { response.status(HttpStatus.SC_OK); diff --git a/src/main/java/com/bwssystems/HABridge/User.java b/src/main/java/com/bwssystems/HABridge/User.java new file mode 100644 index 0000000..a27824e --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/User.java @@ -0,0 +1,65 @@ +package com.bwssystems.HABridge; + +import spark.utils.StringUtils; + +public class User { + private int id; + + private String username; + + private String password; + + private String password2; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPassword2() { + return password2; + } + + public void setPassword2(String password2) { + this.password2 = password2; + } + + public String validate() { + String error = null; + + if(StringUtils.isEmpty(username)) { + error = "You have to enter a username"; + } else if(StringUtils.isEmpty(password)) { + error = "You have to enter a password"; + } else if(!password.equals(password2)) { + error = "The two passwords do not match"; + } + + return error; + } + + public boolean validatePassword() { + if(password != null && password2 != null) + return password.equals(password2); + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index a1d0296..fba75ad 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -1,5 +1,6 @@ package com.bwssystems.HABridge.hue; +import com.bwssystems.HABridge.AuthFramework; import com.bwssystems.HABridge.BridgeSettings; import com.bwssystems.HABridge.BridgeSettingsDescriptor; import com.bwssystems.HABridge.DeviceMapTypes; @@ -40,7 +41,7 @@ import java.util.Map; * Based on Armzilla's HueMulator - a Philips Hue emulator using sparkjava rest server */ -public class HueMulator { +public class HueMulator extends AuthFramework { private static final Logger log = LoggerFactory.getLogger(HueMulator.class); private static final String HUE_CONTEXT = "/api"; diff --git a/src/main/resources/public/scripts/app.js b/src/main/resources/public/scripts/app.js index 5898c7f..c12d0a3 100644 --- a/src/main/resources/public/scripts/app.js +++ b/src/main/resources/public/scripts/app.js @@ -63,6 +63,7 @@ app.run( function (bridgeService) { bridgeService.loadBridgeSettings(); bridgeService.getHABridgeVersion(); bridgeService.getTestUser(); + bridgeService.getSecurityInfo(); bridgeService.viewMapTypes(); }); @@ -80,7 +81,10 @@ String.prototype.replaceAll = function (search, replace) app.service ('bridgeService', function ($http, $window, ngToast) { var self = this; - this.state = {base: "./api/devices", bridgelocation: ".", systemsbase: "./system", huebase: "./api", configs: [], backups: [], devices: [], device: {}, mapandid: [], type: "", settings: [], myToastMsg: [], logMsgs: [], loggerInfo: [], mapTypes: [], olddevicename: "", logShowAll: false, isInControl: false, showVera: false, showHarmony: false, showNest: false, showHue: false, showHal: false, showMqtt: false, showHass: false, showDomoticz: false, showSomfy: false, showLifx: false, habridgeversion: "", viewDevId: "", queueDevId: ""}; + this.state = {base: "./api/devices", bridgelocation: ".", systemsbase: "./system", huebase: "./api", configs: [], backups: [], devices: [], device: {}, + mapandid: [], type: "", settings: [], myToastMsg: [], logMsgs: [], loggerInfo: [], mapTypes: [], olddevicename: "", logShowAll: false, + isInControl: false, showVera: false, showHarmony: false, showNest: false, showHue: false, showHal: false, showMqtt: false, showHass: false, + showDomoticz: false, showSomfy: false, showLifx: false, habridgeversion: "", viewDevId: "", queueDevId: "", securityInfo: {}}; this.displayWarn = function(errorTitle, error) { var toastContent = errorTitle; @@ -122,6 +126,12 @@ app.service ('bridgeService', function ($http, $window, ngToast) { content: theTitle}); }; + this.displayTimer = function (theTitle, timeMillis) { + ngToast.create({ + className: "success", + content: theTitle + " in " + timeMillis + " milliseconds"}); + }; + this.viewDevices = function () { return $http.get(this.state.base).then( function (response) { @@ -177,6 +187,28 @@ app.service ('bridgeService', function ($http, $window, ngToast) { ); }; + this.getSecurityInfo = function () { + return $http.get(this.state.systemsbase + "/securityinfo").then( + function (response) { + self.state.securityInfo = response.data; + }, + function (error) { + self.displayWarn("Cannot get security info: ", error); + } + ); + }; + + this.pushLinkButton = function () { + return $http.put(this.state.systemsbase + "/presslinkbutton").then( + function (response) { + self.displayTimer("Linnk your device in ", 30000); + }, + function (error) { + self.displayWarn("Cannot get security info: ", error); + } + ); + }; + this.aContainsB = function (a, b) { return a.indexOf(b) >= 0; } @@ -1228,6 +1260,9 @@ app.controller('ViewingController', function ($scope, $location, $http, $window, $scope.renumberDevices = function() { bridgeService.renumberDevices(); }; + $scope.pushLinkButton = function() { + bridgeService.pushLinkButton(); + }; $scope.backupDeviceDb = function (optionalbackupname) { bridgeService.backupDeviceDb(optionalbackupname); }; @@ -2934,9 +2969,6 @@ app.filter('configuredSomfyDevices', function (bridgeService) { } }); - - - app.controller('VersionController', function ($scope, bridgeService) { $scope.bridge = bridgeService.state; }); diff --git a/src/main/resources/public/views/configuration.html b/src/main/resources/public/views/configuration.html index 3cb3123..def1839 100644 --- a/src/main/resources/public/views/configuration.html +++ b/src/main/resources/public/views/configuration.html @@ -33,6 +33,7 @@

+

diff --git a/src/main/resources/public/views/system.html b/src/main/resources/public/views/system.html index 7902872..d00cfbc 100644 --- a/src/main/resources/public/views/system.html +++ b/src/main/resources/public/views/system.html @@ -58,6 +58,8 @@ location.reload(); } +

@@ -510,4 +512,20 @@
- \ No newline at end of file + + +