Skip to content

Commit

Permalink
Added in the Cryptify module for password encryption with specs. Firs…
Browse files Browse the repository at this point in the history
…t step towards HTTP Basic.
  • Loading branch information
Michael Bleigh committed Mar 19, 2009
1 parent 208c0ef commit 4088ed0
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 9 deletions.
19 changes: 15 additions & 4 deletions README.markdown
Expand Up @@ -16,21 +16,27 @@ Or you can install it as a traditional Rails plugin:

Note that because TwitterAuth utilizes Rails Engines functionality introduced in Rails 2.3, it will not work with earlier versions of Rails.

**NOTE:** TwitterAuth requires Rails version 2.3 or later because it makes extensive use of the new support for Rails Engines. Previous versions of Rails are not supported.

Usage
=====

*NOTE:* HTTP Basic strategy is not yet supported. Please only use OAuth until this message is removed.
**NOTE:** HTTP Basic strategy is not yet supported. Please only use OAuth until this message is removed.

To utilize TwitterAuth in your application you will need to run the generator:

script/generate twitter_auth --strategy [oauth|basic]
script/generate twitter_auth [--oauth(default)|--basic]

This will generate a migration as well as set up the stubs needed to use the Rails Engines controllers and models set up by TwitterAuth. It will also create a User class that inherits from TwitterUser, abstracting away all of the Twitter authentication functionality and leaving you a blank slate to work with for your application.

This will generate a migration as well as set up the stubs needed to use the Rails Engines controllers and models set up by TwitterAuth. It will also create a User class that inherits from TwitterUser, abstracting away all of the Twitter authentication functionality and leaving you a blank slate to work with for your application.
Finally, it will create a configuration file in `config/twitter_auth.yml` in which you should input your OAuth consumer key and secret (if using the OAuth strategy) as well as a custom callback for development (the `oauth_callback` option is where Twitter will send the browser after authentication is complete. If you leave it blank Twitter will send it to the URL set up when you registered your application).

Usage Basics
------------

*TwitterAuth* borrows heavily from [Restful Authentication](http://github.com/technoweenie/restful-authentication) for its API because it's simple and well-known. Here are some of the familiar methods that are available:
If you need more information about how to use OAuth with Twitter, please visit Twitter's [OAuth FAQ](http://apiwiki.twitter.com/OAuth-FAQ).

TwitterAuth borrows heavily from [Restful Authentication](http://github.com/technoweenie/restful-authentication) for its API because it's simple and well-known. Here are some of the familiar methods that are available:

* `login_required`: a before filter that can be added to a controller to require that a user logs in before he/she can view the page.
* `current_user`: returns the logged in user if one exists, otherwise returns `nil`.
Expand All @@ -39,6 +45,11 @@ Usage Basics
* `store_location`: store the current URL for returning to when a `redirect_back_or_default` is called.
* `authorized?`: override this to add fine-grained access control for when `login_required` is already called.

Tips and Tricks
---------------

The `oauth_callback` set up in `twitter_auth.yml`

Customizing TwitterAuth
-----------------------

Expand Down
2 changes: 2 additions & 0 deletions app/controllers/sessions_controller.rb
Expand Up @@ -8,6 +8,8 @@ def new
url = @request_token.authorize_url
url << "&oauth_callback=#{CGI.escape(TwitterAuth.oauth_callback)}" if TwitterAuth.oauth_callback?
redirect_to url
else

end
end

Expand Down
8 changes: 4 additions & 4 deletions generators/twitter_auth/templates/migration.rb
Expand Up @@ -2,13 +2,13 @@ class TwitterAuthMigration < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :login
<% if options[:oauth] %>
<% if options[:oauth] -%>
t.string :access_token
t.string :access_secret
<% elsif options[:basic] %>
t.string :crypted_password
<% elsif options[:basic] -%>
t.binary :crypted_password
t.string :salt
<% end %>
<% end -%>

# This information is automatically kept
# in-sync at each login of the user. You
Expand Down
4 changes: 4 additions & 0 deletions generators/twitter_auth/templates/twitter_auth.yml
Expand Up @@ -16,8 +16,12 @@ production:
<% else -%>
development:
strategy: basic
# randomly generated key for encrypting Twitter passwords
encryption_key: "<%= key = ActiveSupport::SecureRandom.hex(12) %>"
test:
strategy: basic
encryption_key: "<%= key %>"
production:
strategy: basic
encryption_key: "<%= key %>"
<% end %>
1 change: 1 addition & 0 deletions generators/twitter_auth/twitter_auth_generator.rb
Expand Up @@ -7,6 +7,7 @@ def manifest

m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'twitter_auth_migration'
m.template 'user.rb', File.join('app','models','user.rb')
m.template 'twitter_auth.yml', File.join('config','twitter_auth.yml')
end
end

Expand Down
7 changes: 6 additions & 1 deletion lib/twitter_auth.rb
@@ -1,12 +1,17 @@
module TwitterAuth
mattr_accessor :base_url
self.base_url = 'https://twitter.com'

def self.config(environment=RAILS_ENV)
@config ||= {}
@config[environment] ||= YAML.load(File.open(RAILS_ROOT + '/config/twitter_auth.yml').read)[environment]
end

def self.encryption_key
raise TwitterAuth::Cryptify::Error, 'You must specify an encryption_key in config/twitter_auth.yml' if config['encryption_key'].blank?
config['encryption_key']
end

def self.oauth_callback?
config.key?('oauth_callback')
end
Expand Down
30 changes: 30 additions & 0 deletions lib/twitter_auth/cryptify.rb
@@ -0,0 +1,30 @@
module TwitterAuth
module Cryptify
class Error < StandardError; end

def self.encrypt(data)
salt = generate_salt
{:encrypted_data => EzCrypto::Key.encrypt_with_password(TwitterAuth.encryption_key, salt, data), :salt => salt}
end

def self.decrypt(encrypted_data_or_hash, salt=nil)
case encrypted_data_or_hash
when String
encrypted_data = encrypted_data_or_hash
raise ArgumentError, 'Must provide a salt to decrypt.' unless salt
when Hash
encrypted_data = encrypted_data_or_hash[:encrypted_data]
salt = encrypted_data_or_hash[:salt]
else
raise ArgumentError, 'Must provide either an encrypted hash result or encrypted string and salt.'
end

EzCrypto::Key.decrypt_with_password(TwitterAuth.encryption_key, salt, encrypted_data)
end

def self.generate_salt
ActiveSupport::SecureRandom.hex(4)
end
end
end

1 change: 1 addition & 0 deletions rails/init.rb
@@ -1,5 +1,6 @@
# Gem Dependencies
config.gem 'oauth'
config.gem 'ezcrypto'

require 'json'
require 'twitter_auth'
Expand Down
8 changes: 8 additions & 0 deletions spec/spec_helper.rb
Expand Up @@ -11,6 +11,7 @@ class TwitterAuth::GenericUser
def self.table_name; 'twitter_auth_users' end
end

Object.send(:remove_const, :User)
class User < TwitterAuth::OauthUser

end
Expand Down Expand Up @@ -41,3 +42,10 @@ def stub_oauth!
'oauth_consumer_secret' => 'testsecret'
})
end

