James Anders In the lead role of janders223

14May/120

Rails 3 with Devise Google, and Google Apps Authentication

About three years ago, my company switched from Lotus Notes to Google for email, and have since not been happier with email and all of  the things that come with Google. Maybe two years ago we started developing our own applications in Ruby on Rails applications. I have worked on them off and on for about the last year on a part time basis when I wasn't busy with other things. And now recently, I have been "promoted" into a full time development position as we have more and more projects and more and more maintenance on existing ones.

Naturally, with the advances of OAuth, one of the first requests that was posed to me was 'We need to do single sign on with our Google apps domain'. Cool, we already use Devise, and they allow OAuth authentication, so this should be easy right? Wrong. As usual, with any open source software, documentation and tutorials are rarely in sync and the developer is left to either monkey patch existing solutions, or find the right combinations of gems and solutions. For my solution, I had to do a little of both and am documenting it to help others and for future reference.

First we need to find the correct OAuth gem for our solution. For standard Google OAuth we will be using the omniauth-openid gem from Intridea, and for Google Apps we'll use the omniauth-google-apps gem. So in our Gemfile let's add:

Select All Code:
1
2
3
4
5
# for Google Apps
gem 'omniauth-google-apps'
 
# for standard Google
gem 'omniauth-openid'

Don't forget to bundle install after updating your Gemfile.

Next up, we need to ensure that devise knows we want to use it's omniauthable model. Open up user.rb and add the module

Change this:

Select All Code:
4
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable

To this:

Select All Code:
4
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable

Now let's tell Devise about our Omniauth strategy. In config/initializers/devise.rb add the following:

Select All Code:
1
2
3
4
5
6
7
8
9
10
# Outside of the configuration block
require 'openid/store/filesystem'
 
# Inside the configuration block
 
# For Google Apps
config.omniauth :google_apps, :store => OpenID::Store::Filesystem.new('/tmp'), :domain => 'your-domain.com'
 
# For standard Google
config.omniauth :open_id, :store => OpenID::Store::Filesystem.new('/tmp'), :name => 'google', :identifier => 'https://www.google.com/accounts/o8/id', :require => 'omniauth-openid'

What's going on here? First we make sure we require openid/store/filesystem. This will ensure that openid has access to write the transfer data to the filesystem during the authentication process. Then in config.omniauth we specify either Google Apps or openid, and we also declare the :store to the /tmp directory. Note that if you plan to deploy to Heroku you don't have write access to the filesystem, except for the ./tmp directory and will need to substitute that instead. For Google Apps authentication you will need to declare your domain name to authenticate against. This will ensure that only users inside of your organization can sign in to your application with Google. For standard Google authentication we add :identifier, which points to Google's OAuth URL, we require omniauth-openid, and we specify a name that we'll use later in our OmniuathCallbacks controller.

With these building blocks in place, we're now ready to dive in and get all this working. Let's go back to the user model and add a couple of methods.

Select All Code:
11
12
13
14
15
16
17
18
19
def self.find_for_open_id(access_token, signed_in_resource=nil)
  data = access_token['info']
 
  if user = User.where(:email => data['email']).first
    return user
  else
    User.create!(:email => data['email'], :password => Devise.friendly_token[0,20])
  end
end

Here we define a method to find a user for openid. We pass in all of the the data returned by Google as access_token and set signed_in_resource to nil. We then set a local data variable to hold the info from the access_token. Next we check to see if we have a user that matches the returned email address and if not we create a new one creating a generic password for them. We'll also add the following method to our user model:

Select All Code:
21
22
23
24
25
26
27
def self.new_with_session(params, session)
  super.tap do |user|
    if data = session['devise.googleapps_data'] && session['devise.googleapps_data']['user_info']
      user.email = data['email']
    end
  end
end

Here we're checking to make sure that the data returned is Google Apps data and user info, then setting user.email to the email returned in the data. Now we'll override Devise's OmniauthCallbacks Controller to use our strategy. Let's create a new file app/controllers/callbacks_controller.rb and fill it with:

