Sunday, 22 June 2008

Centralized Static Authorization Using Just a Hash

Deciding how your site authorization will be implemented is not a simple part. You can implement centralized authorization, distribute it over the controllers or let each model exposes its authorized resources. Of course that depends on how your authorization process will be (static, dynamic, roles appear at run time, etc).

Using centralized authorization:

  1. Define one entry point for authorization. So, you can easly disable/enable it.
  2. You will maintain only one scope of code without touching the other business logic code.
  3. If code failure happened, you can easily isolate it from the application process without dropping down the whole logic.
  4. Other business logic is independent and don't aware of the authorization. So, you can easily upgrade your authorization process if you need without touching other application parts.

In RoR, if your authorization will be static over a predefined list of roles, then you can easily implement your authorization check based on user/controller/action triple using the magic word "hash". This will done by creating a nested hash of the following levels:

  • Level one: Users Roles (Admin, Client, etc)
  • Level two: Controllers (Users, Products, Items, etc)
  • Level Three: Actions (Index, Create, Update, Destroy, etc)

Now, you need to catch the request, extract the request entities (requesting user role, controller and action) and then hash these keys to see whither this request is authorized or not.

Lets see small example. If you have two user roles in your application; Admin and User. And you have two controllers; Products and Users. Your hash will be as following:


@roles = {:Admin => {
:Users => :all,
:Products => :all
}
:User => {
:Users => {:update => :all}
:Products => {:index => :all, :show => :all}
}
}

The above hash gives the "Admin" user role the whole access to the both controllers and the whole controller actions. But, for "User" role, he can request "update" action in "Users" controller and "index" or "show" actions in the "Products" controller only.

Note I am using the value ":all" as the end of my roles permissions tree. So, it means "full access" either on the whole controller's actions or specific action.

The next step is putting this roles and the checking code into small module, include it in your ApplicationController then call the "authorize" method using before_filter.

module Authorization

@roles = {:Admin => {
:Users => :all,
:Products => :all
}
:User => {
:Users => {:update => :all}
:Products => {:index => :all, :show => :all}
}
}

def authorize
# check controllers list for the requested user
@controllers_list = @roles[@user.class.to_s.to_sym]
if (@controllers_list.nil?)
# no controllers permitted for this user role
render :text => "", :status => :unauthorized
return false
else
# check actions list inside the controller
@actions_list = @controllers_list[controller_name.to_sym]
if (@actions_list.nil?)
# the requested controller is not permitted
render :text => "", :status => :unauthorized
return false
elsif (@actions_list == :all)
return true
else
# check the called action
@called_action = @actions_list[action_name.to_sym]
if (@called_action.nil?)
render :text => "", :status => :unauthorized
return false
elsif (@called_action == :all)
return true
else
render :text => "", :status => :unauthorized
return false
end
end
end
end

end

class ApplicationController <>

include Authorization

before_filter :authorize

..............

end

You can enlarge or shrink the hash as you need (add/remove roles, controllers, etc) but the "authorize" method code will not change.

Freshbooks Ruby Gem and Its Patch

For who does not know freshbooks, it is an online service for sending invoices and tracking time. You can access this service either through its web-interface or through its exposed APIs. It is not free, but you can create a test account and try it. I used it while working in project at eSpace company.

To access your freshbooks account through the APIs, you need first to enable its APIs and use the given URL and authentication token in your API connection. You will notice that freshbooks APIs have different versions and they distinguish between them using the API access URL, e.g. for API version 2.1 (latest one at post time), your URL will be "https://espacetest.freshbooks.com/api/2.1/xml-in"

You can access freshbooks APIs from inside Ruby on Rails project either directly through http connections or using a Ruby wrapper freshbooks.rb. To install it:

gem install freshbooks

The latest version of this gem (at post time) is freshbooks-2.2. This version contains a small issue regarding API version number. When using this library, you need to pass both your freshbooks account simple URL (espacetest.freshbooks.com) and authentication token.

FreshBooks.setup(@freshbooks_url, @freshbooks_token)

Then, library code concatenates the rest of the URL. Look at this code lines taken from freshbooks.rb:

Line 35: VERSION = '2.2' # Only works with 2.1

Line 37: SERVICE_URL = "/api/#{VERSION}/xml-in"


You will notice that the version number is "2.2" and this variable "VERSION" is used in the API url. But, no API version 2.2 yet!!
So, if you used this gem as it you will get error from freshbooks API saying "Invalid API version.". All you need to do is replacing this value with "2.1"

Line 35: VERSION = '2.1' # Only works with 2.1:

Line 37: SERVICE_URL = "/api/#{VERSION}/xml-in"

Now, enjoy accessing great online invoices service through simplified Ruby wrapper ( after patching :) ).

Wednesday, 18 June 2008

PayPal Sandbox

While working in a project in eSpace contains e-commerce handling, I wanted to test my code against PayPal payment gateway. I found that PayPal have a test server called PayPal Sandbox. Using this test server you can simulate all the transactions you want to do over the actual PayPal account.

