Active Directory Federation Services (ADFS) Authentication Adapter

https://github.com/Microsoft/adfsAuthAdapters/tree/master/UsernamePasswordSecondFactor

Microsoft has provided source code to allow a Username and Password to be used as a second factor of authentication in ADFS. You may ask, why would you want to have the usernam and password as a second factor?

  1. Some automated hacking attempts will can lock out your accounts and/or identify a username and password and obtain access through ADFS or another non-authenticated ADFS system. You can setup a different primary authentication like DUO and then username/password seconds.
  2. You may want to allow Windows Integrated Authentication (WIA) for internal connections but require certain relying parties to still require a password for access. Think payroll/financial applications.

The second reason is precisely why I initially was interested, however once it was compiled and deployed to ADFS, clicking the sign in button would not work but hitting the enter key would. After some diagnosing I determined that I was getting Javascript errors on the page referencing InputUtil. I remembered that years ago I had adjusted the onload.js Javascript for the login page of ADFS so that it did not require the domain name within the username text box. https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/advanced-customization-of-ad-fs-sign-in-pages. This Javascript function was still being called during the second factor authentication and causing the Javascript in the authentication adapter to not run.

To fix this, there are two things that need to be adjusted. Open the file StringResources.resx within the Visual Studio project and go to the key AuthPage. Add the following Javascript below, above function Login(), compile and redeploy to ADFS.

function LoginErrors(){
this.userNameFormatError = 'Enter your user ID in the format 
\u0026quot;domain\user\u0026quot;
 or \u0026quot;user@domain\u0026quot;.'; 
this.passwordEmpty = 'Enter your password.'; 
this.passwordTooLong = 'Password must be shorter than 128 characters.';
}; 
var maxPasswordLength = 128;

function InputUtil(errTextElementID, errDisplayElementID) {
if (!errTextElementID)  errTextElementID = 'errorText'; 
if (!errDisplayElementID)  errDisplayElementID = 'error'; 

this.hasFocus = false;
this.errLabel = document.getElementById(errTextElementID);
this.errDisplay = document.getElementById(errDisplayElementID);
};
 InputUtil.prototype.canDisplayError = function () {
     return this.errLabel && this.errDisplay;
 }
 InputUtil.prototype.checkError = function () {
     if (!this.canDisplayError){
         throw new Error ('Error element not present');
     }
     if (this.errLabel && this.errLabel.innerHTML) {
         this.errDisplay.style.display = '';        
         var cause = this.errLabel.getAttribute('for');
         if (cause) {
             var causeNode = document.getElementById(cause);
             if (causeNode && causeNode.value) {
                 causeNode.focus();
                 this.hasFocus = true;
             }
         }
     }
     else {
         this.errDisplay.style.display = 'none';
     }
};
 InputUtil.prototype.setInitialFocus = function (input) {
     if (this.hasFocus) return;
     var node = document.getElementById(input);
     if (node) {
         if ((/^\s*$/).test(node.value)) {
             node.focus();
             this.hasFocus = true;
         }
     }
 };
 InputUtil.prototype.setError = function (input, errorMsg) {
     if (!this.canDisplayError) {
         throw new Error('Error element not present');
     }
     input.focus();
if (errorMsg) {
    this.errLabel.innerHTML = errorMsg;
}
this.errLabel.setAttribute('for', input.id);
this.errDisplay.style.display = '';
};
 InputUtil.makePlaceholder = function (input) {
     var ua = navigator.userAgent;
if (ua != null && 
    (ua.match(/MSIE 9.0/) != null || 
     ua.match(/MSIE 8.0/) != null ||
     ua.match(/MSIE 7.0/) != null)) {
    var node = document.getElementById(input);
    if (node) {
        var placeholder = node.getAttribute("placeholder");
        if (placeholder != null && placeholder != '') {
            var label = document.createElement('input');
            label.type = "text";
            label.value = placeholder;
            label.readOnly = true;
            label.style.position = 'absolute';
            label.style.borderColor = 'transparent';
            label.className = node.className + ' hint';
            label.tabIndex = -1;
            label.onfocus = function () { this.nextSibling.focus(); };

            node.style.position = 'relative';
            node.parentNode.style.position = 'relative';
            node.parentNode.insertBefore(label, node);
            node.onkeyup = function () { InputUtil.showHint(this); };
            node.onblur = function () { InputUtil.showHint(this); };
            node.style.background = 'transparent';

            node.setAttribute("placeholder", "");
            InputUtil.showHint(node);
        }
    }
}
};
 InputUtil.focus = function (inputField) {
     var node = document.getElementById(inputField);
     if (node) node.focus();
 };
 InputUtil.hasClass = function(node, clsName) {
     return node.className.match(new RegExp('(\s|^)' + clsName + '(\s|$)'));
 };
 InputUtil.addClass = function(node, clsName) {
     if (!this.hasClass(node, clsName)) node.className += " " + clsName;
 };
 InputUtil.removeClass = function(node, clsName) {
     if (this.hasClass(node, clsName)) {
         var reg = new RegExp('(\s|^)' + clsName + '(\s|$)');
         node.className = node.className.replace(reg, ' ');
     }
 };
 InputUtil.showHint = function (node, gotFocus) {
     if (node.value && node.value != '') {
         node.previousSibling.style.display = 'none';
     }
     else {
         node.previousSibling.style.display = '';
     }
 };

