Tuesday, February 17, 2015

Enterprise Management: User-Agent Switcher for Chrome

When I first wrote the User-Agent Switcher for Chrome, I imagined that it would be useful for administrators who wanted their users to use Chrome, but hack around legacy web apps that expected and required ancient browser versions.  Anyone using the steaming turd that is Outlook Web Access knows what I'm talking about.

However, Chrome didn't offer a way to manage extensions' settings -- only policies for managing itself...until recently.  With policy for extensions, Chrome now allows administrators to send policy settings directly to extensions -- not only can you deploy this extension, you could also deploy all the user-agents and permanent spoofs.

Setting policy for extensions is similar to setting policy for other applications on the target OS: the registry on Windows, a protected directory on Linux, Workgroup Manager on Mac, etc.  Chrome looks for these policies and then applies them to the extension (but only if they are crafted correctly -- more on that later.)

In version ~1.0.38, I added support for policy in the extension: I added the ability to set the user-agents, permanent spoofs, and a few of the other settings...and I hope to add more controls in the future.

To take advantage of this, you'll need to follow the steps below.  Unfortunately, every OS has its own format and own way of handling admin policies, so you may have to follow several of these to get coverage on your entire fleet.

Side note: I often found that any misspelling, or slightly-off structure of JSON or XML would cause policy to silently fail (*cough* broken user journey *cough*), so be sure your structure is correct.  Some debugging tips are at the very end of this post.

What Policies Are Supported

I added support for the most typical things an admin would change -- if you peek down at some of the sample policy files below, the structure should make some sense.  The policies currently supported include:
  • UserAgents -- a list of user-agent strings available for the user.  This is a List of Dictionaries/Hashes that contain the following values:
    • title [string] - the name of the user-agent ("Chrome 41")
    • ua_string [string] - the value of the user-agent string
    • vendor [string] - the value of the "navigator.vendor" field
    • badge [string] - the couple of letters that show up over the icon when spoofing
    • append [boolean]- whether the ua_string value should be appended to Chrome's user-agent (false means replace it)
  • PermanentSpoofs -- a list of permanent spoofs.  This is a List of Dictionaries/Hashes that contain the following values:
    • domain - [string] the domain regular expression to match.
    • user_agent - [dictionary] - this is a full UserAgent object (see above).  This is the user-agent to use with the permanent spoof.
  • EditRights -- several one-off settings for defining what users can edit.  This is a Dictionary.  Regardless of what you set here, users cannot modify user-agents or spoofs provided via policy.
    • user_agents [boolean] - whether the user can edit/add/delete user_agent strings.
    • permanent_spoofs [boolean] - whether the user can edit/add/delete permanent spoofs.
  • Other Settings -- more one-off settings representing the "Other Settings" section in the options screen.  This is a Dictionary of values.
    • hotlist_enabled [boolean] - Whether the user can switch the spoofed user-agent on the fly.
    • spoof_override [boolean] - Whether the on-the-fly spoof user-agent overrides permanent spoofs.
    • spoof_per_tab [boolean] - Whether the on-the-fly spoof applies only to the current tab.

Setup on Chrome OS / Google-managed Chrome

The Admin console has a place to manage extensions' settings (more info here.)  When you find the part of the admin console for setting extensions' policies, you can use JSON of this form:

{ "UserAgents": { "Value": [ {"title": "1", "ua_string": "ua_1", "vendor": "v_1", "badge": "A1", "append" : false}, {"title": "2", "ua_string": "ua_2", "vendor": "v_2", "badge": "A2", "append" : false}, {"title": "3", "ua_string": "ua_3", "vendor": "v_3", "badge": "A3", "append" : true} ] }, "PermanentSpoofs": { "Value": [ {"domain" : "foo.com", "user_agent": {"title": "4", "ua_string": "ua_4", "vendor": "v_4", "badge": "A4", "append" : false}}, {"domain" : "bar.com", "user_agent": {"title": "5", "ua_string": "ua_5", "vendor": "v_5", "badge": "A5", "append" : true}} ] }, "EditRights": { "Value": [ {"user_agents" : false, "permanent_spoofs": false} ] }, "OtherSettings": { "Value": { "hotlist_enabled" : false, "spoof_override": true, "spoof_per_tab": false } } }


Setup on Windows

Full disclosure: I haven't tested this at all, since I don't have any Windows machines to work on anymore, but I *think* this is the right structure.  Feedback is greatly appreciated on whether this works or not.


[HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\jhefaickifmkaeoijafmdniimnjfohef\policy\UserAgents\1
"title"="Test"
"ua_string"="Test UA 1"
"vendor"="Testing"
"badge"="aaa"
"append"=false

[HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\jhefaickifmkaeoijafmdniimnjfohef\policy\PermanentSpoofs\1
"domain"="foo.com"

[HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\jhefaickifmkaeoijafmdniimnjfohef\policy\PermanentSpoofs\1\user_agent
"title"="Test 2"
"ua_string"="Test UA 2"
"vendor"="Testing 2"
"badge"="bbb"
"append"=true

[HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\jhefaickifmkaeoijafmdniimnjfohef\policy\EditRights
"user_agents"=false
"permanent_spoofs"=false

[HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\3rdparty\extensions\jhefaickifmkaeoijafmdniimnjfohef\policy\EditRights
"hotlist_enabled"=false
"spoof_override"=false
"spoof_per_tab"=false

Again, I'm not totally sure this is exactly right since I don't have a good way to test it, but please post in the comments if it does.


Setup on Mac

Mac devices use "plist" (policy list?) files to distribute settings -- these are glorified xml text files.  The format for this extension looks like this:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>com.google.Chrome.extensions.jhefaickifmkaeoijafmdniimnjfohef</key>
  <dict>
    <key>UserAgents</key>
    <dict>
      <key>state</key>
      <string>always</string>
      <key>value</key>
      <array>
        <dict>
          <key>title</key>
          <string>mac_plist_1</string>
          <key>ua_string</key>
          <string>mac plist 1</string>
          <key>vendor</key>
          <string>mac vendor 1</string>
          <key>badge</key>
          <string>aaa</string>
          <key>append</key>
          <false/>
        </dict>
        <dict>
          <key>title</key>
          <string>mac_plist_2</string>
          <key>ua_string</key>
          <string>mac plist 2</string>
          <key>vendor</key>
          <string>mac vendor 2</string>
          <key>badge</key>
          <string>bbb</string>
          <key>append</key>
          <true/>
        </dict>
      </array>
    </dict>
    <key>PermanentSpoofs</key>
    <dict>
      <key>state</key>
      <string>always</string>
      <key>value</key>
      <array>
        <dict>
          <key>domain</key>
          <string>www.foo1.com</string>
          <key>user_agent</key>
          <dict>
            <key>title</key>
            <string>mac_plist_3</string>
            <key>ua_string</key>
            <string>mac plist 3</string>
            <key>vendor</key>
            <string>mac vendor 3</string>
            <key>badge</key>
            <string>ccc</string>
            <key>append</key>
            <true/>
          </dict>
        </dict>
        <dict>
          <key>domain</key>
          <string>whatsmyuseragent.com</string>
          <key>user_agent</key>
          <dict>
            <key>title</key>
            <string>mac_plist_4</string>
            <key>ua_string</key>
            <string>mac plist 4</string>
            <key>vendor</key>
            <string>mac vendor 4</string>
            <key>badge</key>
            <string>ddd</string>
            <key>append</key>
            <false/>
          </dict>
        </dict>
      </array>
    </dict>
    <key>EditRights</key>
    <dict>
      <key>state</key>
      <string>always</string>
      <key>value</key>
      <dict>
        <key>user_agents</key>
        <true/>
        <key>permanent_spoofs</key>
        <true/>
      </dict>
    </dict>
    <key>OtherSettings</key>
    <dict>
      <key>state</key>
      <string>always</string>
      <key>value</key>
      <dict>
        <key>hotlist_enabled</key>
        <true/>
        <key>spoof_override</key>
        <true/>
        <key>spoof_per_tab</key>
        <false/>
        <key>send_errors</key>
        <false/>
      </dict>
    </dict>
  </dict>
</dict>
</plist>



Take this content, save it in a file with a name like "abc.plist".  You'll obviously want to update this to adjust to the settings you want -- these are just my test settings.  Mac administrators can deploy this file via Workgroup Manager, with some guidance on how to do this on the Chromium site.


Setup on Linux

I've never tested this on Linux myself, but I surmise that if you use JSON of the same structure as the Chrome OS policy, and put it in a JSON file in the right location (some basic instructions here.)


Why Isn't It Working / Debugging 

In my development and testing, I found it very common for the policies I was trying to set to not appear at all.  Here's a couple of tricks I used to debug issues with getting policies picked up:


  • http://about:policy is a huge help: it shows if the browser is picking up policies for extensions.  If it's not appearing here, it's not getting to the browser.
  • Any misspelling, missing comma (JSON), using true instead of , etc. will make the OS and the browser just ignore the policy.  If the policy isn't appearing in about:policy, or in the OS's policy viewer, there is a good chance the structure is off.
  • When encountering #2, I found it helpful to get just one *very* basic policy working, then build up the other necessary policies one at a time.


Final Thoughts


I'm hoping that some admins out there who are trying to hack around some legacy systems' problems will find this useful.  Not every setting has a corresponding policy, so there's more work for me to do.  I also need to test on more systems, but I think this should work.

Feedback is greatly appreciated -- feel free to leave it here, or on my G+ page.