PayPal Sandbox contains the following features:

  1. Simulate actual transactions (authorize, purchase, credit, etc).
  2. You can create test accounts for sellers/buyers as many as you want.
  3. Every created test account will have its own credit card (fake one). You can use it to test you transactions and its balance will reflect your applied transactions.
  4. You can use any of your seller/buyer test accounts to enter the paypal test site and trace its transactions, create reports, etc.
  5. You can access this test sandbox from your code through PayPal APIs.

But, it contains some drawbacks:

  1. The given test credit cards does not contain verification code (CVV code), but you can use "000" instead of it in your tests.
  2. When you enable the API in your Paypal account, you will get API user name, API password and either a signature string or certificate file. Using Sandbox you will get only the signature string.

Monday, 16 June 2008

Ruby and RSA Encryption

One of the greatest security schemes in e-commerce is RSA Encryption. It depends on one published key for encryption and secured private key for decryption. Private key can also be used in signing a content.

Ruby contains a standard library for SSL (Secure Socket Layer) that helps you finding all required schemes for implementing your secure communication channel over the Internet.

If you want to use RSA schema for encrypting/decrypting your content, follow the following steps:

  1. First, you need to generate the private key and store it in secure store, let us generate one with length 1024:

    @private_key = OpenSSL::PKey::RSA.new(1024)


  2. Now, you can use this key to generate the public key as following:

    @public_key = @private_key.public_key

    To get the string representation of this key:

    @public_key.to_pem


  3. After you publish your public key, your clients can encrypt content as following:

    @encrypted_msg = @public_key.public_encrypt("text to encrypt")


  4. Now, to decrypt the incoming encrypted content, use your secured private key:

    @decrypted_msg = @private_key.private_decrypt(@encrypted_msg)

    You can also use this pair to sign your content with your signature. That can be done by encrypting your content using your secret private key and your clients can check the content authority by decrypting this content using your published public key.

Note: Don't forget that private key, public key and also the encrypted content may contains unprintable characters. To store or print them, you need to encrypt them first using Base64 encoding.

This library also contains other security protocols, check the full documentation.

Sunday, 15 June 2008

Forwarding Email as Attachment Using ActionMailer

Using the default TMail::Mail.create_forward for forwarding emails will not help you using email templates for designing the message structure.

If you have an email message and you want to forward it using ActionMailer email templates all you need to do is attach the email content into your email template with content type 'message/rfc822' and file name with extension 'eml' (message.eml).

Lets see some code do that:

  1. Create your email template view file, lets called it "forward_email.rhtml" and fill it with your required content/design.
  2. In your ActionMailer model, create a method with the same name "forward_email" that accepts string argument contain the email content you need to forward:

    def forward_email(forward_email_content)
    recipients "test@espace.com.eg"
    from "info@espace.com.eg"
    subject "Testing forward email content as attachment"
    attachment "message/rfc822" do |a|
    a.filename = 'email.eml'
    a.body = forward_email_content
    a.transfer_encoding = '7bit'
    a.content_disposition = 'inline'
    end
    end

In this way, you can forward the email content as usual but inside your designed email template.

Wednesday, 11 June 2008

Rails Access Different Payment Gatways Using ActiveMerchant

One of my Ruby on Rails projects in eSpace contains e-commerce process for charging the clients using their credit-card information. As there are many payment gateways (PayPal, Authorize.net, etc), then you just need a simple plugin/gem that helps you access those gateways. Ruby on Rails contains many plugins/gems that give you access to different payment gateways. One of the great plugins is ActiveMerchant. It gives Rails a unified API for accessing different payment gateways with very simple way.
Using ActiveMerchant you will get:

  1. Support for too many payment gateways

  2. Bogus gateway for testing all payment functions that can done on actual gateways (except recurring).

  3. Validates credit card format

The following steps shows you how to install ActiveMerchant, create a credit card object and validates it, then use the bogus gateway for processing e-commerce actions.

  1. You can install ActiveMerchant as gem:

    gem install activemerchant


    or as plugin

    ruby script/plugin install http://activemerchant.googlecode.com/svn/trunk/active_merchant


  2. Create a credit card object:

    credit_card = ActiveMerchant::Billing::CreditCard.new({
    :number => 5105105105105100,
    :month => 9,
    :year => Time.now.year + 1,
    :first_name => 'test',
    :last_name => 'account',
    :verification_value => '123',
    :type => 'visa'
    })

    You can check if the given credit card information is valid or not:

    puts credit_card.errors.to_json if !credit_card.valid?


  3. Create a bogus gateway instance:

    @gateway = ActiveMerchant::Billing::BogusGateway.new(
    :login => 'bogus',
    :password => 'bogus'
    )


  4. Now, you can use the bogus gateway instance to process any e-commerce request you want (authorize, purchase, credit, etc)

    response = @gateway.authorize(1000, @creditcard, @options)

    response = @gateway.purchase(1000, @creditcard, @options)


    Note: 1000 = 10$ (credit values in cents)

  5. If your application uses default currency different than the default currency in the targeted payment gateway, you need to initialize ActiveMerchant with your required currency. To do that, add the default currency in your environment configuration file:

    ActiveMerchant::Billing::PaypalExpressGateway.default_currency = 'GBP'


    or you can pass the currency in each interaction with the gateway:

    :currency => 'GBP'


