Thursday 28 April 2011

Gateway filtering in RubyCAS

Gateway login pages are ones where a user doesn't need to be logged-in... but if they already *are* logged in, then we want to know. Example: the help or contact-us pages are available to non-logged-in users.

Often we want to write these pages using the existing templates so that if they aren't logged in, the templates are fairly simple, but if they *are* logged in, then you display the usual logged-in navigation links.

So you need a filter that will auto-authenticate a user *if* they are already logged in, but doesn't *require* it. The standard CAS-login filters will actually redirect non-logged in users to the login page - and we don't want that happening. So we need to write a little filter-magick using the CAS GatewayFilter.

class WelcomeController < ApplicationController
  # don't *require* login for the public pages (inc actual login page)
  skip_before_filter CASClient::Frameworks::Rails::Filter, :only => [:home, :contact_us, :terms, :cas_login]
  # instead use gateway-login filter
  before_filter :cas_gateway_login, :only => [:home, :contact_us, :terms, :cas_login]

  # ... lots of actions and stuff here

  #####################################################################################
  private
    
    # This before_filter method is used to both call the GatewayFilter and
    # also to then call the setup_cas_user method.  Note that the latter
    # will already have been called on an action, but as we declare the
    # GatewayFilter *after* this, it must be declared again.  So we use this
    # method to make sure that we do both of these in the requisite
    # before_filter.
    def cas_gateway_login
      return false unless CASClient::Frameworks::Rails::GatewayFilter
      setup_cas_user # setup @cas_current_user etc for use by other methods
    end
end # controller

After this, you can use the logged_in? method to determine whether or not to display extra navigation features.

This is one article in a series on Rails single-sign-on with rubyCAS

Saturday 23 April 2011

Gotcha: NoMethodError: undefined method RuntimeError

So... I wrote some code in a plain-ruby class that raised a RuntimeError somewhat like below:

    class MyClass
      def initialize(opts = {})
         # do stuff
         thing = opts[:thing]
         raise RuntimeError "must have a thing!" unless thing.present? && thing.is_a?(Thing)
         # more stuff
      end
    end

and when I ran my fresh new rspec spec over it -> which looks somewhat like:

    it "should raise an error if we don't pass a thing" do
      lambda {
        my_class = MyClass.new(:thing => nil)
      }.should raise_exception(RuntimeError)
    end

I kept getting something weird:

    expected RuntimeError, got 
    #<NoMethodError: undefined method `RuntimeError' for #<MyClass:0xb5acbf9c>>

You may already have spotted the problem... ah, single-character bugs, doncha love em?

Here it is.

WRONG:

     raise RuntimeError "must have a thing!" unless thing.present? && thing.is_a?(Thing)

RIGHT:

     raise RuntimeError, "must have a thing!" unless thing.present? && thing.is_a?(Thing)

of course, you can also just go ahead and leave out the RuntimeError entirely:

     raise "must have a thing!" unless thing.present? && thing.is_a?(Thing)

because it's the default anyways... which makes it nice and defaultish for you.

Tuesday 19 April 2011

RubyCAS - logged_in?

A logged_in? method is fairly common in rails authentication these days. We use it to determine if there *is* a logged-in user for layouts that are shared public and private - for example to use for greetings, login-only navigation features and displaying the correct login/logout buttons.

Once you've implemented CAS signup and a current_user method, you can use the very simple code below on your ApplicationHelper to get the logged_in? functionality

  def logged_in?
    @current_user.present?
  end

Some authentication applications (eg restful_auth) will use a call to logged_in? as an opportunity to actually try to log a user in... but RubyCAS is more complicated than a simple cookie-login... so I've left actual authentication just to the filter-mechanisms already described.

If you have templates where - if you *can* login a user, you'd like to... but you'd like them also to be available to non-logged in users, then you'll need a Gateway filter.

This is one article in a series on Rails single-sign-on with rubyCAS

Thursday 14 April 2011

If a bricks-and-mortar shop were run like a web-deploy

"Hello, emergency response unit, what is your emergency please?"

"Yes, hello, I'm glad I *finally* got you... you have to do something immediately, it's an emergency"

"Yes sir, please tell me what is the nature of your emergency"

"Welll you see, I was in the middle of taking a customer's order, and they were trying to pay me the money, but then the till drawer got stuck and..."

"I'm sorry, sir, did you say the *till* got stuck?"

"Yes, that's right! The drawer got stuck and wouldn't open, and of course, you know, that means I can't take orders... so I called you and you have to fix it."

"I'm sorry sir, we don't really deal with this level of problem - perhaps if you called your building manager during normal work hours..."

"You fix things that are broken, yes? so fix it!"

"Yes, but you've called the emergency support line, not the..."

"What? I pay for this support service, I know you can fix it... just get it working, it's losing me business!"

" have you tried ringing up the sale first?"

...silence...

*taptaptap* *kerCHING!*

"Oh"

*click*

...later that day... after the building manager arrives

"No, no, I'm sorry but you're just going to have to destroy the entire store and start over.."

"I really don't think that's necessary, it's just a few design issues that we need to sort out..."

"Just a few design issues! This is a total branding failure. The display at the far right of the store isn't showing the products we asked for, this will completely ruin our business!"

"But... you're the ones that are supposed to set those products - we just filled it with some cardboard-cutouts so you'd get the idea of how to use it"

"I don't care - it's not what I expected! I expected it to work properly and it doesn't... I swear, I've been looking at sales, and they aren't as high as they should be and so it *must* be this display putting them off. There's nothing else that will do, you just have to destroy the new store and rebuild the old store for us!"

"What? We can't do that at this stage! Do you know how much work it'd be to tear down this new building and rebuild the old again? Are you going to pay for it?"

"Why should *I* pay for it? You messed up the display!"

Another day...

"That's it, close the doors, no customers are allowed in!"

"What's going on? why are you closing up?"

"It's a disaster! You see this picture here? it should be over there! and it's hanging askew!"

"Askew? I don't see it."

"Look at the other two pictures here... they line up with the edges of the cashier's desks exactly... but this one? Do you see why it's so wrong?"

*looks*

*looks some more... *

"I'm... really sorry, but I just don't se..."

"I don't believe it. See, does this help?"

*holding up a ruler*... "the third picture is *1cm* over the edge of the desk"

"Oh, I see it now. Yes - we should straighten that up for the next store-refit"

"Next refit!" *shocked look* "It's a disaster! We can't wait until the next refit! It's terribly unprofessional!"

"...but it's scheduled for tomorrow night, and it's not like it's impacting your cashflow..."

"not impacting... !!! ... It's *ruining* our brand-position! How could you have let something like this go out the door! Can't you get *anything* right? the entire design is ruined!!!"

*stunned look*

"OMG! *that's* we we aren't taking any orders, it's all your fault! we're losing business because of this! fix it now!"

...