Monday, 13 October 2014

Social Media Authentication On Rails- Part 2

In the last post , we have discussed how to get api keys and access for multiple social media platform .In this post we will to setup rails to integrate these applications .

Code SetUp:

Now its Time to Set Up Rails Application with MongoDB , Rails & Omniauth Gem(s).
we will be using “ruby 2.1.1p76” with “Rails 4.1.0” for our application.

Step 1:
In Gem file add the following Gems:
gem 'rails', '4.1.0'
gem 'mongoid', git: 'https://github.com/mongoid/mongoid.git'
gem 'devise'
gem 'bson_ext'
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-linkedin' 
gem 'omniauth-twitter' 
gem "omniauth-google-oauth2"
gem 'twitter'
gem "linkedin"
gem 'oauth2'
Step 2:
Follow the link and install devise gem to your application.
For Omniauth with devise follow the link which will be usefull for development and debugging your application.
Step 3:
Include required Gems in your initializers/devise.rb file and add your API KEYS with API Secret.
require 'devise/orm/mongoid'
require 'omniauth-twitter'
require 'omniauth-google-oauth2'
require 'omniauth-linkedin'
require 'omniauth-facebook'
require 'omniauth-github'

Configuring API Keys:
config.omniauth :twitter, "API KEY", "API SECRET"
config.omniauth :linkedin, "API KEY", "API SECRET", :scope => 'r_basicprofile r_emailaddress rw_nus'
config.omniauth :facebook, 'API KEY', 'API SECRET'
config.omniauth :google_oauth2, 'API KEY', 'API SECRET'

Add the following links to devise registrations/new or shared/_links.html.erb









Open Up Your User Model and Configure it as described below , fields for mongo db can be configured as per your requirement :
class User
  include Mongoid::Document
  devise :omniauthable, :omniauth_providers => [:facebook,:twitter,:linkedin,:google_oauth2]
  embeds_one :user_linkedin_connection, :class_name => 'User::LinkedinConnection'
  embeds_one :user_twitter_connection, :class_name => 'User::TwitterConnection'
  # Configured for Getting mongo key from Session in rails 4 (Mongoid)
  class << self
    def serialize_from_session(key, salt)
      record = to_adapter.get(key.first["$oid"]) if key.present? # to_adapter.get(key.to_s)
      record if record && record.authenticatable_salt == salt
    end
  end
  def self.connect_to_linkedin(auth)
    self.provider = auth.provider
    self.uid = auth.uid
    self.user_linkedin_connection = User::LinkedinConnection.new(:token => auth["extra"]["access_token"].token, :secret => auth["extra"]["access_token"].secret)
    unless self.save
      return false
    end
    true
  end
  def self.disconnect_from_linkedin!
    self.provider = nil
    self.uid = nil
    self.user_linkedin_connection = nil
    self.save!
  end
  def self.find_for_linkedin_oauth(auth, signed_in_resource=nil)
    @user = User.where(:provider => auth.provider, :uid => auth.uid).first
    if @user
      @user.connect_to_linkedin(request.env["omniauth.auth"])
      sign_in_and_redirect @user, :event => :authentication
      set_flash_message(:notice, :success, :kind => "LinkedIn") if is_navigational_format?
    else
      flash[:notice] = "Couldn't find a user connected to your LinkedIn account. Please sign in and then connect your account to LinkedIn."
      redirect_to new_user_session_url
    end
  end
  def self.find_for_facebook_oauth(auth)
   where(auth.slice(:provider, :uid)).first_or_create do |user|
     user.provider = auth.provider
     user.uid = auth.uid
     user.email = auth.info.email
     user.password = Devise.friendly_token[0,20]
     user.name = auth.info.name   # assuming the user model has a name
   end
  end
  def self.find_for_google_oauth2(access_token, signed_in_resource=nil)
    data = access_token.info
    user = User.where(:email => data["email"],:provider => "Google").first
    unless user
      user = User.create( name: data["name"],email: data["email"],provider: "Google", password: Devise.friendly_token[0,20]  )
    end
    user
  end
  def self.find_for_github_oauth(auth)
    record = where(provider: auth.provider, uid: auth.uid.to_s).first
    record || create(provider: auth.provider, uid: auth.uid, email: auth.info.email, password: Devise.friendly_token[0,20], name: auth.info.name )
  end
end
Create two More files named linkedin_connection.rb and twitter_connection.rb inside app/models/user/ which will contain the token and secret provided from provider.

linkedin_connection.rb:
class User::LinkedinConnection
  include Mongoid::Document
  include Mongoid::Timestamps
  embedded_in :user
  field :token
  field :secret
