Permalink
Browse files

made a lot of changes to the structure of the code, and added quite a…

… bit of shoulda tests. still figuring out a good workflow for testing controllers. made a solid distinction between oauth and openid, its all in the readme. version 0.0.36
  • Loading branch information...
1 parent 8522597 commit 9bab11dcf7f643ff105ac402d8914e5bd9d3bb20 @lancejpollard committed May 27, 2010
Showing with 1,410 additions and 555 deletions.
  1. +156 −43 README.markdown
  2. +1 −1 Rakefile
  3. +2 −71 lib/authlogic-connect.rb
  4. +46 −0 lib/authlogic_connect/authlogic_connect.rb
  5. +1 −1 lib/authlogic_connect/callback_filter.rb
  6. +1 −1 lib/authlogic_connect/common.rb
  7. +16 −0 lib/authlogic_connect/common/state.rb
  8. +102 −34 lib/authlogic_connect/common/user.rb
  9. +68 −16 lib/authlogic_connect/common/variables.rb
  10. +0 −1 lib/authlogic_connect/engine.rb
  11. +1 −0 lib/authlogic_connect/{common → }/ext.rb
  12. +3 −1 lib/authlogic_connect/oauth.rb
  13. +17 −13 lib/authlogic_connect/oauth/helper.rb
  14. +61 −76 lib/authlogic_connect/oauth/process.rb
  15. +3 −14 lib/authlogic_connect/oauth/session.rb
  16. +54 −0 lib/authlogic_connect/oauth/state.rb
  17. +9 −1 lib/authlogic_connect/oauth/tokens/google_token.rb
  18. +67 −2 lib/authlogic_connect/oauth/tokens/oauth_token.rb
  19. +2 −0 lib/authlogic_connect/oauth/tokens/twitter_token.rb
  20. +57 −74 lib/authlogic_connect/oauth/user.rb
  21. +52 −27 lib/authlogic_connect/oauth/variables.rb
  22. +3 −0 lib/authlogic_connect/openid.rb
  23. +30 −0 lib/authlogic_connect/openid/process.rb
  24. +6 −53 lib/authlogic_connect/openid/session.rb
  25. +47 −0 lib/authlogic_connect/openid/state.rb
  26. +3 −0 lib/authlogic_connect/openid/tokens/my_openid_token.rb
  27. +6 −0 lib/authlogic_connect/openid/tokens/openid_token.rb
  28. +38 −68 lib/authlogic_connect/openid/user.rb
  29. +17 −3 lib/authlogic_connect/openid/variables.rb
  30. +0 −1 lib/authlogic_connect/token.rb
  31. +0 −1 lib/open_id_authentication.rb
  32. +15 −9 pkg/authlogic-connect.gemspec
  33. +1 −1 rails/init.rb
  34. +21 −0 test/controllers/test_users_controller.rb
  35. +48 −0 test/libs/database.rb
  36. +3 −0 test/libs/user.rb
  37. +2 −0 test/libs/user_session.rb
  38. +53 −0 test/old.rb
  39. +1 −1 test/test_authlogic_connect.rb
  40. +142 −42 test/test_helper.rb
  41. +255 −0 test/test_user.rb