def stub_basic!
TwitterAuth.stub!(:config).and_return({
'strategy' => 'basic',
'encryption_key' => 'secretcode'
})
end
51 changes: 51 additions & 0 deletions spec/twitter_auth/cryptify_spec.rb
@@ -0,0 +1,51 @@
require File.dirname(__FILE__) + '/../spec_helper'

describe TwitterAuth::Cryptify do
before do
stub_basic!
end

it 'should have encrypt and decrypt methods' do
TwitterAuth::Cryptify.should respond_to(:encrypt)
TwitterAuth::Cryptify.should respond_to(:decrypt)
end

describe '.encrypt' do
it 'should return a hash with :encrypted_data and :salt keys' do
result = TwitterAuth::Cryptify.encrypt('some string')
result.should be_a(Hash)
result.key?(:encrypted_data).should be_true
result.key?(:salt).should be_true
end

it 'should make a call to EzCrypto::Key.encrypt_with_password' do
EzCrypto::Key.should_receive(:encrypt_with_password).once.and_return('gobbledygook')
TwitterAuth::Cryptify.encrypt('some string')
end

it 'should not have the same encrypted as plaintext data' do
TwitterAuth::Cryptify.encrypt('some string')[:encrypted_data].should_not == 'some string'
end
end

describe '.decrypt' do
before do
@salt = TwitterAuth::Cryptify.generate_salt
TwitterAuth::Cryptify.stub!(:generate_salt).and_return(@salt)
@string = 'decrypted string'
@encrypted = TwitterAuth::Cryptify.encrypt(@string)
end

it 'should return the original string' do
TwitterAuth::Cryptify.decrypt(@encrypted).should == @string
end

it 'should raise an argument error if encrypted data is provided without a salt' do
lambda{TwitterAuth::Cryptify.decrypt('asodiaoie2')}.should raise_error(ArgumentError)
end

it 'should raise an argument error if a string or hash are not provided' do
lambda{TwitterAuth::Cryptify.decrypt(23)}.should raise_error(ArgumentError)
end
end
end
12 changes: 12 additions & 0 deletions spec/twitter_auth_spec.rb
Expand Up @@ -68,4 +68,16 @@
TwitterAuth.oauth?.should be_false
TwitterAuth.basic?.should be_true
end

describe '#encryption_key' do
it 'should raise a Cryptify error if none is found' do
TwitterAuth.stub!(:config).and_return({})
lambda{TwitterAuth.encryption_key}.should raise_error(TwitterAuth::Cryptify::Error, "You must specify an encryption_key in config/twitter_auth.yml")
end

it 'should return the config[encryption_key] value' do
TwitterAuth.stub!(:config).and_return({'encryption_key' => 'mickeymouse'})
TwitterAuth.encryption_key.should == 'mickeymouse'
end
end
end

0 comments on commit 4088ed0

Please sign in to comment.