A geek stranded on Martha’s Vineyard
Understanding Strong Parameters in Rails 4
This article was originally published in the blog of ActBlue Technical Services.
Earlier this year, in ActBlue, we completed the upgrade of our main application from Rails 3.2 to 4.1. The last step in the process was incorporating strong parameters.
Several of our models are simple and we did not have a problem following the documentation, RailsCast and other articles. But a good number of models (and its controllers) are more complex or were using our own protection mechanism, and we were dragging our feet on them.
The main issue was that we did not really understand how strong parameters worked. Correction: I did not understand, which is the big realization that took place when I had to explain what I was doing to the rest of the team. The part that is missing from all the articles I read is what happens when you do not use strong parameters in the correct way. For this reason, the best solution was a series of tests, which is what we present here.
In order to minimize the dependencies, the tests were written in MiniTest::Spec, and the only additional gem required is mocha. Ruby version is 2.1.4 and Rails 4.1.6.
The full list of tests can be found in this github repo, following we go through the most important ones:
If you change the version of Rails in your Gemfile from 3.2 to 4.1, without making any other changes, parameters in the controllers will be passed untouched to methods new and update_attributes on your models, raising this error:
describe 'completely unhandled params (i.e. no call to permit)' do before do @hash = { 'title' => 'Test', 'body' => 'test body' } @params = ActionController::Parameters.new @hash end it 'should raise error' do -> { Article.new @params }.must_raise ActiveModel::ForbiddenAttributesError end end
The next step is to start whitelisting some parameters by calling the permit method. For sure, you will miss some, which will trigger this notification:
describe 'permit some elements from params and miss others' do before do @params = ActionController::Parameters.new @hash end it 'should log non-permitted attributes' do ActiveSupport::Notifications. expects(:instrument). with('unpermitted_parameters.action_controller', keys: ['body']). once @params.permit(:title).must_equal 'title' => 'Test' end end
In Rails 3.2 when you create or update objects on a model and include attributes that do not exist they willl just be ignored. Version 4.1 raises an exception, which is a better way of handling it, no one would ever rely on the fact they are ignored, right?
describe 'call permit on attributes that do not exist in the model' do before do params = ActionController::Parameters.new @hash. merge('non_attr' => 'test') @permitted_params = params.permit(:title, :non_attr) end it 'ActiveRecord should raise exception on non-attribute' do ex = -> { Article.new @permitted_params }. must_raise ActiveRecord::UnknownAttributeError ex.message.must_equal 'unknown attribute: non_attr' end end
The require method is useful when you want to make sure that certain parameter is present, if not it will raise an exception:
describe 'require something that is not present in params' do before do hash = { 'article_attributes' => {'title' => 'Test', 'body' =>'test body'}, 'other_attributes' => {'amount' => '12', 'color' => 'blue' } } @params = ActionController::Parameters.new hash end it 'should raise exception' do ex = -> { @params.require(:category_attributes) }. must_raise ActionController::ParameterMissing ex.message.must_equal 'param is missing or the value is empty: ' + 'category_attributes' end end
Note that if there are other keys in the params hash they will be removed:
it 'should filter out everything but the required key' do @params.require(:article_attributes).must_equal 'title' => 'Test', 'body' =>'test body' end
Additionally, requiring without calling permit will raise an exception:
it 'should raise error if we try to use it as is (without permit)' do -> { Article.new @params.require(:article_attributes) }. must_raise ActiveModel::ForbiddenAttributesError end
Finally the “standard” way to use strong parameters, this is the form that you will see presented in other blog posts.
describe 'proper use' do before do @attributes = @params.require(:article_attributes). permit(:title, :body) end it 'should be fine if we use require and permit combined' do @article = Article.new @attributes @article.title.must_equal 'Test' @article.body.must_equal 'test body' end end
Nested attributes on a model that has a one-to-one relation. In the example a comment belongs_to an article:
it 'shows how to permit nested params' do attributes = @params. require(:comment). permit(:author, article_attributes: [:id, :title, :body]) attributes.must_equal "author" => "John Smith", "article_attributes" => { "title" => "Test", "body" => "test body"} comment = Comment.new attributes comment.author.must_equal 'John Smith' comment.content.must_be_nil comment.article.title.must_equal 'Test' comment.article.body.must_equal 'test body' end
Nested attributes on a model with a one-to-many relation. The example shows an article that has_many comments. Note that the controller will receive an extra level of keys, the integers 0, 1, etc. Rails internally ignores these numbers so we don’t have to include them in the call to permit:
describe 'nested attributes on articles (which has_many comments)' do before do @hash = { "article" => { "title" => "Test", "body" => "test body", "comments_attributes" => { '0'=>{"author" => "John", "content" => "great"}, '1'=>{"author" => "Mary", "content" => "awful"} } } } @params = ActionController::Parameters.new @hash end it 'shows how to permit nested parameters' do attributes = @params. require(:article). permit(:title, comments_attributes: [:author, :content])[:comments_attributes]. must_equal @hash['article']['comments_attributes'] end end
The repo has additional cases and also shows how the code looks when you are doing updates.
In the next blog post will show the specific steps we took during the upgrade of our application.
Introduction to Contributing to Open Source
Last week I gave a lightning talk on this topic at Boston Ruby Group. There was nothing new in the technical part of my presentation; everyone in the audience was a professional developer and was already following good security practices.
However, I was impressed by the number of people who were not aware that now it’s not only good practice; it’s the law. And if you don’t do it, you’re subject to penalties.
I decided to write this short post with the highlights in order to help spread the word. You can get the full presentation from SlideShare.
I also provide several links below to get more information and resources to help your small business become compliant.
Name
The official name of the regulation is
201 CMR 17.00: Standards for the Protection of Personal Information of Residents of the Commonwealth
When
The regulation became effective March 1, 2010
Who
Any individual, company, or organization, that stores personal information of residents of the Commonwealth of Massachusetts must comply
What is Personal Information?
Person’s first name (or initial) and last name and anyone of these:
- Social Security number
- Driver’s license number
- Account number: credit card, bank account, etc.
Administrative Requirements
The regulation requires one to maintain a comprehensive written information security program (WISP). This document must analyze the risks to security and describe how they’ll be prevented. Some of its elements are:
- Designate one person to be in charge of security
- Security policies
- Restrictions upon access to personal information
- Keeping audit trails
- Overseeing service providers
Technical Requirements
The most important are:
- Firewalls, anti-virus, keeping software updated
- User authentication: passwords or biometrics
- Access control (permission is granted when needed only)
- Encryption is mandatory for:
- Email containing personal information
- Wireless networks
- VPN for remote access
- Laptops and other portable devices
My Recommendations
The simplest way to comply with the regulations is not maintaining any personal information (PI) at all. However, this is rarely the case. If you own a business with 2 employees and keep their Social Security numbers, you must comply.
It’s easy to use web-based services to do payroll, this way the PI won’t be saved in your computer, but with your service provider. Keep paper records in a locked file cabinet inside a secure office. You still need the WISP though (see link to sample below).
If You Need Encryption
The encryption requirement is what has created more problems among small businesses, because they don’t have the expertise to implement the technology.
The easiest way to comply is again: don’t do it.
If you need to send documents containing personal information to your customers, use fax, FedEx, even first class is compliant.
And don’t save personal information in your laptop, this way there’s no need to encrypt it.
Encryption technology has been around for a long time and it’s free if you use open source software, like GPG for email and TrueCrypt1 for encrypting laptops.
The problem with GPG is that it’s complicated for non-technical users, especially the part that involves handling the cryptographic keys.
There are several products that provide easy to use solutions to send and receive secure messages with your customers. I like to recommend the one I developed: Solid Secure. There’s no software to install. It’s very easy to use because it works just like Gmail or Yahoo Mail. But as the owner of your business, you control who becomes a user.
Resources and Additional Information
- Complete content of 201 CMR 17.00 from the official website of the Commonwealth of Massachusetts
- Frequently Asked Questions Regarding 201 CMR 17.00 by the Office of Consumer Affairs and Business Regulations
- Compliance checklist by the Office of Consumer Affairs and Business Regulations
- Article by a Boston lawyer explaining 201 CMR 17.00
- A Sample WISP you can adapt to your organization needs
- Encryption guides, how to use GPG and TrueCrypt1
Disclaimer: I am not a lawyer and this is not legal advice
How to install Doom on the Raspberry Pi
My kids were intrigued by what I was doing as I followed this SparkFun tutorial on how to install Doom on the Raspberry Pi, they were making questions and seem so motivated that I asked if they wanted to do it themselves, they accepted right away.
Although the instructions are good, they are a little outdated and some steps can be simplified. I decided to improve them and post them here.
List of hardware
- Raspberry Pi model B
- SanDisk SDHC 16 GB class 4 memory (all the software requires less than 2 GB)
- Logitech K120 USB keyboard
- USB micro cable
- USB mouse
- Ethernet cable
In case of problems with the peripherals, there’s a list of compatible hardware and a separate list for the SD cards.
There’s also a quick start guide with instructions on how to connect all the components.
Download Arch Linux ARM image
Download the Arch Linux ARM image from the Raspberry Pi website (scroll down to the Arch Linux ARM section).
I wasn’t able to get the .zip file with the direct download and had to get a BitTorrent client from here. Once installed, just click the archlinux-hf-2013-02-11.zip.torrent link on the browser and it will open the BitTorrent client for you. The file is 200 MB and it took 8 minutes to download with my connection.
Write Linux image to SD card
These are instructions for the Mac, for other operating systems look here
These commands and actions need to be performed from an account that has administrator privileges.
(Optional) Verify if the the hash key is the same, in the terminal run this command and compare the long sequence of characters against the SHA-1 field below the link you just clicked (at the time of writing this post it was 1d2508908e7d8c899f4a5284e855cb27c17645dc)
$ shasum ~/Downloads/archlinux-hf-2013-02-11.zip
Extract the image:
$ unzip ~/Downloads/archlinux-hf-2013-02-11.zip
this will create file called archlinux-hf-2013-02-11.img in the current directory
From the terminal run df -h
you’ll get something like this:
Filesystem Size Used Avail Capacity Mounted on /dev/disk0s2 698Gi 89Gi 609Gi 13% / devfs 186Ki 186Ki 0Bi 100% /dev map -hosts 0Bi 0Bi 0Bi 100% /net map auto_home 0Bi 0Bi 0Bi 100% /home
Insert the SD card, run df -h
again and look for the new device that wasn’t listed last time. Record the device name of the filesystem’s partition, in my case it was /dev/disk2s1
Filesystem Size Used Avail Capacity Mounted on /dev/disk0s2 698Gi 89Gi 609Gi 13% / devfs 186Ki 186Ki 0Bi 100% /dev map -hosts 0Bi 0Bi 0Bi 100% /net map auto_home 0Bi 0Bi 0Bi 100% /home /dev/disk2s1 15Gi 2.3Mi 15Gi 1% /Volumes/NO NAME
Unmount the partition so that you will be allowed to overwrite the disk, you need to enter your password
$ sudo diskutil unmount /dev/disk2s1
Using the device name of the partition work out the raw device name for the entire disk, by omitting the final “s1” and replacing “disk” with “rdisk” (this is very important: you will lose all data on the hard drive on your computer if you get the wrong device name). Make sure the device name is the name of the whole SD card as described above, not just a partition of it (for example, rdisk2, not rdisk2s1. Similarly you might have another SD drive name/number like rdisk2 or rdisk4, etc. – recheck by using the df -h command both before and after you insert your SD card reader into your Mac if you have any doubts!):
In our case, the partition is /dev/disk2s1 then the device is /dev/rdisk2
In the terminal write the image to the card with this command, using the raw disk device name from above (read carefully the above step, to be sure you use the correct rdisk# here!):
$ sudo dd bs=1m if=./archlinux-hf-2013-02-11.img of=/dev/rdisk2
This command will take around 4 minutes and will not feedback any information until there is an error or it is finished, in my case this is what I got back:
1850+0 records in 1850+0 records out 1939865600 bytes transferred in 229.546995 secs (8450843 bytes/sec)
Before taking the card out you must eject it with this command:
$ sudo diskutil eject /dev/rdisk2
Put the SD card into the Raspberry Pi. Connect your display, keyboard, mouse, ethernet cables to the Pi. Lastly, connect power through the micro USB cable. The Pi should boot up Arch Linux ARM and get to the log-in screen.
Log in with login: root and password: root
Important
If at any time you want to cut the power to the Pi you must first shut it down. This is the command to do it:
$ shutdown -h now
System configuration
Login as root.
Change root password:
$ passwd
Edit the locale.gen file. All you need to know about the nano editor is here
$ nano /etc/locale.gen
Search for this line and make sure it’s uncommented, i.e. there’s no leading #
en_US.UTF-8 UTF-8
Generate locales:
locale-gen
To define your local timezone, find your region or country under this directory:
$ ls /usr/share/zoneinfo
Then find the file that best represents where you live inside that region, in my case:
$ ls /usr/share/zoneinfo/America
Using the full path create a symbolic link:
$ ln -sf /usr/share/zoneinfo/America/New_York /etc/localtime
When the system displays the date, it should contain the correct timezone:
$ date Sun May 18 13:25:46 EST 2013
Using nano make sure the file /etc/locale.conf contains these 2 lines:
LANG=en_US.UTF-8 LC_COLLATE=C
Define the name for your RPi:
$ nano /etc/hostname
Change alarmpi to doom or any name containing only lower case letters and numbers.
Modify /etc/hosts, look for the line 127.0.0.1 localhost.localdomain localhost
$ nano /etc/hosts
Add at the end of that line the same name you used under /etc/hostnanme:
127.0.0.1 localhost.localdomain localhost doom
Full system update
Verify that your Pi is connected to the Internet through the Ethernet port and login as root. If you forget to connect it’s better to reboot with shutdown -r now
and login again.
The package manager in Arch Linux is called pacman, perform a full system update by issuing this command (note this is a capital S).
$ pacman -Syyu
Once you provide confirmation, it will take several minutes (20 in my case) to download and install the software. Once complete, run this to verify the update was successful:
$ pacman -Syu
It should report nothing to do.
Install sudo and create a user account
The sudo command allows regular users to perform operations that require root priviledges. Install sudo:
$ pacman -Sy sudo
Allow users to run sudo:
$ EDITOR=nano visudo
Uncomment (take away the #) from the line that says: %wheel ALL=(ALL) ALL. Save and exit.
Create a regular user login (change mary for the desired user name, and assign it a password:
$ useradd -m -g users -G audio,lp,optical,storage,video,wheel,games,power,scanner -s /bin/bash mary $ passwd mary
Reboot Pi:
$ shutdown -r now
Log back in with the user and password you just created.
Install sound drivers
Login as the newly created user.
Install Advanced Linux Sound Architecture (Alsa), required for sound:
$ sudo pacman -Sy alsa-firmware alsa-utils
The Pi has 2 audio outputs: HDMI and analog. If you have an HDMI monitor without sound capabilities, but have old fashioned speakers connected to the analog port, the audio won’t work. This forces the Pi to use the analog output and sets volume to maximum:
$ amixer cset name='PCM Playback Route' 1 $ amixer cset name='PCM Playback Volume' 100%
To test the audio execute this command. It will play a “beach” sound that alternates between the left and right speakers.
$ speaker-test -c 2
To stop the test hit Control-C.
If you have trouble setting the audio this article can help.
Important
Remember that you must shutdown before cutting power to the Pi, because you’re logged in as a regular user you must run the command using sudo: ‘sudo shutdown -h now`
Install video drivers
Install Xorg, pre-requisite for GUI apps
$ sudo pacman -Sy xorg-server xorg-xinit xorg-utils xorg-server-utils
Install Xorg framebuffer video driver:
$ sudo pacman -Sy xf86-video-fbdev
Install Openbox and Xterm
Install Openbox and configuration utilities
$ sudo pacman -Sy openbox obconf obmenu lxappearance
Make initial configuration files for your Openbox user:
$ mkdir -p ~/.config/openbox $ cp /etc/xdg/openbox/menu.xml ~/.config/openbox $ cp /etc/xdg/openbox/rc.xml ~/.config/openbox
Create/Modify user’s .xinitrc file
$ nano ~/.xinitrc
Add the following line to this file:
exec dbus-launch openbox-session
Before starting Openbox for the first time, let’s install a terminal:
$ sudo pacman -Sy xterm
Check to see if Openbox works now:
$ startx
This is the graphical interface. All you see is an empty screen with a gray background. Right-click and a menu will pop-up. Select Terminals and then Xterm. This will open the same terminal you’ve been using, but inside a window.
If you want to exit the GUI select Log Out from the pop-up menu.
Desktop background
In the terminal window we just opened type the following command, it will install feh, an image viewer.
$ sudo pacman -Sy feh
Make a directory for your background, then go to it:
$ mkdir ~/wallpapers $ cd ~/wallpapers
Find a wallpaper on the net then download with wget:
$ wget http://carreno.me/pi_wallpaper.jpg
Set your wallpaper (make sure to change destination to actual username and filename):
$ feh --bg-scale /home/mary/wallpapers/pi_wallpaper.jpg
To make your desktop load when starting Openbox, modify file autostart:
$ nano ~/.config/openbox/autostart
Add line:
$ sh ~/.fehbg &
Install programming environment
Install the base development package so you can compile C programs:
$ sudo pacman -Sy base-devel
This installs gcc and make plus other useful tools you may need for development.
Install Python 3 (python) or Python 2.7 (python2)
$ sudo pacman -Sy python python2
Install DOOM
Install chocolate-doom. Chocolate-doom is a shareware port for running DOOM game files (.wad files).
$ sudo pacman -Sy chocolate-doom
Get shareware wad file for playing DOOM demo (you can also use your full game .wad files if you own the game, I tested the Doom II wad file and it works fine):
$ cd /usr/share/doom $ sudo wget http://www.jbserver.com/downloads/games/doom/misc/shareware/doom1.wad.zip
Unzip the file, but first install the zip/unzip utilities:
$ sudo pacman -Sy zip unzip $ sudo unzip doom1.wad.zip
Run DOOM!!!!
$ chocolate-doom -iwad /usr/share/doom/DOOM1.WAD -window
Forget the -window if you want full screen but it will run slower. The smaller the window, the less math the GPU has to do.
References
How to test login from multiple IP addresses in Rails
At the request of a customer, I had to implement sending a notification email every time the application detects two active sessions for the same user from different IP addresses. I could not find a good example on how to test this functionality, so I decided to write a post showing my solution.
I created integration test test/integration/multiple_ip_test.rb
. I my opinion, Rails integration tests are underutilized, this makes difficult to find sample code that goes beyond the basic cases.
require 'test_helper' @@default_ip = "127.0.0.1" class ActionController::Request def remote_ip @@default_ip end end class MultipleIpTest < ActionDispatch::IntegrationTest fixtures :all test "send email notification if login from different ip address" do post_via_redirect login_path, :user => {:username => "john", :password => "test"} assert_equal "/users/john", path reset! @@default_ip = "200.1.1.1" post_via_redirect login_path, :user => {:username => "john", :password => "test"} assert_equal "/users/john", path assert_equal 1, ActionMailer::Base.deliveries.size end end
Integration tests look a lot like functional tests, but there are some differences. You cannot use @request
to change the origin IP address. This is why I had to open the ActionController::Request
class and redefine the remote_ip
method.
Because the response to post_via_redirect
is always 200, instead of using assert_response :redirect
I use the URL to verify the user has logged in successfully.
The call to reset!
is necessary to start a new session.
For an introduction on integration tests, check the Rails Guides on testing, unfortunately they do not mention the reset!
method.