end 
twitter_connection.rb:
class User::TwitterConnection
  include Mongoid::Document           field :token
  include Mongoid::Timestamps         field :secret
  embedded_in :user
   end
Once we get a Success request from Provider , response moves on to omniauth call_back controller where we need to store the response and maintain the session.

Create a omniauth_callback controller and paste the code as provided below:

class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  before_filter :authenticate_user!
  # Linkedin authentication
  def linkedin
    @user = User.where(:provider => request.env["omniauth.auth"].provider, :uid => request.env["omniauth.auth"].uid).first
    if @user.present?
      sign_in_and_redirect @user, :event => :authentication
      set_flash_message(:notice, :success, :kind => "LinkedIn") if is_navigational_format?
    else
      auth = request.env["omniauth.auth"]
      @user = User.new #create new user to save in the database
      @user.email=  auth.info.email #save user email
      @user.provider = auth.provider
      @user.uid = auth.uid
      @user.user_linkedin_connection = User::LinkedinConnection.new(:token => auth["extra"]["access_token"].token, :secret => auth["extra"]["access_token"].secret)
      @user.save(:validate => false) #password for the linkdin to be stored
      sign_in_and_redirect @user, :event => :authentication
      set_flash_message(:notice, :success, :kind => "LinkedIn") if is_navigational_format?
    end
  end

# Facebook authentication
def facebook
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.find_for_facebook_oauth(request.env["omniauth.auth"])
    if @user.persisted?
      sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end

# Twitter authentication
def twitter
    @user = User.where(:provider => request.env["omniauth.auth"].provider, :uid => request.env["omniauth.auth"].uid).first
    if @user.present?
      sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format?
    else
      auth = request.env["omniauth.auth"] # contains all the information about the user login profile.
      @user = User.create(name: auth.extra.raw_info.name,
                  provider: auth.provider,
                  uid: auth.uid,
                  email: auth.uid+"@twitter.com", #create user email
                  password: Devise.friendly_token[0,20],
                  user_twitter_connection: User::TwitterConnection.new(:token => auth.credentials.token, :secret => auth.credentials.secret )
      )
      sign_in_and_redirect @user, :event => :authentication
      set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format?
    end
  end

 # Google authentication
  def google_oauth2
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.find_for_google_oauth2(request.env["omniauth.auth"])
    if @user.persisted?
      sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, :kind => "Google") if is_navigational_format?
      # flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
      # sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
    else
      session["devise.google_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end
end
Now we are Good to Go ahead and start authenticating with social websites in our Rails app.

Note:
• Application status in Provider Settings should be live
• Call back URL should match your application routes
• It is Good to go with Storing session in DB as sometimes cookie_store fails when a logged in user fetches request from provider.


Thanks to Santosh for wroting this post .


Social Media Authentication On Rails- Part 1

This Post is intended to provide an overview of the installation of social authentication plugins like Twitter/Github/Facebook & Google using Rails version 4 with devise(authentication plugin),omniauth(API Plugin for Social websites) & Mongo DB as underlying Database.


Before Proceeding towards installation/building a rails application that allows authentication with social websites we need to get API/Client Keys & API/Client Secret.


Here are the urls from where you can create an App and get the keys:


Facebook:

URL: Facebook Link











Twitter:>

URL: Twitter































Settings: Enter call back url as : http://127.0.0.1:3000/auth/twitter/callback/ under settings tab once you create an application














Google:

URL: Google URL










Settings: Go to API Access and enter redirect url as http://localhost:3000/users/auth/google_oauth2/callback



LinkedIn:>

URL: LinkedIn URL

Linked In Api Doesnot require any call back URI. Application URL is Enough for Linked in api as Omniauth-linked Has its method defined in Gem.













In the next post , the ROR code will be explained to integrate these social applications .



Monday, 15 September 2014

MULE FTP : Create Directory If Not Exist

FTP and SFTP endpoints of mule , does not create directory on the fly , if the directory does not exist at ftp location .
It throws error like this :
ERROR 2007-11-05 15:24:29,192 [connector.ftp.0.receiver.1] org.mule.impl.DefaultExceptionStrategy: Caught exception in Exception Strategy: Failed to change working directory to /IN. Ftp error: 550
java.io.IOException: Failed to change working directory to /IN. Ftp error: 550
        at org.mule.providers.ftp.FtpMessageReceiver.listFiles(FtpMessageReceiver.java:103)
        at org.mule.providers.ftp.FtpMessageReceiver.poll(FtpMessageReceiver.java:72)
        at org.mule.providers.PollingReceiverWorker.run(PollingReceiverWorker.java:47)
        at org.mule.impl.work.WorkerContext.run(WorkerContext.java:310)
        at edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:987)
        at edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:528)
        at java.lang.Thread.run(Thread.java:619) 
