mbleigh-twitter-auth 0.0.2 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,52 +1,83 @@
1
1
  TwitterAuth
2
2
  ===========
3
3
 
4
- TwitterAuth is a plugin to provide a standard authentication stack using Twitter
5
- as an SSO provider. This is obviously most useful and therefore targeted at
6
- apps that intend to heavily use the Twitter API.
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.
7
5
 
8
- **Note:** TwitterAuth uses Rails Engines functionality from Rails 2.3 and is
9
- therefore incompatible with earlier versions of Rails.
6
+ Installation
7
+ ============
10
8
 
11
- Getting Started
12
- ---------------
9
+ You can include TwitterAuth as a gem in your project like so:
13
10
 
14
- First, install either by gem or by plugin:
11
+ config.gem 'mbleigh-twitter-auth', :source => 'http://gems.github.com'
12
+
13
+ Or you can install it as a traditional Rails plugin:
15
14
 
16
- config.gem 'mbleigh-twitter-auth', :source => 'http://gems.github.com/'
17
-
18
- OR
19
-
20
15
  script/plugin install git://github.com/mbleigh/twitter-auth.git
21
16
 
22
- Next, to get started, you will need to generate the migration for the User
23
- model that TwitterAuth uses. It's simple:
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
+ Usage Basics
33
+ ------------
34
+
35
+ If you need more information about how to use OAuth with Twitter, please visit Twitter's [OAuth FAQ](http://apiwiki.twitter.com/OAuth-FAQ).
36
+
37
+ 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:
38
+
39
+ * `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.
40
+ * `current_user`: returns the logged in user if one exists, otherwise returns `nil`.
41
+ * `logged_in?`: true if logged in, false otherwise.
42
+ * `redirect_back_or_default(url)`: redirects to the location where `store_location` was last called or the specified default URL.
43
+ * `store_location`: store the current URL for returning to when a `redirect_back_or_default` is called.
44
+ * `authorized?`: override this to add fine-grained access control for when `login_required` is already called.
45
+
46
+ Accessing the Twitter API
47
+ -------------------------
48
+
49
+ 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.
50
+
51
+ 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).
52
+
53
+ # This code will work with the OAuth and Basic strategies alike.
54
+ user = User.find_by_login('mbleigh')
55
+
56
+ user.twitter.get('/account/verify_credentials')
57
+ # => {'screen_name' => 'mbleigh', 'name' => 'Michael Bleigh' ... }
58
+
59
+ user.twitter.post('/statuses/update.json', 'status' => 'This is my status.')
60
+ # => {"user"=>{"login" => "mbleigh" ... }, "text"=>"This is my status.", "id"=>1234567890 ... }
61
+
62
+ 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!
63
+
64
+ Customizing TwitterAuth
65
+ -----------------------
24
66
 
25
- script/generate migration twitter_auth_migration
26
-
27
- If you look in the migration you will see that there are some information
28
- fields pre-populated (name, location, etc). These will automatically be
29
- retrieved from Twitter at each login and therefor kept both accessible
30
- and fresh for your usage.
67
+ There are a number of hooks to extend the functionality of TwitterAuth. Here is a brief description of each of them.
31
68
 
32
- Believe it or not, that's it! You now have access to the standard suite
33
- of restful-auth controller helpers such as:
69
+ ### Controller Methods
34
70
 
35
- * login_required
36
- * current_user
37
- * logged_in?
71
+ TwitterAuth provides some default controller methods that may be overridden in your `ApplicationController` to behave differently.
38
72
 
39
- And you also have the ability to login through the built-in SessionController.
40
- Just run the app and point your browser to '/login' to get started! You don't
41
- need to sign up because it will automatically create new users if the logging
42
- in user has never logged in before.
73
+ * `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]`.
74
+ * `authentication_succeeded(message=default)`: called when Twitter authorization has completed successfully. By default, simply redirects to the site root and sets the `flash[:notice]`.
75
+ * `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.
43
76
 
44
- Caveats
45
- -------
46
77
 
47
- This is **extremely alpha** code and has not been thoroughly spec'ed or even
48
- inspected. Use at your own risk as the functionality is likely to change
49
- drastically even in the near future.
78
+ Copyright
79
+ ---------
50
80
 
