diff --git a/assembly.xml b/assembly.xml index 031ae05..7d55db0 100644 --- a/assembly.xml +++ b/assembly.xml @@ -20,6 +20,8 @@ lib mqtt-client-0.4.0.jar + bcprov-jdk15on-1.48.jar + bcpkix-jdk15on-1.48.jar diff --git a/doc/example.png b/doc/example.png index a7e9496..d935cb8 100644 Binary files a/doc/example.png and b/doc/example.png differ diff --git a/pom.xml b/pom.xml index f19c4b2..68cd173 100644 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,16 @@ mqtt-client 0.4.0 + + org.bouncycastle + bcprov-jdk15on + 1.48 + + + org.bouncycastle + bcpkix-jdk15on + 1.48 + diff --git a/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerDialog.java b/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerDialog.java index 37d3888..a87a992 100644 --- a/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerDialog.java +++ b/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerDialog.java @@ -2,6 +2,8 @@ import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CCombo; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; @@ -12,6 +14,7 @@ import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; @@ -20,6 +23,7 @@ import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.pentaho.di.core.Const; +import org.pentaho.di.core.Props; import org.pentaho.di.core.exception.KettleStepException; import org.pentaho.di.core.row.RowMeta; import org.pentaho.di.core.row.RowMetaInterface; @@ -40,14 +44,33 @@ public class MQTTProducerDialog extends BaseStepDialog implements StepDialogInterface { private MQTTProducerMeta producerMeta; + + private CCombo wInputField; + + private CTabFolder wTabFolder; + + private CTabItem wGeneralTab; private TextVar wBroker; private TextVar wTopicName; private TextVar wClientID; private TextVar wTimeout; private TextVar wQOS; - private CCombo wInputField; - public MQTTProducerDialog(Shell parent, Object in, TransMeta tr, String sname) { + private CTabItem wCredentialsTab; + private Button wRequiresAuth; + private Label wlUsername; + private TextVar wUsername; + private Label wlPassword; + private TextVar wPassword; + + private CTabItem wSSLTab; + private TextVar wCAFile; + private TextVar wCertFile; + private TextVar wKeyFile; + private TextVar wKeyPassword; + + public MQTTProducerDialog(Shell parent, Object in, TransMeta tr, + String sname) { super(parent, (BaseStepMeta) in, tr, sname); producerMeta = (MQTTProducerMeta) in; } @@ -58,7 +81,8 @@ public MQTTProducerDialog(Shell parent, BaseStepMeta baseStepMeta, producerMeta = (MQTTProducerMeta) baseStepMeta; } - public MQTTProducerDialog(Shell parent, int nr, BaseStepMeta in, TransMeta tr) { + public MQTTProducerDialog(Shell parent, int nr, BaseStepMeta in, + TransMeta tr) { super(parent, nr, in, tr); producerMeta = (MQTTProducerMeta) in; } @@ -109,28 +133,84 @@ public void modifyText(ModifyEvent e) { wStepname.setLayoutData(fdStepname); Control lastControl = wStepname; + // Input field + RowMetaInterface previousFields; + try { + previousFields = transMeta.getPrevStepFields(stepMeta); + } catch (KettleStepException e) { + new ErrorDialog( + shell, + BaseMessages.getString("System.Dialog.Error.Title"), + Messages.getString("MQTTClientDialog.ErrorDialog.UnableToGetInputFields.Message"), + e); + previousFields = new RowMeta(); + } + Label wlInputField = new Label(shell, SWT.RIGHT); + wlInputField.setText(Messages + .getString("MQTTClientDialog.FieldName.Label")); + props.setLook(wlInputField); + FormData fdlInputField = new FormData(); + fdlInputField.top = new FormAttachment(lastControl, margin); + fdlInputField.left = new FormAttachment(0, 0); + fdlInputField.right = new FormAttachment(middle, -margin); + wlInputField.setLayoutData(fdlInputField); + wInputField = new CCombo(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER); + wInputField.setToolTipText(Messages + .getString("MQTTClientDialog.FieldName.Tooltip")); + wInputField.setItems(previousFields.getFieldNames()); + props.setLook(wInputField); + wInputField.addModifyListener(lsMod); + FormData fdFilename = new FormData(); + fdFilename.top = new FormAttachment(lastControl, margin); + fdFilename.left = new FormAttachment(middle, 0); + fdFilename.right = new FormAttachment(100, 0); + wInputField.setLayoutData(fdFilename); + lastControl = wInputField; + + // ==================== + // START OF TAB FOLDER + // ==================== + wTabFolder = new CTabFolder(shell, SWT.BORDER); + props.setLook(wTabFolder, Props.WIDGET_STYLE_TAB); + + // ==================== + // GENERAL TAB + // ==================== + + wGeneralTab = new CTabItem(wTabFolder, SWT.NONE); + wGeneralTab.setText(Messages + .getString("MQTTClientDialog.GeneralTab.Label")); //$NON-NLS-1$ + + FormLayout mainLayout = new FormLayout(); + mainLayout.marginWidth = 3; + mainLayout.marginHeight = 3; + + Composite wGeneralTabComp = new Composite(wTabFolder, SWT.NONE); + props.setLook(wGeneralTabComp); + wGeneralTabComp.setLayout(mainLayout); + // Broker URL - Label wlBroker = new Label(shell, SWT.RIGHT); + Label wlBroker = new Label(wGeneralTabComp, SWT.RIGHT); wlBroker.setText(Messages.getString("MQTTClientDialog.Broker.Label")); props.setLook(wlBroker); FormData fdlBroker = new FormData(); - fdlBroker.top = new FormAttachment(lastControl, margin); + fdlBroker.top = new FormAttachment(0, margin * 2); fdlBroker.left = new FormAttachment(0, 0); fdlBroker.right = new FormAttachment(middle, -margin); wlBroker.setLayoutData(fdlBroker); - wBroker = new TextVar(transMeta, shell, SWT.SINGLE | SWT.LEFT + wBroker = new TextVar(transMeta, wGeneralTabComp, SWT.SINGLE | SWT.LEFT | SWT.BORDER); props.setLook(wBroker); wBroker.addModifyListener(lsMod); FormData fdBroker = new FormData(); - fdBroker.top = new FormAttachment(lastControl, margin); + fdBroker.top = new FormAttachment(0, margin * 2); fdBroker.left = new FormAttachment(middle, 0); fdBroker.right = new FormAttachment(100, 0); wBroker.setLayoutData(fdBroker); lastControl = wBroker; // Topic name - Label wlTopicName = new Label(shell, SWT.RIGHT); + Label wlTopicName = new Label(wGeneralTabComp, SWT.RIGHT); wlTopicName.setText(Messages .getString("MQTTClientDialog.TopicName.Label")); props.setLook(wlTopicName); @@ -139,8 +219,8 @@ public void modifyText(ModifyEvent e) { fdlTopicName.left = new FormAttachment(0, 0); fdlTopicName.right = new FormAttachment(middle, -margin); wlTopicName.setLayoutData(fdlTopicName); - wTopicName = new TextVar(transMeta, shell, SWT.SINGLE | SWT.LEFT - | SWT.BORDER); + wTopicName = new TextVar(transMeta, wGeneralTabComp, SWT.SINGLE + | SWT.LEFT | SWT.BORDER); props.setLook(wTopicName); wTopicName.addModifyListener(lsMod); FormData fdTopicName = new FormData(); @@ -151,7 +231,7 @@ public void modifyText(ModifyEvent e) { lastControl = wTopicName; // Client ID - Label wlClientID = new Label(shell, SWT.RIGHT); + Label wlClientID = new Label(wGeneralTabComp, SWT.RIGHT); wlClientID.setText(Messages .getString("MQTTClientDialog.ClientID.Label")); props.setLook(wlClientID); @@ -160,8 +240,8 @@ public void modifyText(ModifyEvent e) { fdlClientID.left = new FormAttachment(0, 0); fdlClientID.right = new FormAttachment(middle, -margin); wlClientID.setLayoutData(fdlClientID); - wClientID = new TextVar(transMeta, shell, SWT.SINGLE | SWT.LEFT - | SWT.BORDER); + wClientID = new TextVar(transMeta, wGeneralTabComp, SWT.SINGLE + | SWT.LEFT | SWT.BORDER); props.setLook(wClientID); wClientID.addModifyListener(lsMod); FormData fdClientID = new FormData(); @@ -172,7 +252,7 @@ public void modifyText(ModifyEvent e) { lastControl = wClientID; // Connection timeout - Label wlConnectionTimeout = new Label(shell, SWT.RIGHT); + Label wlConnectionTimeout = new Label(wGeneralTabComp, SWT.RIGHT); wlConnectionTimeout.setText(Messages .getString("MQTTClientDialog.ConnectionTimeout.Label")); props.setLook(wlConnectionTimeout); @@ -181,8 +261,8 @@ public void modifyText(ModifyEvent e) { fdlConnectionTimeout.left = new FormAttachment(0, 0); fdlConnectionTimeout.right = new FormAttachment(middle, -margin); wlConnectionTimeout.setLayoutData(fdlConnectionTimeout); - wTimeout = new TextVar(transMeta, shell, SWT.SINGLE | SWT.LEFT - | SWT.BORDER); + wTimeout = new TextVar(transMeta, wGeneralTabComp, SWT.SINGLE + | SWT.LEFT | SWT.BORDER); props.setLook(wTimeout); wTimeout.addModifyListener(lsMod); FormData fdConnectionTimeout = new FormData(); @@ -193,7 +273,7 @@ public void modifyText(ModifyEvent e) { lastControl = wTimeout; // QOS - Label wlQOS = new Label(shell, SWT.RIGHT); + Label wlQOS = new Label(wGeneralTabComp, SWT.RIGHT); wlQOS.setText(Messages.getString("MQTTClientDialog.QOS.Label")); props.setLook(wlQOS); FormData fdlQOS = new FormData(); @@ -201,7 +281,8 @@ public void modifyText(ModifyEvent e) { fdlQOS.left = new FormAttachment(0, 0); fdlQOS.right = new FormAttachment(middle, -margin); wlQOS.setLayoutData(fdlQOS); - wQOS = new TextVar(transMeta, shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER); + wQOS = new TextVar(transMeta, wGeneralTabComp, SWT.SINGLE | SWT.LEFT + | SWT.BORDER); props.setLook(wQOS); wQOS.addModifyListener(lsMod); FormData fdQOS = new FormData(); @@ -211,39 +292,241 @@ public void modifyText(ModifyEvent e) { wQOS.setLayoutData(fdQOS); lastControl = wQOS; - // Input field - RowMetaInterface previousFields; - try { - previousFields = transMeta.getPrevStepFields(stepMeta); - } catch (KettleStepException e) { - new ErrorDialog( - shell, - BaseMessages.getString("System.Dialog.Error.Title"), - Messages.getString("MQTTClientDialog.ErrorDialog.UnableToGetInputFields.Message"), - e); - previousFields = new RowMeta(); - } - Label wlInputField = new Label(shell, SWT.RIGHT); - wlInputField.setText(Messages - .getString("MQTTClientDialog.FieldName.Label")); - props.setLook(wlInputField); - FormData fdlInputField = new FormData(); - fdlInputField.top = new FormAttachment(lastControl, margin); - fdlInputField.left = new FormAttachment(0, 0); - fdlInputField.right = new FormAttachment(middle, -margin); - wlInputField.setLayoutData(fdlInputField); - wInputField = new CCombo(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER); - wInputField.setItems(previousFields.getFieldNames()); - props.setLook(wInputField); - wInputField.addModifyListener(lsMod); - FormData fdFilename = new FormData(); - fdFilename.top = new FormAttachment(lastControl, margin); - fdFilename.left = new FormAttachment(middle, 0); - fdFilename.right = new FormAttachment(100, 0); - wInputField.setLayoutData(fdFilename); - lastControl = wInputField; + FormData fdGeneralTabComp = new FormData(); + fdGeneralTabComp.left = new FormAttachment(0, 0); + fdGeneralTabComp.top = new FormAttachment(0, 0); + fdGeneralTabComp.right = new FormAttachment(100, 0); + fdGeneralTabComp.bottom = new FormAttachment(100, 0); + wGeneralTabComp.setLayoutData(fdGeneralTabComp); + + wGeneralTabComp.layout(); + wGeneralTab.setControl(wGeneralTabComp); + + // ==================== + // CREDENTIALS TAB + // ==================== + wCredentialsTab = new CTabItem(wTabFolder, SWT.NONE); + wCredentialsTab.setText(Messages + .getString("MQTTClientDialog.CredentialsTab.Title")); //$NON-NLS-1$ + + Composite wCredentialsComp = new Composite(wTabFolder, SWT.NONE); + props.setLook(wCredentialsComp); + + FormLayout fieldsCompLayout = new FormLayout(); + fieldsCompLayout.marginWidth = Const.FORM_MARGIN; + fieldsCompLayout.marginHeight = Const.FORM_MARGIN; + wCredentialsComp.setLayout(fieldsCompLayout); + + Label wlRequiresAuth = new Label(wCredentialsComp, SWT.RIGHT); + wlRequiresAuth.setText(Messages + .getString("MQTTClientDialog.RequireAuth.Label")); + props.setLook(wlRequiresAuth); + FormData fdlRequriesAuth = new FormData(); + fdlRequriesAuth.left = new FormAttachment(0, 0); + fdlRequriesAuth.top = new FormAttachment(0, margin * 2); + fdlRequriesAuth.right = new FormAttachment(middle, -margin); + wlRequiresAuth.setLayoutData(fdlRequriesAuth); + wRequiresAuth = new Button(wCredentialsComp, SWT.CHECK); + props.setLook(wRequiresAuth); + FormData fdRequiresAuth = new FormData(); + fdRequiresAuth.left = new FormAttachment(middle, 0); + fdRequiresAuth.top = new FormAttachment(0, margin * 2); + fdRequiresAuth.right = new FormAttachment(100, 0); + wRequiresAuth.setLayoutData(fdRequiresAuth); + + wRequiresAuth.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent arg0) { + boolean enabled = wRequiresAuth.getSelection(); + wlUsername.setEnabled(enabled); + wUsername.setEnabled(enabled); + wlPassword.setEnabled(enabled); + wPassword.setEnabled(enabled); + } + }); + lastControl = wRequiresAuth; + + // Username field + wlUsername = new Label(wCredentialsComp, SWT.RIGHT); + wlUsername.setEnabled(false); + wlUsername.setText(Messages + .getString("MQTTClientDialog.Username.Label")); //$NON-NLS-1$ + props.setLook(wlUsername); + FormData fdlUsername = new FormData(); + fdlUsername.left = new FormAttachment(0, -margin); + fdlUsername.right = new FormAttachment(middle, -2 * margin); + fdlUsername.top = new FormAttachment(lastControl, 2 * margin); + wlUsername.setLayoutData(fdlUsername); + + wUsername = new TextVar(transMeta, wCredentialsComp, SWT.SINGLE + | SWT.LEFT | SWT.BORDER); + wUsername.setEnabled(false); + wUsername.setToolTipText(Messages + .getString("MQTTClientDialog.Username.Tooltip")); + props.setLook(wUsername); + wUsername.addModifyListener(lsMod); + FormData fdResult = new FormData(); + fdResult.left = new FormAttachment(middle, -margin); + fdResult.top = new FormAttachment(lastControl, 2 * margin); + fdResult.right = new FormAttachment(100, 0); + wUsername.setLayoutData(fdResult); + lastControl = wUsername; + + // Password field + wlPassword = new Label(wCredentialsComp, SWT.RIGHT); + wlPassword.setEnabled(false); + wlPassword.setText(Messages + .getString("MQTTClientDialog.Password.Label")); //$NON-NLS-1$ + props.setLook(wlPassword); + FormData fdlPassword = new FormData(); + fdlPassword.left = new FormAttachment(0, -margin); + fdlPassword.right = new FormAttachment(middle, -2 * margin); + fdlPassword.top = new FormAttachment(lastControl, margin); + wlPassword.setLayoutData(fdlPassword); - // Buttons + wPassword = new TextVar(transMeta, wCredentialsComp, SWT.SINGLE + | SWT.LEFT | SWT.BORDER | SWT.PASSWORD); + wPassword.setEnabled(false); + wPassword.setToolTipText(Messages + .getString("MQTTClientDialog.Password.Tooltip")); + props.setLook(wPassword); + wPassword.addModifyListener(lsMod); + FormData fdPassword = new FormData(); + fdPassword.left = new FormAttachment(middle, -margin); + fdPassword.top = new FormAttachment(lastControl, margin); + fdPassword.right = new FormAttachment(100, 0); + wPassword.setLayoutData(fdPassword); + + FormData fdCredentialsComp = new FormData(); + fdCredentialsComp.left = new FormAttachment(0, 0); + fdCredentialsComp.top = new FormAttachment(0, 0); + fdCredentialsComp.right = new FormAttachment(100, 0); + fdCredentialsComp.bottom = new FormAttachment(100, 0); + wCredentialsComp.setLayoutData(fdCredentialsComp); + + wCredentialsComp.layout(); + wCredentialsTab.setControl(wCredentialsComp); + + // ==================== + // SSL TAB + // ==================== + wSSLTab = new CTabItem(wTabFolder, SWT.NONE); + wSSLTab.setText(Messages.getString("MQTTClientDialog.SSLTab.Label")); //$NON-NLS-1$ + + Composite wSSLComp = new Composite(wTabFolder, SWT.NONE); + props.setLook(wSSLComp); + + FormLayout sslCompLayout = new FormLayout(); + sslCompLayout.marginWidth = Const.FORM_MARGIN; + sslCompLayout.marginHeight = Const.FORM_MARGIN; + wSSLComp.setLayout(sslCompLayout); + + // Server CA file path + Label wlCAFile = new Label(wSSLComp, SWT.RIGHT); + wlCAFile.setText(Messages.getString("MQTTClientDialog.CAFile.Label")); //$NON-NLS-1$ + props.setLook(wlCAFile); + FormData fdlCAFile = new FormData(); + fdlCAFile.left = new FormAttachment(0, -margin); + fdlCAFile.right = new FormAttachment(middle, -2 * margin); + fdlCAFile.top = new FormAttachment(0, 2 * margin); + wlCAFile.setLayoutData(fdlCAFile); + + wCAFile = new TextVar(transMeta, wSSLComp, SWT.SINGLE | SWT.LEFT + | SWT.BORDER); + wCAFile.setToolTipText(Messages + .getString("MQTTClientDialog.CAFile.Tooltip")); + props.setLook(wCAFile); + wCAFile.addModifyListener(lsMod); + FormData fdCAFile = new FormData(); + fdCAFile.left = new FormAttachment(middle, -margin); + fdCAFile.top = new FormAttachment(0, 2 * margin); + fdCAFile.right = new FormAttachment(100, 0); + wCAFile.setLayoutData(fdCAFile); + lastControl = wCAFile; + + // Client certificate file path + Label wlCertFile = new Label(wSSLComp, SWT.RIGHT); + wlCertFile.setText(Messages + .getString("MQTTClientDialog.CertFile.Label")); //$NON-NLS-1$ + props.setLook(wlCertFile); + FormData fdlCertFile = new FormData(); + fdlCertFile.left = new FormAttachment(0, -margin); + fdlCertFile.right = new FormAttachment(middle, -2 * margin); + fdlCertFile.top = new FormAttachment(lastControl, margin); + wlCertFile.setLayoutData(fdlCertFile); + + wCertFile = new TextVar(transMeta, wSSLComp, SWT.SINGLE | SWT.LEFT + | SWT.BORDER); + wCertFile.setToolTipText(Messages + .getString("MQTTClientDialog.CertFile.Tooltip")); + props.setLook(wCertFile); + wCertFile.addModifyListener(lsMod); + FormData fdCertFile = new FormData(); + fdCertFile.left = new FormAttachment(middle, -margin); + fdCertFile.top = new FormAttachment(lastControl, margin); + fdCertFile.right = new FormAttachment(100, 0); + wCertFile.setLayoutData(fdCertFile); + lastControl = wCertFile; + + // Client key file path + Label wlKeyFile = new Label(wSSLComp, SWT.RIGHT); + wlKeyFile.setText(Messages.getString("MQTTClientDialog.KeyFile.Label")); //$NON-NLS-1$ + props.setLook(wlKeyFile); + FormData fdlKeyFile = new FormData(); + fdlKeyFile.left = new FormAttachment(0, -margin); + fdlKeyFile.right = new FormAttachment(middle, -2 * margin); + fdlKeyFile.top = new FormAttachment(lastControl, margin); + wlKeyFile.setLayoutData(fdlKeyFile); + + wKeyFile = new TextVar(transMeta, wSSLComp, SWT.SINGLE | SWT.LEFT + | SWT.BORDER); + wKeyFile.setToolTipText(Messages + .getString("MQTTClientDialog.KeyFile.Tooltip")); + props.setLook(wKeyFile); + wKeyFile.addModifyListener(lsMod); + FormData fdKeyFile = new FormData(); + fdKeyFile.left = new FormAttachment(middle, -margin); + fdKeyFile.top = new FormAttachment(lastControl, margin); + fdKeyFile.right = new FormAttachment(100, 0); + wKeyFile.setLayoutData(fdKeyFile); + lastControl = wKeyFile; + + // Client key file password path + Label wlKeyPassword = new Label(wSSLComp, SWT.RIGHT); + wlKeyPassword.setText(Messages + .getString("MQTTClientDialog.KeyPassword.Label")); //$NON-NLS-1$ + props.setLook(wlKeyPassword); + FormData fdlKeyPassword = new FormData(); + fdlKeyPassword.left = new FormAttachment(0, -margin); + fdlKeyPassword.right = new FormAttachment(middle, -2 * margin); + fdlKeyPassword.top = new FormAttachment(lastControl, margin); + wlKeyPassword.setLayoutData(fdlKeyPassword); + + wKeyPassword = new TextVar(transMeta, wSSLComp, SWT.SINGLE | SWT.LEFT + | SWT.BORDER | SWT.PASSWORD); + wKeyPassword.setToolTipText(Messages + .getString("MQTTClientDialog.KeyPassword.Tooltip")); + props.setLook(wKeyPassword); + wKeyPassword.addModifyListener(lsMod); + FormData fdKeyPassword = new FormData(); + fdKeyPassword.left = new FormAttachment(middle, -margin); + fdKeyPassword.top = new FormAttachment(lastControl, margin); + fdKeyPassword.right = new FormAttachment(100, 0); + wKeyPassword.setLayoutData(fdKeyPassword); + lastControl = wKeyPassword; + + FormData fdSSLComp = new FormData(); + fdSSLComp.left = new FormAttachment(0, 0); + fdSSLComp.top = new FormAttachment(0, 0); + fdSSLComp.right = new FormAttachment(100, 0); + fdSSLComp.bottom = new FormAttachment(100, 0); + wSSLComp.setLayoutData(fdSSLComp); + + wSSLComp.layout(); + wSSLTab.setControl(wSSLComp); + + // ==================== + // BUTTONS + // ==================== wOK = new Button(shell, SWT.PUSH); wOK.setText(BaseMessages.getString("System.Button.OK")); //$NON-NLS-1$ wCancel = new Button(shell, SWT.PUSH); @@ -251,6 +534,16 @@ public void modifyText(ModifyEvent e) { setButtonPositions(new Button[] { wOK, wCancel }, margin, null); + // ==================== + // END OF TAB FOLDER + // ==================== + FormData fdTabFolder = new FormData(); + fdTabFolder.left = new FormAttachment(0, 0); + fdTabFolder.top = new FormAttachment(wInputField, margin); + fdTabFolder.right = new FormAttachment(100, 0); + fdTabFolder.bottom = new FormAttachment(wOK, -margin); + wTabFolder.setLayoutData(fdTabFolder); + // Add listeners lsCancel = new Listener() { public void handleEvent(Event e) { @@ -274,6 +567,8 @@ public void widgetDefaultSelected(SelectionEvent e) { wTopicName.addSelectionListener(lsDef); wInputField.addSelectionListener(lsDef); + wTabFolder.setSelection(0); + // Detect X or ALT-F4 or something that kills this window... shell.addShellListener(new ShellAdapter() { public void shellClosed(ShellEvent e) { @@ -309,6 +604,18 @@ private void getData(MQTTProducerMeta producerMeta, boolean copyStepname) { wClientID.setText(Const.NVL(producerMeta.getClientId(), "")); wTimeout.setText(Const.NVL(producerMeta.getTimeout(), "10000")); wQOS.setText(Const.NVL(producerMeta.getQoS(), "0")); + + wRequiresAuth.setSelection(producerMeta.isRequiresAuth()); + wRequiresAuth.notifyListeners(SWT.Selection, new Event()); + + wUsername.setText(Const.NVL(producerMeta.getUsername(), "")); + wPassword.setText(Const.NVL(producerMeta.getPassword(), "")); + + wCAFile.setText(Const.NVL(producerMeta.getSSLCaFile(), "")); + wCertFile.setText(Const.NVL(producerMeta.getSSLCertFile(), "")); + wKeyFile.setText(Const.NVL(producerMeta.getSSLKeyFile(), "")); + wKeyPassword.setText(Const.NVL(producerMeta.getSSLKeyFilePass(), "")); + wStepname.selectAll(); } @@ -328,6 +635,19 @@ private void setData(MQTTProducerMeta producerMeta) { producerMeta.setClientId(wClientID.getText()); producerMeta.setTimeout(wTimeout.getText()); producerMeta.setQoS(wQOS.getText()); + + boolean requiresAuth = wRequiresAuth.getSelection(); + producerMeta.setRequiresAuth(requiresAuth); + if (requiresAuth) { + producerMeta.setUsername(wUsername.getText()); + producerMeta.setPassword(wPassword.getText()); + } + + producerMeta.setSSLCaFile(wCAFile.getText()); + producerMeta.setSSLCertFile(wCertFile.getText()); + producerMeta.setSSLKeyFile(wKeyFile.getText()); + producerMeta.setSSLKeyFilePass(wKeyPassword.getText()); + producerMeta.setChanged(); } diff --git a/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerMeta.java b/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerMeta.java index e5ea289..1969cf1 100644 --- a/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerMeta.java +++ b/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerMeta.java @@ -5,6 +5,7 @@ import org.pentaho.di.core.CheckResult; import org.pentaho.di.core.CheckResultInterface; +import org.pentaho.di.core.Const; import org.pentaho.di.core.Counter; import org.pentaho.di.core.database.DatabaseMeta; import org.pentaho.di.core.exception.KettleException; @@ -36,6 +37,13 @@ public class MQTTProducerMeta extends BaseStepMeta implements StepMetaInterface private String clientId; private String timeout = "10000"; private String qos = "0"; + private boolean requiresAuth; + private String username; + private String password; + private String sslCaFile; + private String sslCertFile; + private String sslKeyFile; + private String sslKeyFilePass; /** * @return Broker URL @@ -127,6 +135,111 @@ public void setQoS(String qos) { this.qos = qos; } + /** + * @return Whether MQTT broker requires authentication + */ + public boolean isRequiresAuth() { + return requiresAuth; + } + + /** + * @param requiresAuth + * Whether MQTT broker requires authentication + */ + public void setRequiresAuth(boolean requiresAuth) { + this.requiresAuth = requiresAuth; + } + + /** + * @return Username to MQTT broker + */ + public String getUsername() { + return username; + } + + /** + * @param username + * Username to MQTT broker + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * @return Password to MQTT broker + */ + public String getPassword() { + return password; + } + + /** + * @param password + * Password to MQTT broker + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * @return Server CA file + */ + public String getSSLCaFile() { + return sslCaFile; + } + + /** + * @param sslCaFile + * Server CA file + */ + public void setSSLCaFile(String sslCaFile) { + this.sslCaFile = sslCaFile; + } + + /** + * @return Client certificate file + */ + public String getSSLCertFile() { + return sslCertFile; + } + + /** + * @param sslCertFile + * Client certificate file + */ + public void setSSLCertFile(String sslCertFile) { + this.sslCertFile = sslCertFile; + } + + /** + * @return Client key file + */ + public String getSSLKeyFile() { + return sslKeyFile; + } + + /** + * @param sslKeyFile + * Client key file + */ + public void setSSLKeyFile(String sslKeyFile) { + this.sslKeyFile = sslKeyFile; + } + + /** + * @return Client key file password + */ + public String getSSLKeyFilePass() { + return sslKeyFilePass; + } + + /** + * @param sslKeyFilePass + * Client key file password + */ + public void setSSLKeyFilePass(String sslKeyFilePass) { + this.sslKeyFilePass = sslKeyFilePass; + } + public void check(List remarks, TransMeta transMeta, StepMeta stepMeta, RowMetaInterface prev, String input[], String output[], RowMetaInterface info) { @@ -162,13 +275,27 @@ public void check(List remarks, TransMeta transMeta, Messages.getString("MQTTClientMeta.Check.InvalidQOS"), stepMeta)); } + if (requiresAuth) { + if (username == null) { + remarks.add(new CheckResult( + CheckResultInterface.TYPE_RESULT_ERROR, + Messages.getString("MQTTClientMeta.Check.InvalidUsername"), + stepMeta)); + } + if (password == null) { + remarks.add(new CheckResult( + CheckResultInterface.TYPE_RESULT_ERROR, + Messages.getString("MQTTClientMeta.Check.InvalidPassword"), + stepMeta)); + } + } } public StepInterface getStep(StepMeta stepMeta, StepDataInterface stepDataInterface, int cnr, TransMeta transMeta, Trans trans) { - return new MQTTProducerStep(stepMeta, stepDataInterface, cnr, transMeta, - trans); + return new MQTTProducerStep(stepMeta, stepDataInterface, cnr, + transMeta, trans); } public StepDataInterface getStepData() { @@ -185,6 +312,20 @@ public void loadXML(Node stepnode, List databases, clientId = XMLHandler.getTagValue(stepnode, "CLIENT_ID"); timeout = XMLHandler.getTagValue(stepnode, "TIMEOUT"); qos = XMLHandler.getTagValue(stepnode, "QOS"); + requiresAuth = Boolean.parseBoolean(XMLHandler.getTagValue( + stepnode, "REQUIRES_AUTH")); + if (requiresAuth) { + username = XMLHandler.getTagValue(stepnode, "USERNAME"); + password = XMLHandler.getTagValue(stepnode, "PASSWORD"); + } + Node sslNode = XMLHandler.getSubNode(stepnode, "SSL"); + if (sslNode != null) { + sslCaFile = XMLHandler.getTagValue(sslNode, "CA_FILE"); + sslCertFile = XMLHandler.getTagValue(sslNode, "CERT_FILE"); + sslKeyFile = XMLHandler.getTagValue(sslNode, "KEY_FILE"); + sslKeyFilePass = XMLHandler.getTagValue(sslNode, + "KEY_FILE_PASS"); + } } catch (Exception e) { throw new KettleXMLException( Messages.getString("MQTTClientMeta.Exception.loadXml"), e); @@ -216,6 +357,46 @@ public String getXML() throws KettleException { if (qos != null) { retval.append(" ").append(XMLHandler.addTagValue("QOS", qos)); } + + retval.append(" ").append( + XMLHandler.addTagValue("REQUIRES_AUTH", + Boolean.toString(requiresAuth))); + if (requiresAuth) { + if (username != null) { + retval.append(" ").append( + XMLHandler.addTagValue("USERNAME", username)); + } + if (password != null) { + retval.append(" ").append( + XMLHandler.addTagValue("PASSWORD", password)); + } + } + + if (sslCaFile != null || sslCertFile != null || sslKeyFile != null + || sslKeyFilePass != null) { + retval.append(" ").append(XMLHandler.openTag("SSL")) + .append(Const.CR); + if (sslCaFile != null) { + retval.append(" " + + XMLHandler.addTagValue("CA_FILE", sslCaFile)); + } + if (sslCertFile != null) { + retval.append(" " + + XMLHandler.addTagValue("CERT_FILE", sslCertFile)); + } + if (sslKeyFile != null) { + retval.append(" " + + XMLHandler.addTagValue("KEY_FILE", sslKeyFile)); + } + if (sslKeyFilePass != null) { + retval.append(" " + + XMLHandler.addTagValue("KEY_FILE_PASS", + sslKeyFilePass)); + } + retval.append(" ").append(XMLHandler.closeTag("SSL")) + .append(Const.CR); + } + return retval.toString(); } @@ -229,6 +410,17 @@ public void readRep(Repository rep, ObjectId stepId, clientId = rep.getStepAttributeString(stepId, "CLIENT_ID"); timeout = rep.getStepAttributeString(stepId, "TIMEOUT"); qos = rep.getStepAttributeString(stepId, "QOS"); + requiresAuth = Boolean.parseBoolean(rep.getStepAttributeString( + stepId, "REQUIRES_AUTH")); + if (requiresAuth) { + username = rep.getStepAttributeString(stepId, "USERNAME"); + password = rep.getStepAttributeString(stepId, "PASSWORD"); + } + sslCaFile = rep.getStepAttributeString(stepId, "SSL_CA_FILE"); + sslCertFile = rep.getStepAttributeString(stepId, "SSL_CERT_FILE"); + sslKeyFile = rep.getStepAttributeString(stepId, "SSL_KEY_FILE"); + sslKeyFilePass = rep.getStepAttributeString(stepId, + "SSL_KEY_FILE_PASS"); } catch (Exception e) { throw new KettleException("MQTTClientMeta.Exception.loadRep", e); } @@ -258,6 +450,35 @@ public void saveRep(Repository rep, ObjectId transformationId, if (qos != null) { rep.saveStepAttribute(transformationId, stepId, "QOS", qos); } + rep.saveStepAttribute(transformationId, stepId, "REQUIRES_AUTH", + Boolean.toString(requiresAuth)); + if (requiresAuth) { + if (username != null) { + rep.saveStepAttribute(transformationId, stepId, "USERNAME", + username); + } + if (password != null) { + rep.saveStepAttribute(transformationId, stepId, "USERNAME", + password); + } + } + + if (sslCaFile != null) { + rep.saveStepAttribute(transformationId, stepId, "SSL_CA_FILE", + sslCaFile); + } + if (sslCertFile != null) { + rep.saveStepAttribute(transformationId, stepId, + "SSL_CERT_FILE", sslCertFile); + } + if (sslKeyFile != null) { + rep.saveStepAttribute(transformationId, stepId, "SSL_KEY_FILE", + sslKeyFile); + } + if (sslKeyFilePass != null) { + rep.saveStepAttribute(transformationId, stepId, + "SSL_KEY_FILE_PASS", sslKeyFilePass); + } } catch (Exception e) { throw new KettleException("MQTTClientMeta.Exception.saveRep", e); } diff --git a/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerStep.java b/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerStep.java index a11fe5a..a545163 100644 --- a/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerStep.java +++ b/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/MQTTProducerStep.java @@ -71,6 +71,25 @@ public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) data.client = new MqttClient(broker, clientId); MqttConnectOptions connectOptions = new MqttConnectOptions(); + if (meta.isRequiresAuth()) { + connectOptions.setUserName(environmentSubstitute(meta + .getUsername())); + connectOptions.setPassword(environmentSubstitute( + meta.getPassword()).toCharArray()); + } + if (broker.startsWith("ssl:")) { + connectOptions + .setSocketFactory(SSLSocketFactoryGenerator + .getSocketFactory( + environmentSubstitute(meta + .getSSLCaFile()), + environmentSubstitute(meta + .getSSLCertFile()), + environmentSubstitute(meta + .getSSLKeyFile()), + environmentSubstitute(meta + .getSSLKeyFilePass()))); + } connectOptions.setCleanSession(true); String timeout = environmentSubstitute(meta.getTimeout()); @@ -88,7 +107,7 @@ public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) clientId)); data.client.connect(connectOptions); - } catch (MqttException e) { + } catch (Exception e) { throw new KettleException(Messages.getString( "MQTTClientStep.ErrorCreateMQTTClient.Message", broker), e); diff --git a/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/SSLSocketFactoryGenerator.java b/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/SSLSocketFactoryGenerator.java new file mode 100644 index 0000000..4df2e76 --- /dev/null +++ b/src/main/java/com/ruckuswireless/pentaho/mqtt/producer/SSLSocketFactoryGenerator.java @@ -0,0 +1,88 @@ +package com.ruckuswireless.pentaho.mqtt.producer; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileReader; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.Security; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; + +public class SSLSocketFactoryGenerator { + + public static SSLSocketFactory getSocketFactory(String caCrtFile, + String crtFile, String keyFile, String password) throws Exception { + + char[] passwordCharArray = password == null ? new char[0] : password + .toCharArray(); + + Security.addProvider(new BouncyCastleProvider()); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + X509Certificate caCert = (X509Certificate) cf + .generateCertificate(new ByteArrayInputStream(Files + .readAllBytes(Paths.get(caCrtFile)))); + + X509Certificate cert = (X509Certificate) cf + .generateCertificate(new ByteArrayInputStream(Files + .readAllBytes(Paths.get(crtFile)))); + + File privateKeyFile = new File(keyFile); + PEMParser pemParser = new PEMParser(new FileReader(privateKeyFile)); + PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder() + .build(passwordCharArray); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter() + .setProvider("BC"); + + Object object = pemParser.readObject(); + KeyPair kp; + + if (object instanceof PEMEncryptedKeyPair) { + kp = converter.getKeyPair(((PEMEncryptedKeyPair) object) + .decryptKeyPair(decProv)); + } else { + kp = converter.getKeyPair((PEMKeyPair) object); + } + + pemParser.close(); + + KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + caKeyStore.load(null, null); + caKeyStore.setCertificateEntry("ca-certificate", caCert); + TrustManagerFactory trustManagerFactory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(caKeyStore); + + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + keyStore.setCertificateEntry("certificate", cert); + keyStore.setKeyEntry("private-key", kp.getPrivate(), passwordCharArray, + new java.security.cert.Certificate[] { cert }); + KeyManagerFactory keyManagerFactory = KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, passwordCharArray); + + SSLContext context = SSLContext.getInstance("TLSv1"); + context.init(keyManagerFactory.getKeyManagers(), + trustManagerFactory.getTrustManagers(), null); + + return context.getSocketFactory(); + + } +} \ No newline at end of file diff --git a/src/main/resources/com/ruckuswireless/pentaho/mqtt/producer/messages/messages_en_US.properties b/src/main/resources/com/ruckuswireless/pentaho/mqtt/producer/messages/messages_en_US.properties index 4959bed..fd0ffbb 100644 --- a/src/main/resources/com/ruckuswireless/pentaho/mqtt/producer/messages/messages_en_US.properties +++ b/src/main/resources/com/ruckuswireless/pentaho/mqtt/producer/messages/messages_en_US.properties @@ -18,12 +18,31 @@ MQTTClientMeta.Check.InvalidField=Field name must be set\! MQTTClientMeta.Check.InvalidClientID=Client ID must be set\! MQTTClientMeta.Check.InvalidConnectionTimeout=Connection timeout must be set\! MQTTClientMeta.Check.InvalidQOS=QoS must be set\! +MQTTClientMeta.Check.InvalidUsername=Username must be set\! +MQTTClientMeta.Check.InvalidPassword=Password must be set\! MQTTClientDialog.Shell.Title=MQTT Client MQTTClientDialog.StepName.Label=Step name +MQTTClientDialog.GeneralTab.Label=General MQTTClientDialog.Broker.Label=Broker URL MQTTClientDialog.TopicName.Label=Topic name MQTTClientDialog.FieldName.Label=Input field name +MQTTClientDialog.FieldName.Tooltip=Name of the field containing the message to be sent over MQTT MQTTClientDialog.ClientID.Label=Client ID +MQTTClientDialog.RequireAuth.Label=Server requires authorization +MQTTClientDialog.Username.Label=Username +MQTTClientDialog.Username.Tooltip=Username for connecting to MQTT bridge +MQTTClientDialog.Password.Label=Password +MQTTClientDialog.Password.Tooltip=Password for connecting to MQTT bridge +MQTTClientDialog.CredentialsTab.Title=Credentials MQTTClientDialog.ConnectionTimeout.Label=Connection timeout MQTTClientDialog.QOS.Label=QoS +MQTTClientDialog.SSLTab.Label=SSL +MQTTClientDialog.CAFile.Label=CA file path +MQTTClientDialog.CAFile.Tooltip=Please specify path to the server CA file +MQTTClientDialog.CertFile.Label=Certificate file path +MQTTClientDialog.CertFile.Tooltip=Please specify path to the client certificate file +MQTTClientDialog.KeyFile.Label=Key file path +MQTTClientDialog.KeyFile.Tooltip=Please specify path to the client key file +MQTTClientDialog.KeyPassword.Label=Key file password +MQTTClientDialog.KeyPassword.Tooltip=Please specify client key file password if any MQTTClientDialog.ErrorDialog.UnableToGetInputFields=Unable to get input fields for this step\! \ No newline at end of file diff --git a/src/main/resources/plugin.xml b/src/main/resources/plugin.xml index 61a6acb..cfa6b59 100644 --- a/src/main/resources/plugin.xml +++ b/src/main/resources/plugin.xml @@ -10,6 +10,8 @@ + +