You’ll also need to adjust the onload.js Javascript you imported into your ADFS theme. Change function if(Login) with the following.

if(Login) {
     Login.submitLoginRequest = function () { 
    //New Line - This checks to see if the user name text box is visible or not
     if(document.getElementById(Login.userNameInput) != null){
         var u = new InputUtil();
         var e = new LoginErrors();
         var userName = document.getElementById(Login.userNameInput);
         var password = document.getElementById(Login.passwordInput);
         if (userName.value && !userName.value.match('[@\\]')) 
         {
             var userNameValue = 'lorettosystem\' + userName.value;
             document.forms['loginForm'].UserName.value = userNameValue;
         }  
  if (!userName.value) {
       u.setError(userName, e.userNameFormatError);
       return false;
    }


    if (!password.value) 
    {
        u.setError(password, e.passwordEmpty);
        return false;
    }
      document.forms['loginForm'].submit();
return false;
}else{
    document.forms['loginForm'].submit();
    return false;
}
};
 }


Popup Intercept – One Window

Released my first publically available Google Chrome Extension called Popup Intercept – One Window!

https://chrome.google.com/webstore/detail/popup-intercept-one-windo/fpkgadnkojegbmhdflhhcjkhgjijiakm

This extension allows you to customize which addresses will open up into a tab of the main Google Chrome window via the options menu. I know there are some other extensions out there that do something similar but none of them offered a way to customize it by URL.

 

Why did you make this?

This started with created an extension that I could click a button and perform an action, however that action needed to be performed on a popup window. Unfortunately, popup windows don’t have a menu or extensions available to them.

Removing locally installed printers and replacing with print server

Recently ran into an environment where there was a print server installed and managing all 200 printers however some users (380 computers) still had printers installed locally and were printing directly to the printer through the IP address or host name. This can cause issues with permissions, driver updates, or logging.