There is an Open Issue for this functionality .

To implement this functionality for FTP , we have to create a custom FTPDispatcher and integrate it in the mule ftp connector .


So first change mule ftp connector to have CustomFTPMessageDispatcher like this:
<ftp:connector name="ftp_con">
  <service-overrides dispatcherFactory="CustomFtpMessageDispatcherFactory"></service-overrides>
 </ftp:connector>
    <flow name="sftpExampleFlow1" doc:name="sftpExampleFlow1">
     
        <file:inbound-endpoint path="E:/ftp" responseTimeout="10000" doc:name="File"/>
        <ftp:outbound-endpoint host="localhost" port="21" responseTimeout="10000" doc:name="FTP" connector-ref="ftp_con"
        outputPattern="custom/path/myFile.txt" path="/home/ftp" 
        />
    </flow>
And here's the DispatcherFactory class .

import org.mule.api.MuleException;
import org.mule.api.endpoint.OutboundEndpoint;
import org.mule.api.transport.MessageDispatcher;
import org.mule.transport.ftp.FtpMessageDispatcherFactory;

public class CustomFtpMessageDispatcherFactory extends FtpMessageDispatcherFactory
{
 /** {@inheritDoc} */
    public MessageDispatcher create(OutboundEndpoint endpoint) throws MuleException
    {
        return new CustomFtpMessageDispatcher(endpoint);
    }
}


And here's the FTPMessageDispacther , which will first create a directory if the outputPattern has it .

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTPClient;
import org.mule.api.MuleEvent;
import org.mule.api.endpoint.OutboundEndpoint;
import org.mule.api.transport.DispatchException;
import org.mule.config.i18n.CoreMessages;
import org.mule.model.streaming.CallbackOutputStream;
import org.mule.transport.ConnectException;
import org.mule.transport.ftp.FtpConnector;
import org.mule.transport.ftp.FtpMessageDispatcher;

public class CustomFtpMessageDispatcher extends FtpMessageDispatcher
{

 Log logger = LogFactory.getLog(CustomFtpMessageDispatcher.class);

 public CustomFtpMessageDispatcher(OutboundEndpoint endpoint)
 {
  super(endpoint);

 }

 protected void doDispatch(MuleEvent event) throws Exception
 {
  Object data = event.getMessage().getPayload();
  String outputPattern = (String) endpoint.getProperty(FtpConnector.PROPERTY_OUTPUT_PATTERN);
  String basePath = endpoint.getEndpointURI().getPath();
  OutputStream out = null;
  if (basePath.endsWith("/"))
   basePath = basePath.substring(0, basePath.length() - 1);

  if (outputPattern != null && outputPattern.contains("/"))
  {
   try
   {
    if (outputPattern.startsWith("/"))
     outputPattern = outputPattern.substring(1, outputPattern.length());
    
    String dirs[] = outputPattern.split("/", -1);
    final FtpConnector connector = (FtpConnector) endpoint.getConnector();
    final FTPClient client = connector.getFtp(endpoint.getEndpointURI());
    for (int i = 0; i < dirs.length - 1; i++)
    {
     try
     {
      if (!dirs[i].isEmpty())
      {
       basePath = basePath + "/" + dirs[i];
       if (!client.changeWorkingDirectory(basePath))
        client.makeDirectory(basePath);
      }

     } catch (Exception e)
     {
      logger.error("Error Creating dir on ftp" + e.getMessage());
     }
    }
    
    String filename = dirs[dirs.length - 1];
    out = client.storeFileStream(filename);
    if (out == null)
    {
     throw new IOException("FTP operation failed: " + client.getReplyString());
    }

    out = new CallbackOutputStream(out, new CallbackOutputStream.Callback()
    {
     public void onClose() throws Exception
     {
      try
      {
       if (!client.completePendingCommand())
       {
        client.logout();
        client.disconnect();
        throw new IOException("FTP Stream failed to complete pending request");
       }
      } finally
      {
       connector.releaseFtp(endpoint.getEndpointURI(), client);
      }
     }
    });
   }
   catch (ConnectException ce)
         {
             // Don't wrap a ConnectException, otherwise the retry policy will not go into effect.
             throw ce;
         }
         catch (Exception e)
         {
             throw new DispatchException(CoreMessages.streamingFailedNoStream(), event, (OutboundEndpoint)endpoint, e);
         }

  } 
  else
  {
   out = connector.getOutputStream(getEndpoint(), event);
  }

  try
  {
   if (data instanceof InputStream)
   {
    InputStream is = ((InputStream) data);
    IOUtils.copy(is, out);
    is.close();
   } else
   {
    byte[] dataBytes;
    if (data instanceof byte[])
    {
     dataBytes = (byte[]) data;
    } else
    {
     dataBytes = data.toString().getBytes(event.getEncoding());
    }
    IOUtils.write(dataBytes, out);
   }
  } finally
  {
   out.close();
  }
 }

}


