xaviershay-twitter-auth 0.1.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/README.markdown +119 -0
  2. data/Rakefile +31 -0
  3. data/VERSION.yml +4 -0
  4. data/app/controllers/sessions_controller.rb +66 -0
  5. data/app/models/twitter_auth/basic_user.rb +63 -0
  6. data/app/models/twitter_auth/generic_user.rb +93 -0
  7. data/app/models/twitter_auth/oauth_user.rb +44 -0
  8. data/app/views/sessions/_login_form.html.erb +17 -0
  9. data/app/views/sessions/new.html.erb +5 -0
  10. data/config/routes.rb +6 -0
  11. data/generators/twitter_auth/USAGE +12 -0
  12. data/generators/twitter_auth/templates/migration.rb +48 -0
  13. data/generators/twitter_auth/templates/twitter_auth.yml +47 -0
  14. data/generators/twitter_auth/templates/user.rb +5 -0
  15. data/generators/twitter_auth/twitter_auth_generator.rb +34 -0
  16. data/lib/twitter_auth.rb +99 -0
  17. data/lib/twitter_auth/controller_extensions.rb +69 -0
  18. data/lib/twitter_auth/cryptify.rb +30 -0
  19. data/lib/twitter_auth/dispatcher/basic.rb +46 -0
  20. data/lib/twitter_auth/dispatcher/oauth.rb +26 -0
  21. data/lib/twitter_auth/dispatcher/shared.rb +40 -0
  22. data/rails/init.rb +8 -0
  23. data/spec/controllers/controller_extensions_spec.rb +162 -0
  24. data/spec/controllers/sessions_controller_spec.rb +250 -0
  25. data/spec/fixtures/config/twitter_auth.yml +17 -0
  26. data/spec/fixtures/factories.rb +18 -0
  27. data/spec/fixtures/fakeweb.rb +18 -0
  28. data/spec/fixtures/twitter.rb +5 -0
  29. data/spec/models/twitter_auth/basic_user_spec.rb +122 -0
  30. data/spec/models/twitter_auth/generic_user_spec.rb +142 -0
  31. data/spec/models/twitter_auth/oauth_user_spec.rb +101 -0
  32. data/spec/schema.rb +41 -0
  33. data/spec/spec.opts +1 -0
  34. data/spec/spec_helper.rb +53 -0
  35. data/spec/twitter_auth/cryptify_spec.rb +51 -0
  36. data/spec/twitter_auth/dispatcher/basic_spec.rb +83 -0
  37. data/spec/twitter_auth/dispatcher/oauth_spec.rb +72 -0
  38. data/spec/twitter_auth/dispatcher/shared_spec.rb +26 -0
  39. data/spec/twitter_auth_spec.rb +160 -0
  40. metadata +127 -0