Instead of manually touching each computer I created a PowerShell script to verify it was a network printer installed locally and if it existed on the print server to install it for the user, reducing phone calls to the help desk. The script also confirms they are on the corporate network to continue and run and also verifies the printer is online so that it doesn’t remove personal printers that may have been installed for home use.

 
   1: #Only run if on the corporate network to prevent removing personal printers

   2: $Continue = $false

   3:  

   4: $DNSAddresses = Get-DNSClientServerAddress | Select-Object -ExpandProperty ServerAddresses

   5:  

   6: foreach ($DNSAddresses in $DNSAddresses)

   7: {

   8:     #Check DNS server IP to ensure on corporate network

   9:     if($DNSAddresses -eq "192.168.x.x")

  10:     {

  11:         $Continue = $true

  12:         Write-Host "We are on corporate network, continuing.."

  13:         break

  14:     }

  15: }

  16:  

  17: if($Continue -eq $true)

  18: {

  19:     $LocalPrinters = Get-Printer

  20:  

  21:     foreach ($printer in $LocalPrinters)

  22:     {

  23:         if($printer.Type -eq 'Local')

  24:         {

  25:  

  26:             $Port = $printer.PortName.Split("_")

  27:        

  28:             #Checking if a live printer on corporate network

  29:             $ConnectionResults = Test-Connection $Port[0] -Count 1 -Quiet

  30:  

  31:             #Checking if printer is a network printer

  32:             if($ConnectionResults -eq $true)

  33:             {

  34:                 Write-Host "Deleting printer: " $printer.Name

  35:                 Remove-Printer -Name $printer.Name

  36:  

  37:                 #Checking if printer is on print server so we can re-add it

  38:                  $PrintServerPrinterPortName = Get-Printer -ComputerName "printserver.domain.org" -Name $Port[0]

  39:  

  40:                 Write-Host "Printers found on print server with same port name: "$PrintServerPrinterPortName.Count

  41:  

  42:                 if($PrintServerPrinterPortName.Count -gt 0)

  43:                 {

  44:                     $AddPrinter = "\\printserver.domain.org\" + $Port[0]

  45:                    Write-Host "Adding printer from print server: " + $AddPrinter

  46:                   

  47:                    Add-Printer -ConnectionName $AddPrinter

  48:                 }

  49:                 else

  50:                 {

  51:                     Write-Host "Did not find printer on print server with same port name. Checking printer name."

  52:                     $PrintServerPrinterName = Get-Printer -ComputerName "printserver.domain.org" -Name $printer.Name

  53:  

  54:                     Write-Host Write-Host "Printers found on print server with same printer name: "$PrintServerPrinterName.Count

  55:  

  56:                     if($PrintServerPrinterName.Count -gt 0)

  57:                     {

  58:                         $AddPrinter = "\\printserver.domain.org\" +$printer.Name

  59:                         Write-Host "Adding printer from print server: " + $AddPrinter

  60:                         

  61:                         Add-Printer -ConnectionName $AddPrinter

  62:                     }

  63:                     else

  64:                     {

  65:                         Write-Host "Exhausted all options and no printers found."

  66:                     }                

  67:                 }

  68:             }        

  69:         }

  70:     }

  71: }

  72:  

Remove DNS NS Records after demoting domain controller with PowerShell

 

Get-DnsServerZone | ForEach-Object { Get-DnsServerResourceRecord -ZoneName $_.ZoneName -RRType Ns | Where-Object {$_.RecordData.NameServer -like ‘DCName.fqdn.com.‘} | Remove-DnsServerResourceRecord -ZoneName $_.ZoneName -Confirm:$false }

 

Replace the bold test with the fully qualified domain name of the name server. Don’t forget to keep the period at the end of it as well.

Directory Services–Cannot Change Password – Constraint Violation nTSecurityDescriptor

 

Recently I ran into an issue where trying to enable or disable the option ‘Cannot Change Password’ in Active Directory in my C# code. Using a Domain Administrator account the code worked perfectly fine, but when it was run under a non-administrator I would get “Constrain Violation Occurred” and the following exception

"0000051B: AtrErr: DSID-030F22B2, #1:\n\t0: 0000051B: DSID-030F22B2, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 20119 (nTSecurityDescriptor)\n"

Now the user in question was delegated full control over the user object which made it more frustrating as it could be done manually within Active Directory Users and Computers. What I did find out is that if the non-administrator user was assigned the owner of the user in the security option the code would work and is what lead me to the answer.

 

You must have the DirectoryEntry.Options.SecurityMasks defined to SecurityMasks.Dacl for it to work for a non-administrator user.

 