Using the bogus gateway in your test will not help you in testing all credit card types (master card, visa, etc). Bogus gateway support only one card type "bogus" :).
Check the full documentation for ActiveMerchant.

Tuesday, 10 June 2008

Forwarding TMail::Mail Objects

Ruby contains an email handler called TMail. Rails use TMail as default email handler in ActionMailer. You can use it to manipulate email content/headers. Accessing TMail object done via TMail::Mail class, check the documentation

In the most cases, you use ActionMailer to design and deliver your emails. But what if you already have an email and need to forward it as is?

In this case you can use the TMail::Mail object directly and with simple code you can forward the email content without doing any content manipulation. The following steps shows how you can forward an email content.

  1. If you have the email content as string, you can ask TMail to parse this string into TMail::Mail object:

    @email = TMail::Mail.parse(email_content)


  2. Using the parsed TMail::Mail object, you can create a forward copy of the email using one line of code:

    @forward_email = @email.create_forward()


  3. Now, you have a prepared forward email content, you can change any field you want according to your requirements:

    @forward_email.to = 'my_friend@domain.com'
    @forward_email.from = 'me@domain.com'


    Note: don't change the forward email body, it contains the actual email content you need to forward.

  4. Next, lets deliver this forwarded email using our ActionMailer model. You need to write two lines of code to do that:

    @forward_email.write_back
    YourMailerHanlder.deliver(@forward_email)


    The first line flattens the TMail::Mail object into a MIME message ready for sending.
    The second line asks the ActionMailer to deliver the given email.

This technique will not help you in adding any new content into the forwarded email, you just forward it into an empty email as is. I did that while working in RoR project in eSpace.

Wednesday, 4 June 2008

ActionMailer: Receiving Emails Contain Attachments

Web applications may be forced to receive emails and process its content, either its body or contained attachments. ActionMailer can be used to receive emails and give you access to its content. ActionMailer functionality based on receiving TMail object of your email content and give you access on all its parts (from, to, attachments, etc).

All you need to do is add the following method to your action mailer model:

def receive(email)
...
end


"email" argument is an object of type TMail::Mail. You can use it to read email metadata:

@to = email.to
@from = email.from
@body = email.body


The nice part is accessing the email attachments (if exist) through simple code. The following code checks if the email have attachments and read each attachment file name, content type and its content:

def receive(email)
if email.has_attachments?
email.attachments.each do |attachment|
@file_name = attachment.original_filename
@content_type = attachment.content_type
@content = attachment.read
end
end

end


Using ActionMailer for receiving emails contains two magical parts:

  1. Some email bodys encoded using Base64 encoding, also the attachments. The magical part in TMail is that it handles decoding the attachment content using Base64 decoder for you. So, you access the attachment content directly without decoding it.

  2. If you read the incoming email from a system file and need to feed it to your ActionMailer.receive method, all you need to do is reading the file content and give it directly to your ActionMailer.receive method without converting it into TMail object and ActionMailer will do the rest:

    file = open("my_email")
    email_content = file.read
    file.close
    MailerAgent.receive(email_content)

The missing part in this process is that ActionMailer will not listen at the email server for catching the new emails. I used one solution for this part; you can configure your email server to catch all incoming emails, save them inside a specific folder (file per email). Then you create a rake task that run periodically, reads the folder files and send each file content to your ActionMailer.receive method. But you need to filter the spam emails in this case.

Tuesday, 3 June 2008

Rails Basic HTTP Authentication

Some web applications require authentication for accessing some or all resources. The simple case in authentication is depending on http protocol and its authentication mechanism.

There are deferent types for http authentication, the simplest one is Basic Access Authentication. It depends on encoding the user name and password using Base64 encoding and put it in the http authorization header.

Rails helps you in using http basic authentication, or as they say in its documentation "Makes it dead easy to do HTTP Basic authentication". All you need to do is very simple:


  1. Create a separated module, may be called "Authentication"

    module Authentication

    end

  2. Create a method inside the module that will do your authentication

    def authenticate

       authenticate_or_request_with_http_basic do |username, password|

       end

    end

    Note that I put a call to authenticate_or_request_with_http_basic method in ActionController::HttpAuthentication::Basic Module that will provide you with the sent user name and password after decoding them using Base64 decoder.

  3. Now, you need to call the authentication method before every call to your controllers. That will done by putting a "before_filter" call in your ApplicationController class:

    class ApplicationController < ActionController::Base

       include Authentication

       before_filter :authenticate

    .....

    end


  4. If you need to skip some controllers from authentication chain, put in this controller a call to skip_before_filter:

    skip_before_filter :authenticate


  5. The last step is filling your authentication method with the code that will do the authentication check using the given user name and password.