Select All Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CallbacksController < Devise::OmniauthCallbacksController
 
  skip_before_filter :verify_authenticity_token, :only => [:google_apps]
 
  def google_apps
    @user = User.find_for_open_id(request.env["omniauth.auth"], current_user)
 
    if @user.persisted?
      flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
      sign_in_and_redirect @user, :event => :authentication
    else
      session["devise.google_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end
end

First, we're skipping the before filter that verifies the authenticity token for our action. Our action will be based upon which authentication we use. For Google Apps we are using google_apps and standard Google we'll just use google. Then we check to see if the user has been persisted and sign them in if they are and create them if they aren't. No we have to tell Devise to use our new controller. Open routes.rb and modify our user resource:

Select All Code:
3
devise_for :users, :controllers => { :omniauth_callbacks => "callbacks" }

And the final piece to tie all of this together is to provide the link for users to sign in inside of app/views/layout/application.html.erb :

Select All Code:
16
link_to 'Sign in', user_omniauth_authorize_path(:google_apps)

And that's it! Now when users click 'Sign in' they will be passed either directly to Google or to your Google Apps domain for authentication! As an alternative solution, you can offer the standard Google authorization, and your users can then use their Google Apps account, however if you wish to limit then to your domain only, then use the Google Apps version. Full source code is available on Github.

31Oct/112

Why I Code Better in Public

Productivity is always one of the hardest things to measure, especially it seems in programming. What do you measure as a solo programmer? Sure there are milestones in any project, and they can be a decent indicator of long term productivity, but I am thinking more short term. What constitutes a productive day, writing one class, one view or something else?

While I can't put a measurement on it, I always feel more productive when I work in public places. Most people will immediately say, "I could never work someplace public, there are too many distractions", and before you do, let me explain.

For me, the distractions at say a Starbucks is far less than those at home. At home, there is television, the screaming kids next door, and my comfortable couch. Now there are resolutions to these, turn the TV off, headphones, and sit at my desk, but even then I struggle to focus. My brain just works differently I guess. It feels as though it has something do with familiarity and comfort.

I get into this same funk, and lack of productivity when I am at my desk at work. The answer to this, is often "office squatting" or working from one of our many conference rooms, but this sometimes doesn't even work. Also, spending as much time as I do in hotel rooms, that same funk creeps it's nasty head back in. Thankfully, hotels have lobbies, restaurants, and bars that are always filled with people and my "productivity boosting" distractions.

Call it ADD or whatever you will, but the more little distractions that I have, the better I am able to focus on the task at hand. I can put my headphones on, sip a Venti Americano and just crank out some code while sitting at a Starbucks. A quick glance to check out the woman walking in the door, popping over to Spotify to add a song to a playlist, none of these little things seem to stop my workflow. I also very closely follow the Pomodoro technique while at a coffe shop, whereas at home, I always forget to start the timer.

These little, short burst, public distractions, over the longer distractions in private make me feel far more productive. And of course I write this, or rather try, as I sit in my hotel room with Monday Night Football on, which has increased the time to done exponentially. This also makes me curious, where are you more productive?

2Oct/110

My Mongoid, Devise, CanCan Implementation

This tutorial is more so that I can remember exactly what I did in the future when I am trying to implement this again, but I hope it can also help those looking to do something similar. I searched around for quite some time trying to find an implementation similar to what I was looking for, or even something with part of what I was looking for, with no luck. That left with me figuring it as I went, reading documentation, reading blog posts, and a lot of trial and error.

I didn't feel comfortable with trial and error on my actual application, so I created a separate application, implementing everything that I wanted to do in my actual code. I am not going to walk through the steps of creating the application, setting up mongoid, devise, or cancan, they have documentation for that. I will also only being showing the relevant bits of code, but the example is based up on the project management example that you always see in Railscasts. The full working example code is available on Github. Lastly, there are no tests in this code, other than what I checked in the browser.

 

First up is my Gemfile, to show the gems I am using. Yes I am developing against Rails 3.0.9 as that is what my company's apps are on.

Select All Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
source 'http://rubygems.org'
 
gem 'rails', '3.0.9'
gem 'mongoid'
gem 'bson_ext'
gem 'nested_form'
gem 'devise'
gem 'cancan'
 
group :development, :test do
  gem 'rspec-rails'
  gem 'database_cleaner'
  gem 'autotest'
  gem 'webrat'
  gem 'factory_girl_rails'
end

Be sure to run bundle install to install those gems. Next up, we'll create our default devise installation and user model.

Select All Code:
1
rails g devise User

Then of course we will want our Devise views.

Select All Code:
1
rails g devise:views

Next I want to override the Devise Registrations Controller, as I don't want users to be able to sign up directly, I want to an admin to manage them. Generate that registrations controller and we'll override a few of it's methods.

Select All Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class RegistrationsController < Devise::RegistrationsController
 
  def new
    if can? :manage, User
      super
    else
      flash[:failure] = "Registration is not allowed"
      redirect_to root_path
    end
  end
 
  def create
    build_resource
 
    if resource.save
      set_flash_message :notice, :signed_up
      redirect_to after_sign_up_path_for(resource)
    else
      clean_up_passwords(resource)
      render_with_scope :new
    end
  end
 
  def update
    self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
 
    if resource.update_with_password(params[resource_name])
      set_flash_message :notice, :updated if is_navigational_format?
      redirect_to after_update_path_for(resource)
    else
      clean_up_passwords(resource)
      respond_with_navigational(resource){ render_with_scope :edit }
    end
  end
 
  protected
 
    def stored_location_for(resource)
      nil
    end
 
    def after_sign_in_path_for(resource)
      projects_path
    end
 
    def after_sign_up_path_for(resource)
      dashboard_index_path
    end
 
    def after_update_path_for(resource)
      dashboard_index_path
    end
 
end

First of all, take note that the controller inherits from Devise::RegistrationsController and not ApplicationController. Next I am overriding the new method, with the can? method from CanCan. This checks if the current user can manage users, and if so just presents the basic new method from Devise with the call to super. If the user cannot manage users, then we have a flash message and redirect to the root path. Then for the create action, I had to do a little override to take out the signin redirect after creation. As an admin, I don't want to sign in as the user I just created, I probably want to create another user. Overring the update method is simply to remove login and logout and change up the redirect. Under the protected declaration I am overriding some methods to change my redirects after those actions. The first one here is key, because by default Devise always redirects to root, so we set stored_location_for(resource) to nil so that we can set it ourselves in the following methods.

Also notice that I am only overriding the methods that I need to. This is the beauty of Devise, you have the opportunity to completely customize it to your needs.

Next up, since I have implemented a CanCan method, here is my ability.rb.

Select All Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Ability
  include CanCan::Ability
 
  def initialize(user)
 
    if user.role == "Admin"
      can :manage, :all
    end
 
    if user.role == "User"
      can :update, User
      can [:create, :index], Project
      can [:update, :read], Project do |project|
        project.user_id == user.id
      end
    end
 
  end
end

A lot of good things here. There are two different calls to can for the Project model because, I need any user to be able to use the create and index action on the Projects Controller, but for show and update, I only want projects that belong to the current user.

 

The last part of tying all of this together really is the routes file.

Select All Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DeviseTest::Application.routes.draw do
 
  get "dashboard/index"
 
  devise_for :users, :controllers => { :registrations => "registrations" }
  devise_scope :user do
    get '/login' => 'devise/sessions#new'
    get '/logout' => 'devise/sessions#destroy'
  end
 
  resources :users, :controller => "user", :only => :destroy
 
  resources :projects
 
  root :to => 'home#index'
 
end

A couple of things to note going on in here. We are telling devise_for to override the default controller to use our registrations controller with :controllers => { :registrations => "registrations" }. I had a typo for days, in that I forgot the s on the :controllers, it took a stackoverflow post for someone else to catch my typo. The second thing is that I am using a User Controller for the destroy action. There is nothing special about the destroy action of Devise, and since I am using a dashboard style user management interface, I just implement a simple destroy method in a User Controller.

And that's really it, just remember to use the devise routes when wanting to create and edit users. And to use the can? method for anything that you need to authorize. If you pull down or check out the source on Github, please don't make fun of the lack of my CSS skills. I welcome any improvements or suggestions for the code that you care to submit.

 

The full source code:

https://github.com/janders223/mongoid_devise_cancan_admin

I wish that I could cite all of my sources that I found in pulling this together, but can't remember where I pulled all of the bits and bytes from. Here are the big ones though.

Mongoid:

http://mongoid.org/

Devise:

https://github.com/plataformatec/devise

CanCan:

https://github.com/ryanb/cancan

23Sep/110

Rails 3, Mongoid, Devise, CanCan, and my take on it all

This has been me

This guy to the left. That guy right there has been me for the last couple of days. You see I am fairly new to Ruby on Rails, and am still learning. The hardest part is that I don't develop full time. I have that job that puts food on my table and a roof over my head. Developing is just my passion, and I only get to do it in my free time, and when I have an idea, and ideas have been sparse.

A few weeks ago, one of my managers (yes I have many), approached me about an application that my company needed while we were hanging out in his garage. The end goal was to get it up and running without incurring too much cost. He knows that I am looking to further improve my skills as a developer, and thought that I may want to take a stab at it. And boy did I ever.

While I can't get into the details of the app, there are some basic requirements I can divulge. It needed to run on MongoDB, which is perfect because I have been looking for an excuse to start using and learning Mongo. It has to do user accounts with roles. I had heard of Devise and CanCan, but really hadn't had a need to use them yet. Now the question was, how do I tie all of these together, to accomplish the goals of my application.

Naturally, I dove right in, setup a new project, added the gems, and started coding. I Googled a few things here and there, but thought I had it. Boy was I wrong. Reading a snippet of code here and another snippet over there wasn't the right answer. So I Googled some more, and never really came up with one good example, tutorial, or anything that would remotely point me in the right direction of what I was looking for.

Being back at square one, still knowing nothing about MongoDB, Devise, and CanCan, I did what I should have done in the first place, read the documentation, and re-read the documentation, until I had a firm understanding of how everything worked separately. Then I dove back into the code, and hammered out a quick and dirty example application of something similar to what I wanted.

So, first as a reminder to myself of exactly how I set it up, and secondly as a hope that I can help anyone else as stuck as I was, I am going to put together a brief tutorial-ish series of posts. I'll start it from nothing and build it into a working application. I'll have the finished product running on a server, as well as the full source code available on GitHub. Look for the first post to come sometime in the neat future.

The second part of this post with the code is available here

12Jun/110

A Bored Saturday Hacking with Arduino

Today was quite the boring day for me. After spending the last two weeks traveling for work, I just wanted to veg out, and I did, watched several movies. But then that wasn't enough. So I broke out the Arduino boxes and set out to just create something from what I have on hand.

I wanted to revisit my nRF24L01+ and Nordic FOB, but couldn't find my old sketches or the wiring diagrams for it. This led me to pull out the ST7565 LCD, but what could I do with this? Well then I saw my TMP36 temperature sensor, and an idea started to form. Simple, I'll just display my temperature data on the LCD.

First I had to get the LCD wired up. I used Adafruit's tutorial, since it is their product, and loaded up the example sketch. This gave me the expected output of all of the awesome graphical stuff that this LCD is capable of. Step 1 done.

ST7565 LCD

Step 2 was getting the temperature sensor wired up and transmitting data. Again I turned to an Adafruit tutorial, wired it up and loaded their example sketch. This got me displaying temperature data into the serial monitor. Now things were getting interesting.

Serial Output Temperature Data

Next was the slightly tricky part. Displaying the temperature data, stored in a float variable, converted to a char to be displayed on the LCD. I tried several methods, and many Google searches, and came up with nil. The first example I found was to just typecast the float as char, like this:

Select All Code:
1
2
3
4
char str[32];
float f = 12.215;
 
str = char(f);

This example, while it will work well in straight C code, did not play nicely with the Arduino IDE. Compile errors, showed that it wouldn't convert the char to char[32].

Select All Code:
1
error: incompatible types in assignment of 'char' to 'char [32]'

Then I found the sprintf method. This looked to be exactly what I needed. After implementing this into my sketch, I was getting output on the LCD, but it was nowhere near what I was expecting from the serial output.

Select All Code:
1
sprintf(AI1String,"AI1 Value: %0d.%d", temp1);

This showed my temperature as -21875. This led me to believe that it was something in my temperature conversion. I double checked the math at least 20 times, and even reverted back to the example TMP36 code to make sure it was right. What I didn't realize, is that the sprintf function won't convert a float to char, but it will convert an int to a char. So the only thing I needed to do was typecast my float as an int inside of the sprintf function.

Select All Code:
1
sprintf(msg,"Current Temp: %d", (int)tempF);

Finally! I was displaying temperature on the LCD! Of course I still wasn't finished yet. I happen to have a photocell sensor laying here, and a backlit LCD. Why not make it self dimming? So I wired up the photocell with yet another Adafruit tutorial, just replacing the LED in their example with the LED of the LCD backlight, and viola! A self dimming temperature displaying LCD.

In the future I think I may add a barometric or humidity sensor, and a time module and make myself one of those fancy weather stations. The wiring schematic is a little rough I know, you'll have to excuse my first attempt at writing one. In the mean time, here is a video of this guy in action, and the source code.

Wiring Diagram

Click through for larger image

 

 

Select All Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "ST7565.h"
#include <stdio.h>
 
int AI1Raw = 0;
int photocellPin = 1;
int photocellReading;
int ledPin = 11;
int ledBrightness;
char msg[32];
int reading;
float volts;
float tempC;
float tempF;
 
ST7565 glcd(9, 8, 7, 6, 5);
#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH  16 
 
void setup()
{
  Serial.begin(9600);
  glcd.st7565_init();
  glcd.st7565_command(CMD_DISPLAY_ON);
  glcd.st7565_command(CMD_SET_ALLPTS_NORMAL);
  glcd.st7565_set_brightness(0x18);
}
 
void loop()
{
  photocellReading = analogRead(photocellPin);
 
  Serial.print(photocellReading); Serial.println(" Analog Reading");
 
  photocellReading = 1023 - photocellReading;
 
  ledBrightness = map(photocellReading, 0, 1023, 0, 255);
  analogWrite(ledPin, ledBrightness);
 
  delay(100);
 
  reading = analogRead(AI1Raw);
 
  volts = reading * 5.0;
  volts /= 1024.0;
  Serial.print(volts); Serial.println(" volts");
  tempC = (volts - 0.5) * 100;
  Serial.print(tempC); Serial.println(" degrees C");
  tempF = (tempC * 9.0 /5.0) + 32.0;
  Serial.print(tempF); Serial.println(" degrees F");
  sprintf(msg,"Current Temp: %d", (int)tempF);  
 
  glcd.drawstring(0, 0, msg);
  glcd.display();    
 
  delay(1000);
 
  glcd.clear();
}
Page 1 of 4
1
2
3
4