Continue with security update

This commit is contained in:
Admin
2017-03-23 16:36:25 -05:00
parent ddee3a42a9
commit b508a8a16a
12 changed files with 329 additions and 47 deletions

View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId>
<version>4.3.1Secure-b</version>
<version>4.3.1Secure-c</version>
<packaging>jar</packaging>
<name>HA Bridge</name>
@@ -63,7 +63,7 @@
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.3</version>
<version>2.5.5</version>
<exclusions>
<exclusion>
<artifactId>slf4j-simple</artifactId>

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -1,33 +1,24 @@
package com.bwssystems.HABridge;
import java.util.Map;
public class BridgeSecurityDescriptor {
private String uiPassword;
private boolean passwordSet;
private Map<String, User> 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<String, User> 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<String, User> 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<String, User> entry : users.entrySet())
{
if(entry.getValue().getPassword() != null && !entry.getValue().getPassword().isEmpty()) {
secureFlag = true;
break;
}
}
}
return secureFlag;
}
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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";

View File

@@ -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;
});

View File

@@ -33,6 +33,7 @@
<p>
<button class="btn btn-primary" type="submit" ng-click="renumberDevices()">Renumber Devices</button>
<button ng-if="bridge.securityInfo.useLinkButton" class="btn btn-primary" type="submit" ng-click="pushLinkButton()">Link</button>
</p>
<scrollable-table watch="bridge.devices">
<table class="table table-bordered table-striped table-hover">

View File

@@ -58,6 +58,8 @@
location.reload();
}
</script>
<button class="btn btn-warning"
type="submit" ng-click="updateSecurity()">Update Security Settings</button>
</p>
<table class="table table-bordered table-striped table-hover">
<thead>
@@ -510,4 +512,20 @@
</tr>
</table>
</div>
</div>
</div>
<script type="text/ng-template" id="securityDialog">
<div class="ngdialog-message">
<h2>Select value</h2>
<p>
<input type="radio" ng-model="valueType" value="percentage" ng-change="changeScale()"> Percentage
<input type="radio" ng-model="valueType" value="raw" ng-change="changeScale()"> Raw
</p>
<p>
<rzslider rz-slider-model="slider.value" rz-slider-options="slider.options"></rzslider>
</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-primary" ng-click="setValue()">Set</button>
</div>
</script>