View
@@ -10,31 +10,6 @@ There are 3 ways you can allow your users to login with Authlogic Connect:
All of that is easier than creating a new account and password.
-## Helpful links
-
-* <b>Authlogic:</b> [http://github.com/binarylogic/authlogic](http://github.com/binarylogic/authlogic)
-* <b>Authlogic Connect Example Project:</b> [http://github.com/viatropos/authlogic-connect-example](http://github.com/viatropos/authlogic-connect-example)
-* <b>Live example with Twitter and Facebook using Rails 3:</b> [http://authlogic-connect.heroku.com](http://authlogic-connect.heroku.com)
-* <b>Rails 2.3.5 Example:</b> [http://github.com/viatropos/authlogic-connect-example-rails2](http://github.com/viatropos/authlogic-connect-example-rails2)
-* **Rubygems Repository:** [http://rubygems.org/gems/authlogic-connect](http://rubygems.org/gems/authlogic-connect)
-
-## Supported Providers
-
-### Oauth
-
-- Twitter
-- Facebook
-- Google
-
-### OpenID
-
-- MyOpenID
-
-Lists of all known providers here:
-
-- [Oauth Providers](http://wiki.oauth.net/ServiceProviders)
-- [OpenID Providers](http://en.wikipedia.org/wiki/List_of_OpenID_providers)
-
## Install and use
### 1. Install Authlogic Connect
@@ -59,6 +34,20 @@ Rails 3: `Gemfile`
gem "oauth"
gem "oauth2"
gem "authlogic-connect"
+
+### 2b. Add the `OpenIdAuthentication.store`
+
+Do to "some strange problem":http://github.com/openid/ruby-openid/issues#issue/1 I have yet to really understand, Rails 2.3.5 doesn't like when `OpenIdAuthentication.store` is null, which means it uses the "in memory" store and for some reason fails.
+
+So as a fix, add these at the end of your `config/environment.rb` files:
+
+In development mode:
+
+ OpenIdAuthentication.store = :file
+
+In production (on Heroku primarily)
+
+ OpenIdAuthentication.store = :memcache
### 3. Add the Migrations
@@ -77,7 +66,8 @@ Files needed are:
In `config/initializers/authlogic_connect_config.rb`, write your keys and secrets for each service you would like to support. You have to manually go to the websites and register with the service provider (list of those links coming soon, in token classes for now).
AuthlogicConnect.config = {
- :services => {
+ :default => "facebook",
+ :connect => {
:twitter => {
:key => "my_key",
:secret => "my_secret",
@@ -120,14 +110,21 @@ Because of the redirects involved in Oauth and OpenID, you MUST pass a block to
You should save your `@user` objects this way as well, because you also want the user to authenticate with OAuth.
If we don't use the block, we will get a DoubleRender error. This lets us skip that entire block and send the user along their way without any problems.
+
+### 6. Add Parameters to Forms in your Views
-### 6. Create Custom Tokens (if they don't already exist)
+ <%# oauth_register_button :value => "Register with Twitter" %>
+ <%# oauth_login_button :value => "Login with Twitter" %>
+
+Check out the example projects to see exactly what's required. These aren't totally useful yet. Their job is to just send the right parameters to authlogic-connect.
+
+### 7. Create Custom Tokens (if they don't already exist)
Here's an example of the FacebookToken for Oauth
class FacebookToken < OauthToken
- version 2.0 # oauth 2.0
+ version 2.0
settings "https://graph.facebook.com",
:authorize_url => "https://graph.facebook.com/oauth/authorize",
@@ -137,16 +134,103 @@ Here's an example of the FacebookToken for Oauth
If there is an Oauth/OpenID service you need, let me know, or fork/add/push and I will integrate it into the project and add you to the list.
-Currently Implemented (some fully, some partially):
+## Examples
+
+These are examples of what you can get from a User. Code is placed in controller for demo purposes, it should be abstracted into the model.
+
+### API
+
+User model has the following public accessors and methods. This example assumes:
+
+# You've associated your Google, OpenID, and Twitter accounts with this app.
+# You're currently logged in via Google.
+
+ def show
+ @user = @current_user
+ puts @user.tokens #=> [
+ #<OpenidToken id: 12, user_id: 9, type: "OpenidToken", key: "http://my-openid-login.myopenid.com/", token: nil, secret: nil, active: nil, created_at: "2010-05-24 14:52:19", updated_at: "2010-05-24 14:52:19">,
+ #<TwitterToken id: 13, user_id: 9, type: "TwitterToken", key: "my-twitter-id-123", token: "twitter-token", secret: "twitter-secret", active: nil, created_at: "2010-05-24 15:03:05", updated_at: "2010-05-24 15:03:05">,
+ #<GoogleToken id: 14, user_id: 9, type: "GoogleToken", key: "my-email@gmail.com", token: "google-token", secret: "google-secret", active: nil, created_at: "2010-05-24 15:09:04", updated_at: "2010-05-24 15:09:04">]
+ puts @user.tokens.length #=> 3
+ # currently logged in with...
+ puts @user.active_token #=> #<GoogleToken id: 14, user_id: 9, type: "GoogleToken", key: "my-email@gmail.com", token: "google-token", secret: "google-secret", active: nil, created_at: "2010-05-24 15:09:04", updated_at: "2010-05-24 15:09:04">
+ puts @user.authenticated_with #=> ["twitter", "openid", "google"]
+ puts @user.authenticated_with?(:twitter) #=> true
+ puts @user.authenticated_with?(:facebook) #=> false
+ puts @user.has_token?(:google) #=> true
+ puts @user.get_token(:google) #=> #<GoogleToken id: 14, user_id: 9, type: "GoogleToken", key: "my-email@gmail.com", token: "google-token", secret: "google-secret", active: nil, created_at: "2010-05-24 15:09:04", updated_at: "2010-05-24 15:09:04">
+ # change active_token
+ @user.active_token = @user.get_token(:twitter)
+ puts @user.active_token #=> #<TwitterToken id: 13, user_id: 9, type: "TwitterToken", key: "my-twitter-id-123", token: "twitter-token", secret: "twitter-secret", active: nil, created_at: "2010-05-24 15:03:05", updated_at: "2010-05-24 15:03:05">
+ @twitter = @user.active_token
+ @twitter_profile = JSON.parse(@twitter.get("/account/verify_credentials.json").body) #=> twitter api stuff
+ # ...
+ end
-- [Oauth Tokens](http://github.com/viatropos/authlogic-connect/tree/master/lib/authlogic_connect/oauth/tokens/)
-
-### 7. Add login and register buttons to your views
+### Get Facebook Data
- <%# oauth_register_button :value => "Register with Twitter" %>
- <%# oauth_login_button :value => "Login with Twitter" %>
+If they've associated their Facebook account with your site, you can access Facebook data.
-Check out the example projects to see exactly what's required. These aren't totally useful yet. Their job is to just send the right parameters to authlogic-connect.
+ def show
+ @user = @current_user
+ token = @user.active_token
+ facebook = JSON.parse(token.get("/me"))
+ @profile = {
+ :id => facebook["id"],
+ :name => facebook["name"],
+ :photo => "https://graph.facebook.com/#{facebook["id"]}/picture",
+ :link => facebook["link"],
+ :title => "Facebook"
+ }
+ @profile = @user.profile
+ end
+
+## Supported Providers
+
+### Oauth
+
+- Twitter
+- Facebook
+- Google
+
+### OpenID
+
+- MyOpenID
+
+Lists of all known providers here:
+
+- [Oauth Providers](http://wiki.oauth.net/ServiceProviders)
+- [OpenID Providers](http://en.wikipedia.org/wiki/List_of_OpenID_providers)
+- [More OpenID](http://openid.net/get-an-openid/)
+
+## Oauth vs. OpenID
+
+There is a big but subtle difference between Oauth and OpenID: Oauth is NOT a login protocol. OpenID IS.
+
+You should use Oauth when you want to be able to access and/or manipulate data on behalf of the user. If all you want is authentication, OpenID is best. However, if you want to login through Twitter or Facebook, you _have_ to use Oauth (forget Facebook Connect, too complicated).
+
+An example would be using Google and Oauth. With Google and Oauth, the user can grant you rights to "access the gmail contacts" for example, and you can get a list of their contacts. That requires that your app is authorized, which requires they grant access via Oauth. This is kinda strange though, because why does a login app need to access your google contacts? It doesn't. That's why we should use OpenID in this case. But we can still use Oauth... continuing...
+
+The problem with the Google Contacts Oauth example is that when you get the Oauth Access Token, Google doesn't give you any data, so you can't say "the 'guy who just logged in's email is abc@gmail.com, save that to the database". That's where OpenID would shine.
+
+If you want to use Oauth for logging in, you must get back some unique key to identify the user by. The best options are something like email, username, or some unique id. This is accomplished in the `GoogleToken` oauth class by passing a block to the `key` class method:
+
+ key do |access_token|
+ body = JSON.parse(access_token.get("https://www.google.com/m8/feeds/contacts/default/full?alt=json&max-results=0").body)
+ email = body["feed"]["author"].first["email"]["$t"] # $t is some weird google json thing
+ end
+
+That hack lets us use Oauth to get the email address of the user, which we need if we want to somehow find the account for a user who has logged out
+
+The confusing thing is that Twitter allows you to login with Oauth, it's one of the few it seems. This is because Twitter sends back the `user_id` and `screen_name`, allowing you to pretend the user logged in. Google doesn't send that. Which means you have to make an _additional_ call to the service, if you're using Oauth. If you're using OpenID, that's specifically for login so you're going to get back email/name/etc.
+
+## Helpful links
+
+* <b>Authlogic:</b> [http://github.com/binarylogic/authlogic](http://github.com/binarylogic/authlogic)
+* <b>Authlogic Connect Example Project:</b> [http://github.com/viatropos/authlogic-connect-example](http://github.com/viatropos/authlogic-connect-example)
+* <b>Live example with Twitter and Facebook using Rails 3:</b> [http://authlogic-connect.heroku.com](http://authlogic-connect.heroku.com)
+* <b>Rails 2.3.5 Example:</b> [http://github.com/viatropos/authlogic-connect-example-rails2](http://github.com/viatropos/authlogic-connect-example-rails2)
+* **Rubygems Repository:** [http://rubygems.org/gems/authlogic-connect](http://rubygems.org/gems/authlogic-connect)
## The Flow
@@ -192,7 +276,9 @@ Then a Rack Middleware filter converts the GET return request from the authentic
### Tests
-This has no tests! I had to build this in a day and am not fluent with Shoulda, which I'd like to use. It should have lots of tests to make sure all permutations of login and account association work perfectly.
+This only has a few unit tests. Enough to make sure the methods are returning what we are expecting.
+
+It should have Functional and Integration tests, using the Authlogic Connect example projects. If any of you guys know of an easy way to set that up, I'd love to know. Send me a github message :).
Goal:
@@ -201,19 +287,46 @@ Goal:
- Testing style like [Paperclip Tests](http://github.com/thoughtbot/paperclip/tree/master/test/)
- Rails 2.3+ and Rails 3 Compatability
-I have no idea how to get up and running with Autotest and Shoulda right now. If you know, I'd love to get the answer on Stack Overflow:
-
-[http://stackoverflow.com/questions/2823224/what-test-environment-setup-do-committers-use-in-the-ruby-community](http://stackoverflow.com/questions/2823224/what-test-environment-setup-do-committers-use-in-the-ruby-community)
+### TODO
-## TODO
+- If the user bails out in the middle of a login session, there needs to be a mechanism that knows how to reset their session.
+- If the openid is filled out, and then the user clicks Twitter oauth, it should know that it should log them in via twitter. This can only really be done by javascript. But what should take precedence? The thing that requires no typing: oauth. So oauth should be checked first on save.
+- Add rememberme functionality correctly. Right now I think it remembers you by default.
+- Login should work without having to access the remote service again.
+- If I create new user with Twitter or Google, then logout, I can login through twitter no problem. However, I cannot login through Google. This is because google returns new tokens, so I can't find it in the database. How do I find it? Also, if you go and revoke access to twitter (go to your twitter profile on twitter.com, click "settings", and revoke access to app) after you've created an account, and you try to login, same problem. This is because tokens are regenerated. NEED CONFIRMATION SCREEN
+- If the user has only created an account with say Twitter, then logs out, if they try to login with google, it should ask if they have a different account. How should this work?
-- Change `register_with_oauth` and related to `register_method` and `login_method`: oauth, openid, traditional
-- Build view helpers
+OAuth is for accessing remote information. It doesn't always give you data about the user. OpenID on the other hand gives you all the info you need for login.
## Helpful References for Rails 3
- [Rails 3 Initialization Process](http://ryanbigg.com/guides/initialization.html)
- [Rails 3 Plugins - Part 1, Big Picture](http://www.themodestrubyist.com/2010/03/01/rails-3-plugins---part-1---the-big-picture/)
- [Rails 3 Plugins - Part 2, Writing an Engine](http://www.themodestrubyist.com/2010/03/05/rails-3-plugins---part-2---writing-an-engine/)
- [Rails 3 Plugins - Part 3, Initializers](http://www.themodestrubyist.com/2010/03/16/rails-3-plugins---part-3---rake-tasks-generators-initializers-oh-my/)
-- [Using Gemspecs as Intended](http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/)
+- [Using Gemspecs as Intended](http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/)
+
+## Parameters
+
+should look like this:
+
+Params from form:
+
+ {"authentication_type"=>"user", "submit"=>"Register", "openid_identifier"=>"", "oauth_provider"=>"twitter"}
+
+Session just before redirect:
+
+ {"authentication_type"=>"user", "oauth_request_token"=>"token_key", "session_id"=>"session_hash", "auth_callback_method"=>"POST", "auth_attributes"=>{"login_count"=>0}, "oauth_request_token_secret"=>"token_secret", "auth_request_class"=>"User", "auth_method"=>"oauth", "oauth_provider"=>"twitter"}
+
+## Details
+
+The regular OAuth process is a four-step sequence:
+
+1. ask for a "request" token.
+2. ask for the token to be authorized, which triggers user approval.
+3. exchange the authorized request token for an "access" token.
+4. use the access token to interact with the user's Google service data.
+
+## OpenID Process
+
+If they logout and log back into OpenID, we can find their token solely from the data they pass in (`openid_identifier`). This is unlike Oauth, where we have to run through the whole process again because we don't know anything about them.
View
@@ -6,7 +6,7 @@ require 'rake/gempackagetask'
spec = Gem::Specification.new do |s|
s.name = "authlogic-connect"
s.author = "Lance Pollard"
- s.version = "0.0.3.4"
+ s.version = "0.0.3.6"
s.summary = "Authlogic Connect: Let your app use all of Oauth and OpenID"
s.homepage = "http://github.com/viatropos/authlogic-connect"
s.email = "lancejpollard@gmail.com"
@@ -7,78 +7,9 @@
this = File.dirname(__FILE__)
library = "#{this}/authlogic_connect"
-class Hash
- def recursively_symbolize_keys!
- self.symbolize_keys!
- self.values.each do |v|
- if v.is_a? Hash
- v.recursively_symbolize_keys!
- elsif v.is_a? Array
- v.recursively_symbolize_keys!
- end
- end
- self
- end
-end
-
-class Array
- def recursively_symbolize_keys!
- self.each do |item|
- if item.is_a? Hash
- item.recursively_symbolize_keys!
- elsif item.is_a? Array
- item.recursively_symbolize_keys!
- end
- end
- end
-end
-
-module AuthlogicConnect
- KEY = "connect"
-
- class << self
-
- attr_accessor :config
-
- def config=(value)
- value.recursively_symbolize_keys!
- @config = value
- end
-
- def key(path)
- result = self.config
- path.to_s.split(".").each { |node| result = result[node.to_sym] if result }
- result
- end
-
- def credentials(service)
- key("#{KEY}.#{service.to_s}")
- end
-
- def services
- key(KEY)
- end
-
- def service_names
- services.keys.collect(&:to_s)
- end
-
- def include?(service)
- !credentials(service).nil?
- end
-
- def token(key)
- raise "can't find key '#{key.to_s}' in AuthlogicConnect.config" unless AuthlogicConnect.include?(key) and !key.to_s.empty?
- "#{key.to_s.camelcase}Token".constantize
- end
-
- def consumer(key)
- token(key).consumer
- end
- end
-end
-
require "#{this}/open_id_authentication"
+require "#{library}/ext"
+require "#{library}/authlogic_connect"
require "#{library}/callback_filter"
require "#{library}/token"
require "#{library}/openid"
Oops, something went wrong.

0 comments on commit 9bab11d

Please sign in to comment.