Post Comments And Suggestions !!!


Friday, 12 September 2014

COLOURIZED LOGGING IN RUBY(IRB) PROMPT

Wirble is a set of enhancements for ruby(irb). Wirble enables several items mentioned on the RubyGarden "Irb Tips and Tricks" page, including tab-completion, history, and a built-in ri command, as well as colorized results and a couple other goodies.

Wirble even works with rvm , lets hack the trick to do so:

Step 1:
When we start irb prompt it first checks for .irbrc file in home folder. ~/.irbrc.

Step 2 :
To enable wirble in irb prompt, Open the ~/.irbrc file (Create a new one if Not present.), and Paste the contents of the below code.
require 'irb/completion'
 require 'irb/ext/save-history'
 require 'rubygems'
 unless Gem::Specification.find_all_by_name('wirble').any?
   puts "Installing Wirble"
   %x{gem install 'wirble' --no-ri --no-rdoc}
 end
 Gem.refresh 
 require 'wirble'
# Initializing Wirble
Wirble.init
Wirble.colorize
# Customize according to your will.
colors = Wirble::Colorize.colors.merge({
  :object_class => :purple,
  :symbol => :purple,
  :symbol_prefix => :purple
})
Wirble::Colorize.colors = colors

Step 3:
Move out of the blog for a colourfull irb prompt and Njoy Coding in Ruby. Thanks to Santosh for writing this post .



Tuesday, 2 September 2014

Mule SFTP : Verify false Error

The following error might occur while using SFTP inbound or outbound endpoint in mule :
Error during login to x@192.168.2.18: verify: false
java.io.IOException: Error during login to x@192.168.2.18: verify: false
 at org.mule.transport.sftp.SftpClient.logAndThrowLoginError(SftpClient.java:200)
 at org.mule.transport.sftp.SftpClient.login(SftpClient.java:146)
 at org.mule.transport.sftp.SftpConnectionFactory.createClient(SftpConnectionFactory.java:98)
 at org.mule.transport.sftp.SftpConnector.createSftpClient(SftpConnector.java:191)
 at org.mule.transport.sftp.SftpConnector.createSftpClient(SftpConnector.java:174)
 at org.mule.transport.sftp.SftpReceiverRequesterUtil.getAvailableFiles(SftpReceiverRequesterUtil.java:75)
 at org.mule.transport.sftp.SftpMessageReceiver.poll(SftpMessageReceiver.java:62)
 at org.mule.transport.AbstractPollingMessageReceiver.performPoll(AbstractPollingMessageReceiver.java:219)
 at org.mule.transport.PollingReceiverWorker.poll(PollingReceiverWorker.java:84)
 at org.mule.transport.PollingReceiverWorker.run(PollingReceiverWorker.java:53)
 at org.mule.work.WorkerContext.run(WorkerContext.java:311)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
 at java.lang.Thread.run(Thread.java:722)
Mule SFTP uses JSch to connect to SFTP location . This error occurs with particular Java JDK updates . You might get this error If you are using following versions of jdk :
java7u6
java7u7
java7u15
This error does not occur if we use JDK version < java7u6 .

For more information please read this link.

Post Comments And Suggestions !!


Wednesday, 20 August 2014

JAXB Error : FWK005 parse may not be called while parsing

org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
Above Error occurs because you might have SchemaFactory instance , which is shared by multiple threads. Like declaring it at class level , and using it in methods .
private SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
The SchemaFactory is not thread safe . It should always be initialized and accessed in local scope. So whenever you need SchemaFactory instance , just create it locally instead of using it at instance level.


Post Comments And Suggestions !!


Tuesday, 19 August 2014

How To Link Git Tag with Rails app

I use Git tags to manage the version numbers of my Rails apps.

Every time a new version is ready, I tag the current commit like this :
git tag -a v1.10 -m "FIXED PC-43/67/78"
I have created a ruby file in initializers which defines a constant to hold this information (in this case “v1.10”):
APP_VERSION = `git describe --always` unless defined? APP_VERSION
This constant simply contains the output of Git describe.
Now I can use it anywhere in my app where I would like to display the version number.

This post is written by Santosh.

Post Comments And Suggestions