@@ -0,0 +1,119 @@
1
+ TwitterAuth
2
+ ===========
3
+
4
+ TwitterAuth aims to provide a complete authentication and API access solution for creating Twitter applications in Rails. It provides a generator and all of the necessary components to use Twitter as the sole authentication provider for an application using either Twitter's OAuth or HTTP Basic authentication strategies.
5
+
6
+ Installation
7
+ ============
8
+
9
+ You can include TwitterAuth as a gem in your project like so:
10
+
11
+ config.gem 'twitter-auth', :lib => 'twitter_auth'
12
+
13
+ Or you can install it as a traditional Rails plugin:
14
+
15
+ script/plugin install git://github.com/mbleigh/twitter-auth.git
16
+
17
+ Note that because TwitterAuth utilizes Rails Engines functionality introduced in Rails 2.3, it will not work with earlier versions of Rails.
18
+
19
+ **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.
20
+
21
+ Usage
22
+ =====
23
+
24
+ To utilize TwitterAuth in your application you will need to run the generator:
25
+
26
+ script/generate twitter_auth [--oauth (default) | --basic]
27
+
28
+ 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.
29
+
30
+ 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).
31
+
32
+ Sign in with Twitter
33
+ --------------------
34
+
35
+ Twitter recently implemented a convenience layer on top of OAuth called [Sign in with Twitter](http://apiwiki.twitter.com/Sign-in-with-Twitter). TwitterAuth makes use of this by default in newly generated applications by setting the `authorize_path` in `twitter_auth.yml`.
36
+
37
+ If you already have an application utilizing TwitterAuth that you would like to utilize the new system, simply add this line to your `twitter_auth.yml` in each environment:
38
+
39
+ authorize_path: "/oauth/authenticate"
40
+
41
+ Usage Basics
42
+ ------------
43
+
44
+ If you need more information about how to use OAuth with Twitter, please visit Twitter's [OAuth FAQ](http://apiwiki.twitter.com/OAuth-FAQ).
45
+
46
+ 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:
47
+
48
+ * `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.
49
+ * `current_user`: returns the logged in user if one exists, otherwise returns `nil`.
50
+ * `logged_in?`: true if logged in, false otherwise.
51
+ * `redirect_back_or_default(url)`: redirects to the location where `store_location` was last called or the specified default URL.
52
+ * `store_location`: store the current URL for returning to when a `redirect_back_or_default` is called.
53
+ * `authorized?`: override this to add fine-grained access control for when `login_required` is already called.
54
+
55
+ Accessing the Twitter API
56
+ -------------------------
57
+
58
+ Obviously if you're using Twitter as an authentication strategy you probably have interest in accessing Twitter API information as well. Because I wasn't really satisfied with either of the popular Twitter API Ruby libraries ([Twitter4R](http://twitter4r.rubyforge.org) and [Twitter](http://twitter.rubyforge.org)) and also because neither support OAuth (yet), I decided to go with a simple, dependency-free API implementation.
59
+
60
+ The `User` class will have a `twitter` method that provides a generic dispatcher with HTTP verb commands available (`get`, `put`, `post`, and `delete`). These are automatically initialized to the `base_url` you specified in the `twitter_auth.yml` file, so you need only specify a path. Additionally, it will automatically append a .json extension and parse the JSON if you don't provide (it returns strings for XML because, well, I don't like XML and don't feel like parsing it).
61
+
62
+ # This code will work with the OAuth and Basic strategies alike.
63
+ user = User.find_by_login('mbleigh')
64
+
65
+ user.twitter.get('/account/verify_credentials')
66
+ # => {'screen_name' => 'mbleigh', 'name' => 'Michael Bleigh' ... }
67
+
68
+ user.twitter.post('/statuses/update.json', 'status' => 'This is my status.')
69
+ # => {"user"=>{"login" => "mbleigh" ... }, "text"=>"This is my status.", "id"=>1234567890 ... }
70
+
71
+ If Twitter returns something other than a 200 response code, TwitterAuth will catch it and try to raise a salient error message. The exception class is `TwitterAuth::Dispatcher::Error` if you're in the mood to catch it.
72
+
73
+ This area of the code is still a little raw, but hopefully will evolve to be a little more user-friendly as TwitterAuth matures. In the meantime, it's a perfectly workable foundation library, and the fact that it works the same with OAuth and HTTP Basic makes it all the better!
74
+
75
+ Customizing TwitterAuth
76
+ -----------------------
77
+
78
+ There are a number of hooks to extend the functionality of TwitterAuth. Here is a brief description of each of them.
79
+
80
+ ### Controller Methods
81
+
82
+ TwitterAuth provides some default controller methods that may be overridden in your `ApplicationController` to behave differently.
83
+
84
+ * `authentication_failed(message)`: called when Twitter authorization has failed during the process. By default, simply redirects to the site root and sets the `flash[:error]`.
85
+ * `authentication_succeeded(message=default)`: called when Twitter authorization has completed successfully. By default, simply redirects to the site root and sets the `flash[:notice]`.
86
+ * `access_denied`: what happens when the `login_required` before filter fails. By default it stores the current location to return to and redirects to the login process.
87
+ * `build_user(user)`: after a new user has been instantiated, but before it is saved, it is passed to this method so that you can set extra attributes
88
+
89
+ Tips and Tricks
90
+ ---------------
91
+
92
+ * If you are getting an `OpenSSL::SSL:SSLError (certificate verify failed)` you may want to [see this ticket and comments](https://mbleigh.lighthouseapp.com/projects/27783-twitterauth/tickets/6-error-on-login#ticket-6-2).
93
+
94
+ Development
95
+ -----------
96
+
97
+ ### Specs
98
+
99
+ To be able to run the test suite, twitter-auth needs to be hosted inside a project. Create a new blank rails app, and add the following lines to config/environments/test.rb:
100
+
101
+ config.gem "rspec", :lib => false, :version => ">= 1.2.0"
102
+ config.gem "rspec-rails", :lib => false, :version => ">= 1.2.0"
103
+ config.gem "remarkable_rails", :lib => false
104
+
105
+ Install twitter-auth as a plugin to vendor/plugins, then cd to vendor/plugins/twitter-auth and run rake.
106
+
107
+ Resources
108
+ ---------
109
+
110
+ * **Bug Reports:** See the [Issues Page](http://github.com/mbleigh/twitter-auth/issues) to report any problems you have using TwitterAuth.
111
+ * **Blog Post:** The [original blog post about TwitterAuth](http://intridea.com/2009/3/23/twitter-auth-for-near-instant-twitter-apps) has a tutorial as well to get you started.
112
+ * **GitHub Pages:** TwitterAuth has a [simple GitHub page](http://mbleigh.com/twitter-auth)
113
+
114
+ Copyright
115
+ ---------
116
+
117
+ **TwitterAuth** is Copyright (c) 2009 [Michael Bleigh](http://www.mbleigh.com) and [Intridea, Inc.](http://www.intridea.com/), released under the MIT License.
118
+
119
+ TwitterAuth is not affiliated with Twitter, Inc.
@@ -0,0 +1,31 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+
4
+ desc 'Default: run specs.'
5
+ task :default => :spec
6
+
7
+ desc 'Run the specs'
8
+ Spec::Rake::SpecTask.new(:spec) do |t|
9
+ t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
10
+ t.spec_files = FileList['spec/**/*_spec.rb']
11
+ end
12
+
13
+ begin
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |s|
16
+ s.name = "twitter-auth"
17
+ s.summary = "TwitterAuth is a Rails plugin gem that provides Single Sign-On capabilities for Rails applications via Twitter."
18
+ s.email = "michael@intridea.com"
19
+ s.homepage = "http://github.com/mbleigh/twitter-auth"
20
+ s.description = "TwitterAuth is a Rails plugin gem that provides Single Sign-On capabilities for Rails applications via Twitter. Both OAuth and HTTP Basic are supported."
21
+ s.files = FileList["[A-Z]*", "{bin,generators,lib,spec,config,app,rails}/**/*"] - FileList["**/*.log"]
22
+
23
+ s.authors = ["Michael Bleigh"]
24
+ s.add_dependency('oauth', '>= 0.3.1')
25
+ s.add_dependency('ezcrypto', '>= 0.7.2')
26
+ s.rubyforge_project = 'twitter-auth'
27
+ end
28
+ rescue LoadError
29
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
30
+ end
31
+
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 18
@@ -0,0 +1,66 @@
1
+ class SessionsController < ApplicationController
2
+ def new
3
+ if TwitterAuth.oauth?
4
+ @request_token = TwitterAuth.consumer.get_request_token
5
+ session[:request_token] = @request_token.token
6
+ session[:request_token_secret] = @request_token.secret
7
+
8
+ url = @request_token.authorize_url
9
+ url << "&oauth_callback=#{CGI.escape(TwitterAuth.oauth_callback)}" if TwitterAuth.oauth_callback?
10
+ redirect_to url
11
+ else
12
+ # we don't have to do anything, it's just a simple form for HTTP basic!
13
+ end
14
+ end
15
+
16
+ def create
17
+ logout_keeping_session!
18
+ if user = User.authenticate(params[:login], params[:password])
19
+ self.current_user = user
20
+ authentication_succeeded and return
21
+ else
22
+ authentication_failed('Unable to verify your credentials through Twitter. Please try again.', '/login') and return
23
+ end
24
+ end
25
+
26
+ def oauth_callback
27
+ unless session[:request_token] && session[:request_token_secret]
28
+ authentication_failed('No authentication information was found in the session. Please try again.') and return
29
+ end
30
+
31
+ unless params[:oauth_token].blank? || session[:request_token] == params[:oauth_token]
32
+ authentication_failed('Authentication information does not match session information. Please try again.') and return
33
+ end
34
+
35
+ @request_token = OAuth::RequestToken.new(TwitterAuth.consumer, session[:request_token], session[:request_token_secret])
36
+
37
+ @access_token = @request_token.get_access_token
38
+
39
+ # The request token has been invalidated
40
+ # so we nullify it in the session.
41
+ session[:request_token] = nil
42
+ session[:request_token_secret] = nil
43
+
44
+ @user = User.identify_or_create_from_access_token(@access_token) do |user|
45
+ build_user(user)
46
+ end
47
+
48
+ session[:user_id] = @user.id
49
+
50
+ cookies[:remember_token] = @user.remember_me
51
+
52
+ authentication_succeeded
53
+ rescue Net::HTTPServerException, Net::HTTPFatalError, TwitterAuth::Dispatcher::Error => e
54
+ case e.message
55
+ when '401 "Unauthorized"'
56
+ authentication_failed('This authentication request is no longer valid. Please try again.') and return
57
+ else
58
+ authentication_failed('There was a problem trying to authenticate you. Please try again.') and return
59
+ end
60
+ end
61
+
62
+ def destroy
63
+ logout_keeping_session!
64
+ redirect_back_or_default('/')
65
+ end
66
+ end
@@ -0,0 +1,63 @@
1
+ require 'net/http'
2
+
3
+ module TwitterAuth
4
+ module BasicUser
5
+ def self.included(base)
6
+ base.class_eval do
7
+ attr_protected :crypted_password, :salt
8
+ end
9
+
10
+ base.extend TwitterAuth::BasicUser::ClassMethods
11
+ end
12
+
13
+ module ClassMethods
14
+ def verify_credentials(login, password)
15
+ response = TwitterAuth.net.start { |http|
16
+ request = Net::HTTP::Get.new('/account/verify_credentials.json')
17
+ request.basic_auth login, password
18
+ http.request(request)
19
+ }
20
+
21
+ if response.code == '200'
22
+ JSON.parse(response.body)
23
+ else
24
+ false
25
+ end
26
+ end
27
+
28
+ def authenticate(login, password)
29
+ if twitter_hash = verify_credentials(login, password)
30
+ user = identify_or_create_from_twitter_hash_and_password(twitter_hash, password)
31
+ user
32
+ else
33
+ nil
34
+ end
35
+ end
36
+
37
+ def identify_or_create_from_twitter_hash_and_password(twitter_hash, password)
38
+ if user = User.find_by_login(twitter_hash['screen_name'])
39
+ user.assign_twitter_attributes(twitter_hash)
40
+ user.password = password
41
+ user.save
42
+ user
43
+ else
44
+ user = User.new_from_twitter_hash(twitter_hash)
45
+ user.password = password
46
+ user.save
47
+ user
48
+ end
49
+ end
50
+ end
51
+
52
+ def password=(new_password)
53
+ encrypted = TwitterAuth::Cryptify.encrypt(new_password)
54
+ self.crypted_password = encrypted[:encrypted_data]
55
+ self.salt = encrypted[:salt]
56
+ end
57
+
58
+ def password
59
+ TwitterAuth::Cryptify.decrypt(self.crypted_password, self.salt)
60
+ end
61
+ end
62
+ end
63
+
@@ -0,0 +1,93 @@
1
+ module TwitterAuth
2
+ class GenericUser < ActiveRecord::Base
3
+ attr_protected :login, :remember_token, :remember_token_expires_at
4
+
5
+ TWITTER_ATTRIBUTES = [
6
+ :name,
7
+ :location,
8
+ :description,
9
+ :profile_image_url,
10
+ :url,
11
+ :protected,
12
+ :profile_background_color,
13
+ :profile_sidebar_fill_color,
14
+ :profile_link_color,
15
+ :profile_sidebar_border_color,
16
+ :profile_text_color,
17
+ :profile_background_image_url,
18
+ :profile_background_tile,
19
+ :friends_count,
20
+ :statuses_count,
21
+ :followers_count,
22
+ :favourites_count,
23
+ :time_zone,
24
+ :utc_offset
25
+ ]
26
+
27
+ validates_presence_of :login
28
+ validates_format_of :login, :with => /\A[a-z0-9_]+\z/i
29
+ validates_length_of :login, :in => 1..15
30
+ validates_uniqueness_of :login, :case_sensitive => false
31
+ validates_uniqueness_of :remember_token, :allow_blank => true
32
+
33
+ def self.table_name; 'users' end
34
+
35
+ def self.new_from_twitter_hash(hash)
36
+ raise ArgumentError, 'Invalid hash: must include screen_name.' unless hash.key?('screen_name')
37
+
38
+ user = User.new
39
+ user.login = hash['screen_name']
40
+
41
+ TWITTER_ATTRIBUTES.each do |att|
42
+ user.send("#{att}=", hash[att.to_s]) if user.respond_to?("#{att}=")
43
+ end
44
+
45
+ user
46
+ end
47
+
48
+ def self.from_remember_token(token)
49
+ first(:conditions => ["remember_token = ? AND remember_token_expires_at > ?", token, Time.now])
50
+ end
51
+
52
+ def assign_twitter_attributes(hash)
53
+ TWITTER_ATTRIBUTES.each do |att|
54
+ send("#{att}=", hash[att.to_s]) if respond_to?("#{att}=")
55
+ end
56
+ end
57
+
58
+ def update_twitter_attributes(hash)
59
+ assign_twitter_attributes(hash)
60
+ save
61
+ end
62
+
63
+ if TwitterAuth.oauth?
64
+ include TwitterAuth::OauthUser
65
+ else
66
+ include TwitterAuth::BasicUser
67
+ end
68
+
69
+ def twitter
70
+ if TwitterAuth.oauth?
71
+ TwitterAuth::Dispatcher::Oauth.new(self)
72
+ else
73
+ TwitterAuth::Dispatcher::Basic.new(self)
74
+ end
75
+ end
76
+
77
+ def remember_me
78
+ return false unless respond_to?(:remember_token)
79
+
80
+ self.remember_token = ActiveSupport::SecureRandom.hex(10)
81
+ self.remember_token_expires_at = Time.now + TwitterAuth.remember_for.days
82
+
83
+ save
84
+
85
+ {:value => self.remember_token, :expires => self.remember_token_expires_at}
86
+ end
87
+
88
+ def forget_me
89
+ self.remember_token = self.remember_token_expires_at = nil
90
+ self.save
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,44 @@
1
+ module TwitterAuth
2
+ module OauthUser
3
+ def self.included(base)
4
+ base.class_eval do
5
+ attr_protected :access_token, :access_secret
6
+ end
7
+
8
+ base.extend TwitterAuth::OauthUser::ClassMethods
9
+ base.extend TwitterAuth::Dispatcher::Shared
10
+ end
11
+
12
+ module ClassMethods
13
+ def identify_or_create_from_access_token(token, secret=nil, &block)
14
+ raise ArgumentError, 'Must authenticate with an OAuth::AccessToken or the string access token and secret.' unless (token && secret) || token.is_a?(OAuth::AccessToken)
15
+
16
+ response = token.get(TwitterAuth.path_prefix + '/account/verify_credentials.json')
17
+ user_info = handle_response(response)
18
+
19
+ if user = User.find_by_login(user_info['screen_name'])
20
+ user.assign_twitter_attributes(user_info)
21
+ user.access_token = token.token
22
+ user.access_secret = token.secret
23
+ user.save
24
+ user
25
+ else
26
+ User.create_from_twitter_hash_and_token(user_info, token, &block)
27
+ end
28
+ end
29
+
30
+ def create_from_twitter_hash_and_token(user_info, access_token)
31
+ user = User.new_from_twitter_hash(user_info)
32
+ user.access_token = access_token.token
33
+ user.access_secret = access_token.secret
34
+ yield user if block_given?
35
+ user.save
36
+ user
37
+ end
38
+ end
39
+
40
+ def token
41
+ OAuth::AccessToken.new(TwitterAuth.consumer, access_token, access_secret)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,17 @@
1
+ <% form_tag session_path, :id => 'login_form' do %>
2
+ <div class='field'>
3
+ <label for='login'>Twitter Username:</label>
4
+ <%= text_field_tag 'login', nil, :class => 'text_field' %>
5
+ </div>
6
+ <div class='field'>
7
+ <label for='password'>Password:</label>
8
+ <%= password_field_tag 'password', nil, :class => 'password_field' %>
9
+ </div>
10
+ <!--<div class='checkbox-field'>
11
+ <%= check_box_tag 'remember_me' %> <label for='remember_me'>Keep Me Logged In</label>
12
+ </div>-->
13
+ <div class='field submit'>
14
+ <%= submit_tag 'Log In', :class => 'submit' %>
15
+ </div>
16
+ <% end %>
17
+
@@ -0,0 +1,5 @@
1
+ <h1>Log In Via Twitter</h1>
2
+
3
+ <p>This application utilizes your Twitter username and password for authentication; you do not have to create a separate account here. To log in, just enter your Twitter credentials in the form below.</p>
4
+
5
+ <%= render :partial => 'login_form' %>