81
+ **TwitterAuth** is Copyright (c) 2009 [Michael Bleigh](http://www.mbleigh.com) and [Intridea, Inc.](http://www.intridea.com/), released under the MIT License.
51
82
 
52
- Copyright (c) 2009 [Michael Bleigh](http://www.mbleigh.com) and [Intridea, Inc.](http://www.intridea.com/), released under the MIT license
83
+ TwitterAuth is not affiliated with Twitter, Inc.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
+ :minor: 1
3
+ :patch: 1
2
4
  :major: 0
3
- :minor: 0
4
- :patch: 2
@@ -0,0 +1,12 @@
1
+ TwitterAuth Generator
2
+ =====================
3
+
4
+ The TwitterAuth generator allows you to generate the components necessary to implement Twitter as a Single Sign-On provider for your site.
5
+
6
+ To run it, you simply need to call it:
7
+
8
+ script/generate twitter_auth
9
+
10
+ This will generate the migration necessary for the users table as well as generate a User model that extends the appropriate TwitterAuth model template and a config/twitter.yml that allows you to set your OAuth consumer key and secret.
11
+
12
+ By default, TwitterAuth uses OAuth as its authentication strategy. If you wish to use HTTP Basic you can pass in the --basic option.
@@ -1,33 +1,42 @@
1
1
  class TwitterAuthMigration < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :users do |t|
4
- t.string :login
5
- t.string :crypted_password
6
- t.string :salt
7
-
8
- # Basic info pulled automatically from Twitter.
9
- # Feel free to remove any of these columns you don't want.
10
- t.string :name
11
- t.string :location
12
- t.text :description
13
- t.string :profile_image_url
14
- t.string :url
4
+ t.string :login
5
+ <% if options[:oauth] -%>
6
+ t.string :access_token
7
+ t.string :access_secret
8
+ <% elsif options[:basic] -%>
9
+ t.binary :crypted_password
10
+ t.string :salt
11
+ <% end -%>
12
+
13
+ # This information is automatically kept
14
+ # in-sync at each login of the user. You
15
+ # may remove any/all of these columns.
16
+ t.string :name
17
+ t.string :location
18
+ t.string :description
19
+ t.string :profile_image_url
20
+ t.string :url
15
21
  t.boolean :protected
16
- t.string :profile_background_color
17
- t.string :profile_sidebar_fill_color
18
- t.string :profile_link_color
19
- t.string :profile_sidebar_border_color
20
- t.string :profile_text_color
22
+ t.string :profile_background_color
23
+ t.string :profile_sidebar_fill_color
24
+ t.string :profile_link_color
25
+ t.string :profile_sidebar_border_color
26
+ t.string :profile_text_color
21
27
  t.integer :friends_count
22
28
  t.integer :statuses_count
23
- t.integer :followers_count
24
- t.integer :favourites_count
29
+ t.integer :followers_count
30
+ t.integer :favourites_count
31
+
32
+ # Probably don't need both, but they're here.
25
33
  t.integer :utc_offset
26
-
34
+ t.string :time_zone
35
+
27
36
  t.timestamps
28
37
  end
29
38
  end
30
-
39
+
31
40
  def self.down
32
41
  drop_table :users
33
42
  end
@@ -0,0 +1,38 @@
1
+ <% if options[:oauth] -%>
2
+ development:
3
+ strategy: oauth
4
+ base_url: "https://twitter.com"
5
+ api_timeout: 10
6
+ oauth_consumer_key: devkey
7
+ oauth_consumer_secret: devsecret
8
+ oauth_callback: "http://localhost:3000/oauth_callback"
9
+ test:
10
+ strategy: oauth
11
+ base_url: "https://twitter.com"
12
+ api_timeout: 10
13
+ oauth_consumer_key: testkey
14
+ oauth_consumer_secret: testsecret
15
+ oauth_callback: "http://localhost:3000/oauth_callback"
16
+ production:
17
+ strategy: oauth
18
+ api_timeout: 10
19
+ base_url: "https://twitter.com"
20
+ oauth_consumer_key: prodkey
21
+ oauth_consumer_secret: prodsecret
22
+ <% else -%>
23
+ development:
24
+ strategy: basic
25
+ api_timeout: 10
26
+ base_url: "https://twitter.com"
27
+ # randomly generated key for encrypting Twitter passwords
28
+ encryption_key: "<%= key = ActiveSupport::SecureRandom.hex(12) %>"
29
+ test:
30
+ strategy: basic
31
+ api_timeout: 10
32
+ base_url: "https://twitter.com"
33
+ encryption_key: "<%= key %>"
34
+ production:
35
+ strategy: basic
36
+ api_timeout: 10
37
+ encryption_key: "<%= key %>"
38
+ <% end %>
@@ -1,7 +1,5 @@
1
1
  class User < TwitterAuth::GenericUser
2
- # Because Rails 2.3 does not allow for simple
3
- # overriding of an Engine class from a samely
4
- # named model in the app, this is instead
5
- # inheriting from a GenericUser class that
6
- # already utilizes the 'users' table.
7
- end
2
+ # Extend and define your user model as you see fit.
3
+ # All of the authentication logic is handled by the
4
+ # parent TwitterAuth::GenericUser class.
5
+ end
@@ -1,10 +1,34 @@
1
- class TwitterAuthGenerator < Rails::Generator::Base
2
- def manifest
3
- record do |m|
1
+ class TwitterAuthGenerator < Rails::Generator::Base
2
+ default_options :oauth => true, :basic => false
3
+
4
+ def manifest
5
+ record do |m|
4
6
  m.class_collisions 'User'
5
-
6
- m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => "twitter_auth_migration"
7
- m.template 'user.rb', File.join('app/models', 'user.rb')
7
+
8
+ m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'twitter_auth_migration'
9
+ m.template 'user.rb', File.join('app','models','user.rb')
10
+ m.template 'twitter_auth.yml', File.join('config','twitter_auth.yml')
8
11
  end
9
12
  end
10
- end
13
+
14
+ protected
15
+
16
+ def banner
17
+ "Usage: #{$0} twitter_auth"
18
+ end
19
+
20
+ def add_options!(opt)
21
+ opt.separator ''
22
+ opt.separator 'Options:'
23
+
24
+ opt.on('-O', '--oauth', 'Use the OAuth authentication strategy to connect to Twitter. (default)') { |v|
25
+ options[:oauth] = v
26
+ options[:basic] = !v
27
+ }
28
+
29
+ opt.on('-B', '--basic', 'Use the HTTP Basic authentication strategy to connect to Twitter.') { |v|
30
+ options[:basic] = v
31
+ options[:oauth] = !v
32
+ }
33
+ end
34
+ end
data/lib/twitter_auth.rb CHANGED
@@ -1,5 +1,69 @@
1
1
  module TwitterAuth
2
2
  class Error < StandardError; end
3
+
4
+ def self.config(environment=RAILS_ENV)
5
+ @config ||= {}
6
+ @config[environment] ||= YAML.load(File.open(RAILS_ROOT + '/config/twitter_auth.yml').read)[environment]
7
+ end
8
+
9
+ def self.base_url
10
+ config['base_url'] || 'https://twitter.com'
11
+ end
12
+
13
+ def self.api_timeout
14
+ config['api_timeout'] || 10
15
+ end
16
+
17
+ def self.encryption_key
18
+ raise TwitterAuth::Cryptify::Error, 'You must specify an encryption_key in config/twitter_auth.yml' if config['encryption_key'].blank?
19
+ config['encryption_key']
20
+ end
21
+
22
+ def self.oauth_callback?
23
+ config.key?('oauth_callback')
24
+ end
25
+
26
+ def self.oauth_callback
27
+ config['oauth_callback']
28
+ end
29
+
30
+ # The authentication strategy employed by this
31
+ # application. Set in +config/twitter.yml+ as
32
+ # strategy; valid options are oauth or basic.
33
+ def self.strategy
34
+ strat = config['strategy']
35
+ raise ArgumentError, 'Invalid TwitterAuth Strategy: Valid strategies are oauth and basic.' unless %w(oauth basic).include?(strat)
36
+ strat.to_sym
37
+ rescue Errno::ENOENT
38
+ :oauth
39
+ end
40
+
41
+ def self.oauth?
42
+ strategy == :oauth
43
+ end
44
+
45
+ def self.basic?
46
+ strategy == :basic
47
+ end
48
+
49
+ # The OAuth consumer used by TwitterAuth for authentication. The consumer key and secret are set in your application's +config/twitter.yml+
50
+ def self.consumer
51
+ OAuth::Consumer.new(
52
+ config['oauth_consumer_key'],
53
+ config['oauth_consumer_secret'],
54
+ :site => TwitterAuth.base_url
55
+ )
56
+ end
57
+
58
+ def self.net
59
+ uri = URI.parse(TwitterAuth.base_url)
60
+ net = Net::HTTP.new(uri.host, uri.port)
61
+ net.use_ssl = uri.scheme == 'https'
62
+ net.read_timeout = TwitterAuth.api_timeout
63
+ net
64
+ end
3
65
  end
4
66
 
5
- require 'twitter_auth/controller_extensions'
67
+ require 'twitter_auth/controller_extensions'
68
+ require 'twitter_auth/cryptify'
69
+ require 'twitter_auth/dispatcher/oauth'
@@ -1,200 +1,64 @@
1
1
  module TwitterAuth
2
- # The extensions to ActionController to enable our
3
- # usage of Twitter-based authentication. This is
4
- # taken directly from restful-authentication, so
5
- # no credit is due here.
2
+ # These methods borrow HEAVILY from Rick Olsen's
3
+ # Restful Authentication. All cleverness props
4
+ # go to him, not me.
6
5
  module ControllerExtensions
7
- # Returns true or false if the user is logged in.
8
- # Preloads @current_user with the user model if they're logged in.
9
- def logged_in?
10
- !!current_user
6
+ def self.included(base)
7
+ base.send :helper_method, :current_user, :logged_in?, :authorized?
8
+ end
9
+
10
+ protected
11
+
12
+ def authentication_failed(message, destination='/')
13
+ flash[:error] = message
14
+ redirect_to destination
15
+ end
16
+
17
+ def authentication_succeeded(message = 'You have logged in successfully.', destination = '/')
18
+ flash[:notice] = message
19
+ redirect_to destination
11
20
  end
12
21
 
13
- # Accesses the current user from the session.
14
- # Future calls avoid the database because nil is not equal to false.
15
22
  def current_user
16
- @current_user ||= (login_from_session || login_from_basic_auth) unless @current_user == false
23
+ @current_user ||= User.find_by_id(session[:user_id])
17
24
  end
18
25
 
19
- # Store the given user id in the session.
20
26
  def current_user=(new_user)
21
- session[:user_id] = new_user ? new_user.id : nil
22
- @current_user = new_user || false
27
+ session[:user_id] = new_user.id
28
+ @current_user = new_user
23
29
  end
24
30
 
25
- # Check if the user is authorized
26
- #
27
- # Override this method in your controllers if you want to restrict access
28
- # to only a few actions or if you want to check if the user
29
- # has the correct rights.
30
- #
31
- # Example:
32
- #
33
- # # only allow nonbobs
34
- # def authorized?
35
- # current_user.login != "bob"
36
- # end
37
- #
38
- def authorized?(action=nil, resource=nil, *args)
39
- logged_in?
31
+ def authorized?
32
+ !!current_user
40
33
  end
41
34
 
42
- # Filter method to enforce a login requirement.
43
- #
44
- # To require logins for all actions, use this in your controllers:
45
- #
46
- # before_filter :login_required
47
- #
48
- # To require logins for specific actions, use this in your controllers:
49
- #
50
- # before_filter :login_required, :only => [ :edit, :update ]
51
- #
52
- # To skip this in a subclassed controller:
53
- #
54
- # skip_before_filter :login_required
55
- #
56
35
  def login_required
57
- authorized? || access_denied
36
+ authorized? || access_denied
58
37
  end
59
38
 
60
- # Redirect as appropriate when an access request fails.
61
- #
62
- # The default action is to redirect to the login screen.
63
- #
64
- # Override this method in your controllers if you want to have special
65
- # behavior in case the user is not authorized
66
- # to access the requested action. For example, a popup window might
67
- # simply close itself.
68
39
  def access_denied
69
- respond_to do |format|
70
- format.html do
71
- store_location
72
- redirect_to new_session_path
73
- end
74
- # format.any doesn't work in rails version < http://dev.rubyonrails.org/changeset/8987
75
- # you may want to change format.any to e.g. format.any(:js, :xml)
76
- format.any(:xml, :json) do
77
- request_http_basic_authentication 'Web Password'
78
- end
79
- end
40
+ store_location
41
+ redirect_to login_path
80
42
  end
81
43
 
82
- # Store the URI of the current request in the session.
83
- #
84
- # We can return to this location by calling #redirect_back_or_default.
85
44
  def store_location
86
45
  session[:return_to] = request.request_uri
87
46
  end
88
47
 
89
- # Redirect to the URI stored by the most recent store_location call or
90
- # to the passed default. Set an appropriately modified
91
- # after_filter :store_location, :only => [:index, :new, :show, :edit]
92
- # for any controller you want to be bounce-backable.
93
- def redirect_back_or_default(default=nil)
94
- redirect_to(session[:return_to] || default || self.default_location)
48
+ def redirect_back_or_default(default)
49
+ redirect_to(session[:return_to] || default)
95
50
  session[:return_to] = nil
96
51
  end
97
-
98
- # Override this method with what you want your 'default default'
99
- # to be (where it will redirect if there's no back and no explicit
100
- # default).
101
- def default_location
102
- '/'
103
- end
104
52
 
105
- # Inclusion hook to make #current_user and #logged_in?
106
- # available as ActionView helper methods.
107
- def self.included(base)
108
- base.send :helper_method, :current_user, :logged_in?, :authorized? if base.respond_to? :helper_method
109
- end
110
-
111
- #
112
- # Login
113
- #
114
-
115
- # Called from #current_user. First attempt to login by the user id stored in the session.
116
- def login_from_session
117
- self.current_user = User.find_by_id(session[:user_id]) if session[:user_id]
118
- end
119
-
120
- # Called from #current_user. Now, attempt to login by basic authentication information.
121
- def login_from_basic_auth
122
- authenticate_with_http_basic do |login, password|
123
- self.current_user = User.authenticate(login, password)
124
- end
53
+ def logged_in?
54
+ !!current_user
125
55
  end
126
-
127
- #
128
- # Logout
129
- #
130
56
 
131
- # Called from #current_user. Finaly, attempt to login by an expiring token in the cookie.
132
- # for the paranoid: we _should_ be storing user_token = hash(cookie_token, request IP)
133
- # def login_from_cookie
134
- # user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
135
- # if user && user.remember_token?
136
- # self.current_user = user
137
- # handle_remember_cookie! false # freshen cookie token (keeping date)
138
- # self.current_user
139
- # end
140
- # end
141
-
142
- # This is ususally what you want; resetting the session willy-nilly wreaks
143
- # havoc with forgery protection, and is only strictly necessary on login.
144
- # However, **all session state variables should be unset here**.
145
57
  def logout_keeping_session!
146
- # Kill server-side auth cookie
147
- @current_user.forget_me if @current_user.is_a? User
148
- @current_user = false # not logged in, and don't do it for me
149
- # kill_remember_cookie! # Kill client-side auth cookie
150
- session[:user_id] = nil # keeps the session but kill our variable
151
- # explicitly kill any other session variables you set
152
- end
153
-
154
- # The session should only be reset at the tail end of a form POST --
155
- # otherwise the request forgery protection fails. It's only really necessary
156
- # when you cross quarantine (logged-out to logged-in).
157
- def logout_killing_session!
158
- logout_keeping_session!
159
- reset_session
58
+ @current_user = nil
59
+ session[:user_id] = nil
160
60
  end
161
-
162
- #
163
- # Remember_me Tokens
164
- #
165
- # Cookies shouldn't be allowed to persist past their freshness date,
166
- # and they should be changed at each login
167
-
168
- # Cookies shouldn't be allowed to persist past their freshness date,
169
- # and they should be changed at each login
170
-
171
- # def valid_remember_cookie?
172
- # return nil unless @current_user
173
- # (@current_user.remember_token?) &&
174
- # (cookies[:auth_token] == @current_user.remember_token)
175
- # end
176
-
177
- # Refresh the cookie auth token if it exists, create it otherwise
178
- # def handle_remember_cookie! new_cookie_flag
179
- # return unless @current_user
180
- # case
181
- # when valid_remember_cookie? then @current_user.refresh_token # keeping same expiry date
182
- # when new_cookie_flag then @current_user.remember_me
183
- # else @current_user.forget_me
184
- # end
185
- # send_remember_cookie!
186
- # end
187
- #
188
- # def kill_remember_cookie!
189
- # cookies.delete :auth_token
190
- # end
191
- #
192
- # def send_remember_cookie!
193
- # cookies[:auth_token] = {
194
- # :value => @current_user.remember_token,
195
- # :expires => @current_user.remember_token_expires_at }
196
- # end
197
61
  end
198
62
  end
199
63
 
200
- ActionController::Base.send :include, TwitterAuth::ControllerExtensions
64
+ ActionController::Base.send(:include, TwitterAuth::ControllerExtensions)