Allow Change Password

   1: public static void AllowChangePassword(DirectoryEntry user)

   2:        {

   3:            user.Options.SecurityMasks = SecurityMasks.Dacl;

   4:  

   5:            // Create a Guid that identifies the Change Password right.

   6:            Guid changePasswordGuid =

   7:                new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}");

   8:  

   9:            // Get the ActiveDirectorySecurity for the user.

  10:            ActiveDirectorySecurity userSecurity = user.ObjectSecurity;

  11:  

  12:            // Create a SecurityIdentifier object for "everyone".

  13:            SecurityIdentifier everyoneSid =

  14:                new SecurityIdentifier(WellKnownSidType.WorldSid, null);

  15:  

  16:            // Create a SecurityIdentifier object for "self".

  17:            SecurityIdentifier selfSid =

  18:                new SecurityIdentifier(WellKnownSidType.SelfSid, null);

  19:  

  20:            // Create an access rule to allow everyone the change password 

  21:            // right. 

  22:            // This is used to remove any existing access rules.

  23:            ActiveDirectoryAccessRule allowEveryone =

  24:                new ActiveDirectoryAccessRule(

  25:                    everyoneSid,

  26:                    ActiveDirectoryRights.ExtendedRight,

  27:                    AccessControlType.Allow,

  28:                    changePasswordGuid);

  29:  

  30:            // Create an access rule to deny everyone the change password right.

  31:            ActiveDirectoryAccessRule denyEveryone =

  32:                new ActiveDirectoryAccessRule(

  33:                    everyoneSid,

  34:                    ActiveDirectoryRights.ExtendedRight,

  35:                    AccessControlType.Deny,

  36:                    changePasswordGuid);

  37:  

  38:            // Create an access rule to allow self the change password right.

  39:            // This is used to remove any existing access rules.

  40:            ActiveDirectoryAccessRule allowSelf =

  41:                new ActiveDirectoryAccessRule(

  42:                    selfSid,

  43:                    ActiveDirectoryRights.ExtendedRight,

  44:                    AccessControlType.Allow,

  45:                    changePasswordGuid);

  46:  

  47:            // Create an access rule to deny self the change password right.

  48:            ActiveDirectoryAccessRule denySelf =

  49:                new ActiveDirectoryAccessRule(

  50:                    selfSid,

  51:                    ActiveDirectoryRights.ExtendedRight,

  52:                    AccessControlType.Deny,

  53:                    changePasswordGuid);

  54:  

  55:            // Remove any existing rule that gives "everyone" the change 

  56:            // password right.

  57:            userSecurity.RemoveAccessRuleSpecific(denyEveryone);

  58:  

  59:            // Add a new access rule to deny "everyone" the change password 

  60:            // right.

  61:            userSecurity.AddAccessRule(allowEveryone);

  62:  

  63:            // Remove any existing rule that gives "self" the change password 

  64:            // right.

  65:            userSecurity.RemoveAccessRuleSpecific(denySelf);

  66:  

  67:            // Add a new access rule to deny "self" the change password right.

  68:            userSecurity.AddAccessRule(allowSelf);

  69:  

  70:            // Commit the changes.

  71:            user.CommitChanges();

  72:  

  73:            user.Options.SecurityMasks = SecurityMasks.None;

  74:        }

 

Deny Change Password

   1: public static void DenyChangePassword(DirectoryEntry user)

   2:       {

   3:           user.Options.SecurityMasks = SecurityMasks.Dacl;

   4:           

   5:           // Create a Guid that identifies the Change Password right.

   6:           Guid changePasswordGuid =

   7:               new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}");

   8:  

   9:           // Get the ActiveDirectorySecurity for the user.

  10:           ActiveDirectorySecurity userSecurity = user.ObjectSecurity;

  11:  

  12:           // Create a SecurityIdentifier object for "everyone".

  13:           SecurityIdentifier everyoneSid =

  14:               new SecurityIdentifier(WellKnownSidType.WorldSid, null);

  15:  

  16:           // Create a SecurityIdentifier object for "self".

  17:           SecurityIdentifier selfSid =

  18:               new SecurityIdentifier(WellKnownSidType.SelfSid, null);

  19:  

  20:           // Create an access rule to allow everyone the change password 

  21:           // right. 

  22:           // This is used to remove any existing access rules.

  23:           ActiveDirectoryAccessRule allowEveryone =

  24:               new ActiveDirectoryAccessRule(

  25:                   everyoneSid,

  26:                   ActiveDirectoryRights.ExtendedRight,

  27:                   AccessControlType.Allow,

  28:                   changePasswordGuid);

  29:  

  30:           // Create an access rule to deny everyone the change password right.

  31:           ActiveDirectoryAccessRule denyEveryone =

  32:               new ActiveDirectoryAccessRule(

  33:                   everyoneSid,

  34:                   ActiveDirectoryRights.ExtendedRight,

  35:                   AccessControlType.Deny,

  36:                   changePasswordGuid);

  37:  

  38:           // Create an access rule to allow self the change password right.

  39:           // This is used to remove any existing access rules.

  40:           ActiveDirectoryAccessRule allowSelf =

  41:               new ActiveDirectoryAccessRule(

  42:                   selfSid,

  43:                   ActiveDirectoryRights.ExtendedRight,

  44:                   AccessControlType.Allow,

  45:                   changePasswordGuid);

  46:  

  47:           // Create an access rule to deny self the change password right.

  48:           ActiveDirectoryAccessRule denySelf =

  49:               new ActiveDirectoryAccessRule(

  50:                   selfSid,

  51:                   ActiveDirectoryRights.ExtendedRight,

  52:                   AccessControlType.Deny,

  53:                   changePasswordGuid);

  54:  

  55:           // Remove any existing rule that gives "everyone" the change 

  56:           // password right.

  57:           userSecurity.RemoveAccessRuleSpecific(allowEveryone);

  58:  

  59:           // Add a new access rule to deny "everyone" the change password 

  60:           // right.

  61:           userSecurity.AddAccessRule(denyEveryone);

  62:  

  63:           // Remove any existing rule that gives "self" the change password 

  64:           // right.

  65:           userSecurity.RemoveAccessRuleSpecific(allowSelf);

  66:  

  67:           // Add a new access rule to deny "self" the change password right.

  68:           userSecurity.AddAccessRule(denySelf);

  69:  

  70:           // Commit the changes.

  71:           user.CommitChanges();

  72:  

  73:           user.Options.SecurityMasks = SecurityMasks.None;

  74:       }

