Ok, been a wee while since my last post and lots of exciting things have been happening. Got the new firewall bedded down, finally added ‘automatic’ link redundancy to our WAN through the addition of OSPF, preparing a phone system upgrade, new more comprehensive DR plans, not to mention the plethora of other little bits of pieces – things are finally coming together.
This post was originally planned to be around BUS100 and importing budgets, sadly what I expected to be a 20 hours of coding turned in to a marathon effort involving my last 4 weekends without a satisfactory solution…so instead, we’ll talk about poor mans security…
The Problem:
Our Account Manager doesn’t like the idea of the people paying the bills being able to update and commit bank account details especially when EFTs are involved. We are pretty lucky in our organisation that there is a fairly high level of well deserved trust, however the AP staff being able to change the bank accounts in CRS692 isn’t good.
Now our Account Manager wants our AP staff to be able to update the bank details, but when they do so the status should change to 10 – so no payments can be made. Then another specifically designated person will need to change the status back to 20 before any more EFTs can be put through.
Standard M3 will not do this – Lawsons option is PFI. A quick chat to our Lawson Account Manager and we discover (unsurprisingly) that PFI was several zeros more expensive than we could justify for this particular problem and there wasn’t an immediate need for PFI in other areas.
So, another jscript was born. Before you cry out that this script can be circumvented fairly easily, yes, yes it can – I’m not aware of a way to stop people changing their customisations against a specific panel. However this is a little security through obscurity which has been deemed ‘adequate’ for the time being. I am certainly open to other suggestions on how we can address this issue properly 🙂
This script expands on a previous post “Validating Bank Account Details – Cancelling the Save” https://potatoit.wordpress.com/2011/02/06/validating-bank-account-details-%E2%80%93-cancelling-the-save/ adding functionality which will disable the Status ComboBox in CRS692/E unless you are a specific user AND when Next is pressed it will change the Status to 10 if there was a change AND the user isn’t one of our authorised users. The script is smart enough to allow the user to Next through the screen when there are no changes without modifying the status.
Without further a-do the code
import System; import System.Windows; import System.Windows.Controls; import MForms; import Mango.Services; package MForms.JScript { class ValidateBankDetails_012 { var giicInstanceController : IInstanceController = null; // this is where we will store the IInstanceController to make it available to the rest of the class var ggrdContentGrid : Grid = null; // this is the Grid that we get passed by the Init() var gstrOriginalBank : String = null; // store the original 'Bank' component of the bank account var gstrOriginalBranch : String = null; // store the original 'Branch' component of the bank account var gstrOriginalAccount : String = null; // store the original 'Account' component of the bank account var gstrOriginalSuffix : String = null; // store the original 'Suffix' component of the bank account var tbBank : TextBox = null; // here we cache the Bank TextBox var tbBranch : TextBox = null; // here we cache the Branch TextBox var tbAccount : TextBox = null; // here we cache the Account TextBox var tbSuffix : TextBox = null; // here we cache the Suffix TextBox var cmbStatus : ComboBox = null; // here we cache the Status ComboBox public function Init(element: Object, args: Object, controller : Object, debug : Object) { giicInstanceController = controller; // save the controller to a more accessible variable ggrdContentGrid = controller.RenderEngine.Content; // save the Content Grid to a more accessbile variable initSetup(giicInstanceController); // go out and do some of our setup giicInstanceController.add_Requesting(OnRequesting); // add our event so we can actually respond to user interaction } // this little function will go out and retrieve the TextBoxes // and ComboBox that we are interested in private function retrieveTextBoxes() { try { ggrdContentGrid = giicInstanceController.RenderEngine.Content; // we have some issues during testing navigating backwards and fowards through panels, so we retrieve our content object here again tbBank = ScriptUtil.FindChild(ggrdContentGrid, "W1BF02"); // Bank tbBranch = ScriptUtil.FindChild(ggrdContentGrid, "W2BF04"); // Branch tbAccount = ScriptUtil.FindChild(ggrdContentGrid, "W3BF07"); // Account tbSuffix = ScriptUtil.FindChild(ggrdContentGrid, "W4BF03"); // Suffix cmbStatus = ScriptUtil.FindChild(ggrdContentGrid, "WWSTAT"); // Status } catch(ex) { MessageBox.Show("retrieveTextBoxes Exception: " + ex.message); } } private function initSetup(aiicInstanceController : IInstanceController) { try { giicInstanceController = aiicInstanceController; // retrieve the TextBoxes and Combobox that we will be working with retrieveTextBoxes(); if(null != tbBank) { gstrOriginalBank = tbBank.Text; // get the 'original' value of the Bank } if(null != tbBranch) { gstrOriginalBranch = tbBranch.Text; // get the 'original' value of the Branch } if(null != tbAccount) { gstrOriginalAccount = tbAccount.Text; // get the 'original' value of the Account } if(null != tbSuffix) { gstrOriginalSuffix = tbSuffix.Text; // get the 'original' value of the Suffix } if(null != cmbStatus) { // if the user isn't jbloggs or jdoe then disable the status ComboBox if((0 != String.Compare(ApplicationServices.UserContext.UserName, "jbloggs", true)) && (0 != String.Compare(ApplicationServices.UserContext.UserName, "jdoe", true))) { cmbStatus.IsEnabled = false; } } } catch(ex) { MessageBox.Show("initSetup() Exception " + ex.message); } } // clean up in this case is pretty basic - remove our event private function cleanpUp() { try { giicInstanceController.remove_Requesting(OnRequesting); } catch(exClose) { MessageBox.Show("cleanpUp() exception when removing requesting Event " + exClose.message); } } public function OnRequesting(sender: Object, e: CancelRequestEventArgs) { try { if(e.CommandType == MNEProtocol.CommandTypeKey) // we're looking for a key event { if(e.CommandValue == MNEProtocol.KeyEnter) // specifically we're looking the enter key event { try { if(true == giicInstanceController.RenderEngine.PanelHeader.EndsWith("CRS692/E")) // are we on panel E? { retrieveTextBoxes(); if(true == haveWeChangedAnyDetails()) // have the bank account details been changed? { if(false == validBankAccount()) // is the bank account valid? { e.Cancel = true; // it wasn't valid so cancel the request } else { if(null != cmbStatus) { // we changed our account, if the user isn't jbloggs or jdoe then we need to change the status to 10 (which is index 0 in the ComboBox) We can do this even when the ComboBox is disabled if((0 != String.Compare(ApplicationServices.UserContext.UserName, "jbloggs", true)) && (0 != String.Compare(ApplicationServices.UserContext.UserName, "jdoe", true))) { cmbStatus.SelectedIndex = 0; } } } } if(false == e.Cancel) { cleanpUp(); } } } catch(exKeyEnter) { MessageBox.Show("KeyEnter Exception: " + exKeyEnter.message); } } else if( (e.CommandValue == MNEProtocol.KeyF3) || (e.CommandValue == MNEProtocol.KeyF03)) { try { giicInstanceController.remove_Requesting(OnRequesting); } catch(exClose) { MessageBox.Show("F3 pressed, exception when removing requesting Event " + exClose.message); } } } } catch(ex) { MessageBox.Show(ex.message); } } // check to see if two strings are equal (ignoring case) // this includes if they are both Null or Empty // return true if they are the same private function areStringsEqual(astrString1 : String, astrString2 : String) { var bResult : boolean = false; if( (false == String.IsNullOrEmpty(astrString1)) && (false == String.IsNullOrEmpty(astrString2)) ) { if(0 == String.Compare(astrString1, astrString2, true)) { bResult = true; } } else { if( (true == String.IsNullOrEmpty(astrString1)) && (true == String.IsNullOrEmpty(astrString2)) ) { bResult = true; } } return(bResult); } // check to see if we have changed any details private function haveWeChangedAnyDetails() { var bResult : boolean = false; // compare our existing TextBoxes against the original values if(false == areStringsEqual(gstrOriginalBank, tbBank.Text)) { bResult = true; } if(false == areStringsEqual(gstrOriginalBranch, tbBranch.Text)) { bResult = true; } if(false == areStringsEqual(gstrOriginalAccount, tbAccount.Text)) { bResult = true; } if(false == areStringsEqual(gstrOriginalSuffix, tbSuffix.Text)) { bResult = true; } return(bResult); } // this is the heart of the class // we will turn the background textbox to green // if we have a semi-valid account // If we know that we don't have the correct number // of characters then we set the background to orange // and we cancel the saving of the information public function validBankAccount() { var bResult : boolean = false; try { var bError : boolean = false; // this is where we keep track of if there is an error var bBankOK : boolean = false; var bBranchOK : boolean = false; var bAccountOK : boolean = false; var bSuffixOK : boolean = false; var strError : String = null; // in our world, our bank is two characters if(false == String.IsNullOrEmpty(tbBank.Text)) { if(tbBank.Text.Length != 2) { strError = "Bank is incorrect"; tbBank.Background = System.Windows.Media.Brushes.Orange; bError = true; } else { bBankOK = true; } } else bBankOK = true; if(true == bBankOK) { tbBank.Background = System.Windows.Media.Brushes.LightGreen; } // 4 characters for the branch if(false == String.IsNullOrEmpty(tbBranch.Text)) { if(tbBranch.Text.Length != 4) { tbBranch.Background = System.Windows.Media.Brushes.Orange; bError = true; } else { bBranchOK = true; } } else bBranchOK = true; if(true == bBranchOK) { tbBranch.Background = System.Windows.Media.Brushes.LightGreen; } // 7 characters for the account if(false == String.IsNullOrEmpty(tbAccount.Text)) { if(tbAccount.Text.Length != 7) { tbAccount.Background = System.Windows.Media.Brushes.Orange; bError = true; } else bAccountOK = true; } else bAccountOK = true; if(true == bAccountOK) { tbAccount.Background = System.Windows.Media.Brushes.LightGreen; } // three characters for the suffix if(false == String.IsNullOrEmpty(tbSuffix.Text)) { if(tbSuffix.Text.Length != 3) { tbSuffix.Background = System.Windows.Media.Brushes.Orange; bError = true; } else bSuffixOK = true; } else bSuffixOK = true; if(true == bSuffixOK) { tbSuffix.Background = System.Windows.Media.Brushes.LightGreen; } if( (false == bSuffixOK) && (false == bAccountOK) && (false == bBranchOK) && (false == bBankOK) ) { } else bResult = true; } catch(ex) { MessageBox.Show("Error validating the bank account"); } if(true == bError) { // cancel the save bResult = false; MessageBox.Show("Sorry, but you haven't entered the account number correctly"); } return(bResult); } } }
Be aware that this is the first cut being presented for testing…so it’s entirely possible that you may find a scenario that it breaks in…
Have fun!
Hi Scott, Personalisations can be easily turned by the (from memory) ‘disable all personalisations’ option in the menu. Another option for something like this is Smart Notifications from LBI or the equivalent product from the BI toolset you’re using. You’ll do a query on the table storing the bank account details and fire an email to the CFO whenever they change. Again not security, rather an audit trail that addresses the risk. Al.
Hi Al, thanks for the suggestion – the disable personalisations only disables the ability to change the personalisations for a panel rather than the whole application?
Smart Notifications – I had forgotten about them; no LBI here but it parallels a conversation I had on Friday after the acceptance testing; we discussed running a scheduled report to check for changes in bank account details…
Fun, fun, fun 🙂
Hi Scott. Disable personalisations disables jscript, formatting etc. across the LSO session. Al.