Outlook Prompting Password using Mapi

I’ve spent hours trying to figure out why an Outlook client kept prompting for a password when I moved the users mailbox to Exchange 2016. After hours of the normal troubleshooting I found that it did not prompt for the password when cached mode was off. After having the user connect to another machine for testing and comparing the Connection Status (Shit + right click Outlook icon in systray) I found that it wasn’t making a second connection to the Exchange Directory.

I closed Outlook, went to C:\users\username\appdata\microsoft\outlook and deleted the offlineaddressbook folder and re-opened Outlook. Whola it worked again!

Group Policy Printers not installing after update KB3170455 – MS16-087

 

Microsoft changed the way printers are installed with update KB3170455 (MS16-087). Now, for printers to be installed using group policy, the print driver must be set as true under packaged in Print Management. Some vendors have not updated their drivers to make them packaged, but there is a way to force a package to be packaged.

 

Open regedit and go to HKLM\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows *** (choose version)\Drivers\Version-3\**DriverName**

 

Set the attribute PrinterDriverAttributes to 1 and restart the print spooler on the server. The driver will now show true as packaged and will install using GPO.

Group Policy not applying to security filtered user or group

 

Came across an issue this week where a newly imaged computer would not apply group policies that were filtered to an Active Directory group. GPResult showed no reference of the group policy, but it did show that the user was in the correct AD group.

 

After much research, I found that Microsoft released a Security Update for Group Policy on June 14, 2016 (https://support.microsoft.com/en-us/kb/3163622). This security update changes the way a client workstation reads the group policy. Prior to this update being installed, if a GP was a user policy, the policy would have been read by the current user’s credentials. Once the security update is applied to the client’s workstation, all group policies are read by the computer account.

 

To fix the issue, go to the group policy in which has security filtering and click on the Delegation tab. Click Add and add Authenticated Users with Read permission. Or, you can add Domain Computers with Read permission. Adding the permission under the Delegation tab with either method, only allows the computer accounts to read the policy to apply it based on the security filter. It will not apply to all authenticated users or domain computers.

Sage 50–Automatic Backup – An unknown error occurred compressing the database files.

 

While setting up a new Sage 50 2015 server I kept getting an error message “An unknown error occurred compressing the database files.” After further troubleshooting with Microsoft Process Monitor (https://technet.microsoft.com/en-us/library/bb896645.aspx) I found that it was trying to put a temporary file in a folder called “Backup” inside the company folder. This is probably a rare condition and only seen if you try to select a backup folder other than the default.

Simply create a folder called “Backup” in the company folder and try it again.

Enterprise Google Chrome Extension Not Installing

 

Had a recent issue where a Google Chrome extension I built wasn’t installing via group policy. After a lot of troubleshooting I found an entry in HKLM\Software\Policies\Google\Chrome\ExtensionInstallSources. Once I deleted that entire registry key (not just the value), the and restarted Chrome, the extension installed using the pushed settings that is in HKLM\Software\Policies\Google\Chrome\ExtensionInstallSources.