Permalink
Browse files

Merge "CFID 325 - Ruby login server for the UAA"

  • Loading branch information...
joeldsa authored and Gerrit Code Review committed Jun 19, 2012
2 parents ae43346 + 0ab4378 commit c22e65fd40d5dfa7d004cccfa49e1f8b6a4eae87
Showing with 1,026 additions and 0 deletions.
  1. +8 −0 samples/ruby-login-server/Gemfile
  2. +34 −0 samples/ruby-login-server/Gemfile.lock
  3. +86 −0 samples/ruby-login-server/README.md
  4. +12 −0 samples/ruby-login-server/config.ru
  5. +234 −0 samples/ruby-login-server/login.rb
  6. +94 −0 samples/ruby-login-server/openid_login.rb
  7. +101 −0 samples/ruby-login-server/public/css/openid-shadow.css
  8. +69 −0 samples/ruby-login-server/public/css/openid.css
  9. BIN samples/ruby-login-server/public/images.large/aol.gif
  10. BIN samples/ruby-login-server/public/images.large/facebook.gif
  11. BIN samples/ruby-login-server/public/images.large/google.gif
  12. BIN samples/ruby-login-server/public/images.large/mailru.gif
  13. BIN samples/ruby-login-server/public/images.large/myopenid.gif
  14. BIN samples/ruby-login-server/public/images.large/openid.gif
  15. BIN samples/ruby-login-server/public/images.large/rambler.gif
  16. BIN samples/ruby-login-server/public/images.large/verisign.gif
  17. BIN samples/ruby-login-server/public/images.large/vkontakte.gif
  18. BIN samples/ruby-login-server/public/images.large/yahoo.gif
  19. BIN samples/ruby-login-server/public/images.large/yandex.gif
  20. BIN samples/ruby-login-server/public/images.small/aol.ico
  21. BIN samples/ruby-login-server/public/images.small/aol.ico.gif
  22. BIN samples/ruby-login-server/public/images.small/aol.ico.png
  23. BIN samples/ruby-login-server/public/images.small/blogger.ico
  24. BIN samples/ruby-login-server/public/images.small/blogger.ico.gif
  25. BIN samples/ruby-login-server/public/images.small/blogger.ico.png
  26. BIN samples/ruby-login-server/public/images.small/claimid.ico
  27. BIN samples/ruby-login-server/public/images.small/claimid.ico.gif
  28. BIN samples/ruby-login-server/public/images.small/claimid.ico.png
  29. BIN samples/ruby-login-server/public/images.small/clickpass.ico
  30. BIN samples/ruby-login-server/public/images.small/clickpass.ico.gif
  31. BIN samples/ruby-login-server/public/images.small/clickpass.ico.png
  32. BIN samples/ruby-login-server/public/images.small/facebook.ico
  33. BIN samples/ruby-login-server/public/images.small/facebook.ico.gif
  34. BIN samples/ruby-login-server/public/images.small/facebook.ico.png
  35. BIN samples/ruby-login-server/public/images.small/flickr.ico
  36. BIN samples/ruby-login-server/public/images.small/flickr.ico.gif
  37. BIN samples/ruby-login-server/public/images.small/flickr.ico.png
  38. BIN samples/ruby-login-server/public/images.small/google.ico
  39. BIN samples/ruby-login-server/public/images.small/google.ico.gif
  40. BIN samples/ruby-login-server/public/images.small/google.ico.png
  41. BIN samples/ruby-login-server/public/images.small/google_profile.ico
  42. BIN samples/ruby-login-server/public/images.small/google_profile.ico.gif
  43. BIN samples/ruby-login-server/public/images.small/google_profile.ico.png
  44. BIN samples/ruby-login-server/public/images.small/launchpad.ico
  45. BIN samples/ruby-login-server/public/images.small/launchpad.ico.gif
  46. BIN samples/ruby-login-server/public/images.small/launchpad.ico.png
  47. BIN samples/ruby-login-server/public/images.small/linkedin.ico
  48. BIN samples/ruby-login-server/public/images.small/linkedin.ico.gif
  49. BIN samples/ruby-login-server/public/images.small/linkedin.ico.png
  50. BIN samples/ruby-login-server/public/images.small/livejournal.ico
  51. BIN samples/ruby-login-server/public/images.small/livejournal.ico.gif
  52. BIN samples/ruby-login-server/public/images.small/livejournal.ico.png
  53. BIN samples/ruby-login-server/public/images.small/mailru.ico
  54. BIN samples/ruby-login-server/public/images.small/mailru.ico.gif
  55. BIN samples/ruby-login-server/public/images.small/mailru.ico.png
  56. BIN samples/ruby-login-server/public/images.small/myopenid.ico
  57. BIN samples/ruby-login-server/public/images.small/myopenid.ico.gif
  58. BIN samples/ruby-login-server/public/images.small/myopenid.ico.png
  59. BIN samples/ruby-login-server/public/images.small/openid.ico
  60. BIN samples/ruby-login-server/public/images.small/openid.ico.gif
  61. BIN samples/ruby-login-server/public/images.small/openid.ico.png
  62. BIN samples/ruby-login-server/public/images.small/rambler.ico
  63. BIN samples/ruby-login-server/public/images.small/rambler.ico.gif
  64. BIN samples/ruby-login-server/public/images.small/rambler.ico.png
  65. BIN samples/ruby-login-server/public/images.small/technorati.ico
  66. BIN samples/ruby-login-server/public/images.small/technorati.ico.gif
  67. BIN samples/ruby-login-server/public/images.small/technorati.ico.png
  68. BIN samples/ruby-login-server/public/images.small/twitter.ico
  69. BIN samples/ruby-login-server/public/images.small/twitter.ico.gif
  70. BIN samples/ruby-login-server/public/images.small/twitter.ico.png
  71. BIN samples/ruby-login-server/public/images.small/verisign.ico
  72. BIN samples/ruby-login-server/public/images.small/verisign.ico.gif
  73. BIN samples/ruby-login-server/public/images.small/verisign.ico.png
  74. BIN samples/ruby-login-server/public/images.small/vidoop.ico
  75. BIN samples/ruby-login-server/public/images.small/vidoop.ico.gif
  76. BIN samples/ruby-login-server/public/images.small/vidoop.ico.png
  77. BIN samples/ruby-login-server/public/images.small/vkontakte.ico
  78. BIN samples/ruby-login-server/public/images.small/vkontakte.ico.gif
  79. BIN samples/ruby-login-server/public/images.small/vkontakte.ico.png
  80. BIN samples/ruby-login-server/public/images.small/winliveid.ico
  81. BIN samples/ruby-login-server/public/images.small/winliveid.ico.gif
  82. BIN samples/ruby-login-server/public/images.small/winliveid.ico.png
  83. BIN samples/ruby-login-server/public/images.small/wordpress.ico
  84. BIN samples/ruby-login-server/public/images.small/wordpress.ico.gif
  85. BIN samples/ruby-login-server/public/images.small/wordpress.ico.png
  86. BIN samples/ruby-login-server/public/images.small/yahoo.ico
  87. BIN samples/ruby-login-server/public/images.small/yahoo.ico.gif
  88. BIN samples/ruby-login-server/public/images.small/yahoo.ico.png
  89. BIN samples/ruby-login-server/public/images.small/yandex.ico
  90. BIN samples/ruby-login-server/public/images.small/yandex.ico.gif
  91. BIN samples/ruby-login-server/public/images.small/yandex.ico.png
  92. BIN samples/ruby-login-server/public/images/openid-inputicon.gif
  93. BIN samples/ruby-login-server/public/images/openid-providers-en.png
  94. BIN samples/ruby-login-server/public/images/openid-providers-ru.png
  95. +32 −0 samples/ruby-login-server/public/js/jquery-1.2.6.min.js
  96. +96 −0 samples/ruby-login-server/public/js/openid-en.js
  97. +202 −0 samples/ruby-login-server/public/js/openid-jquery.js
  98. +11 −0 samples/ruby-login-server/views/confirm.erb
  99. +47 −0 samples/ruby-login-server/views/login.erb
@@ -0,0 +1,8 @@
+source :rubygems
+
+gem "sinatra"
+gem "rest-client"
+gem "yajl-ruby"
+gem "sequel"
+gem "ruby-openid"
+gem "thin"
@@ -0,0 +1,34 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ daemons (1.1.8)
+ eventmachine (0.12.10)
+ mime-types (1.18)
+ rack (1.4.1)
+ rack-protection (1.2.0)
+ rack
+ rest-client (1.6.7)
+ mime-types (>= 1.16)
+ ruby-openid (2.1.8)
+ sequel (3.34.1)
+ sinatra (1.3.2)
+ rack (~> 1.3, >= 1.3.6)
+ rack-protection (~> 1.2)
+ tilt (~> 1.3, >= 1.3.3)
+ thin (1.3.1)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
+ tilt (1.3.3)
+ yajl-ruby (0.8.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ rest-client
+ ruby-openid
+ sequel
+ sinatra
+ thin
+ yajl-ruby
@@ -0,0 +1,86 @@
+# Login Sample Application
+
+This login application is a sample for how you can set up your own custom login user interface using the UAA as a backend Identity Provider. The application also supports different openid identity providers. The application is written in ruby and uses the sinatra framework. You may choose to embed it along with your code to provide a customized look and feel for the login interface using the UAA as an identity provider and a token server to issue Oauth access tokens.
+
+## Quick start
+
+Start your UAA
+
+ $ git clone git@github.com:cloudfoundry/uaa.git
+ $ cd uaa
+ $ mvn install
+ $ mvn tomcat:run
+
+Verify that the uaa has started by going to http://localhost:8080/uaa
+
+Start the sample login application
+
+ $ cd samples/ruby-login-server
+ $ bundle install
+ $ bundle exec thin start
+ >> Using rack adapter
+ I, [2012-06-14T14:54:46.273942 #5369] INFO -- : Using token server http://localhost:8080/uaa
+ >> Thin web server (v1.3.1 codename Triple Espresso)
+ >> Maximum connections set to 1024
+ >> Listening on 0.0.0.0:3000, CTRL+C to stop
+
+You can start the oauth flow with a HTTP GET request to http://localhost:3000/oauth/authorize?client_id=app&response_type=code&scope=openid&redirect_uri=http://foo.com
+
+Login with the pre-created UAA user/password of "marissa/koala"
+
+## Customizing the application
+
+### Using a different token server
+
+The back end UAA token server URL can be customized by setting the UAA_TOKEN_SERVER environment variable
+This application also uses the login client to support logging in with authenticated email addresses from OpenID providers. The secret for the login client can be customized by
+setting the LOGIN_CLIENT_SECRET environment variable
+
+For details on the login client, please refer to the UAA.
+
+Each of these variables default to the values for a locally hosted UAA.
+
+### Customizing the user interface
+
+There are two ruby templates that can be customized for look and feel to match your application. These are login.erb used to display the login interface and confirm.erb used to display the authorization confirmation page.
+
+### Logging
+
+Before using this as a sample, please configure the application logging to ensure that no confidential information is logged.
+
+## Playing with the entire flow
+
+You may write a simple sinatra client application to test the end to end flow as follows. This client also uses application defaults for a locally hosted UAA. It logs the user into the UAA and displays a UAA access token if the login succeeds
+
+ require 'sinatra'
+ require 'yajl'
+ require 'restclient'
+ require 'base64'
+
+ LOGIN_SERVER_URL = ENV['LOGIN_SERVER_URL'] || "http://localhost:3000"
+ UAA_TOKEN_SERVER = ENV['UAA_TOKEN_SERVER'] || "http://localhost:8080/uaa"
+ CLIENT_SECRET = ENV['CLIENT_SECRET'] || "appclientsecret"
+
+ get '/' do
+ "<html><body><a href=\"#{LOGIN_SERVER_URL}/oauth/authorize?client_id=app&response_type=code&scope=openid&redirect_uri=#{request.scheme}://#{request.host_with_port}/done\"\">Login</a></body></html>"
+ end
+
+ get '/done' do
+ code = params[:code]
+ $logger = Logger.new(STDOUT)
+ RestClient.log = $logger
+ response = RestClient.post("#{UAA_TOKEN_SERVER}/oauth/token", {"grant_type" => "authorization_code", "code" => "#{code}", "redirect_uri" => "#{request.scheme}://#{request.host_with_port}/done"}, {:accept => :json, :authorization => "Basic #{Base64.strict_encode64("app:#{CLIENT_SECRET}")}"}) \
+ {|response, request, result, &block| response}
+ puts "#{response.body.inspect}"
+
+ begin
+ access_token = Yajl::Parser.new.parse(response.body)["access_token"]
+ decoded_token = Base64.decode64(access_token.split('.')[1])
+ "Access Token from UAA is #{access_token} \
+ <br /><br />Decoded token is #{decoded_token} \
+ <br /><br /><a href=\"#{LOGIN_SERVER_URL}/logout\">Logout</a>"
+ rescue => e
+ puts "#{e.backtrace}"
+ "Could not fetch access token"
+ end
+ end
@@ -0,0 +1,12 @@
+$:.unshift(".")
+
+require 'login'
+require 'openid_login'
+
+map "/openid" do
+ run OpenIdLoginApplication.new
+end
+
+map "/" do
+ run LoginApplication.new
+end
@@ -0,0 +1,234 @@
+require 'sinatra/base'
+require 'restclient'
+require 'yajl'
+require 'logger'
+require 'base64'
+
+# The LoginApplication class handles the oauth
+# authorization flow for the CloudFoundry UAA
+class LoginApplication < Sinatra::Base
+ enable :sessions
+
+ # URL of the uaa token server
+ UAA_TOKEN_SERVER = ENV['UAA_TOKEN_SERVER'] || "http://localhost:8080/uaa"
+ # Client secret of the login client. The login client allows this
+ # application to authenticate to the token endpoint to get an access token
+ # for a pre-authenticated email address (for the case when a pre-authenticated
+ # email address is received by this application from an OpenID provider)
+ LOGIN_CLIENT_SECRET = ENV['LOGIN_CLIENT_SECRET'] || "loginsecret"
+
+ # Handles requests to the /login endpoint.
+ # If an authenticated user session already exists with the authorization server,
+ # the user is redirected to a confimation page
+ get '/login' do
+ pass unless params[:email].nil?
+
+ # If there is already a session with the uaa, the user has been authenticated
+ if uaa_session?(request).nil?
+ erb :login
+ else
+ redirect '/confirm'
+ end
+ end
+
+ # The start of the oauth flow for the client application
+ get '/oauth/authorize' do
+ if params.nil? || params.empty?
+ halt 404
+ end
+
+ # Store the request parameters in session
+ [:client_id, :response_type, :scope, :redirect_uri, :state].each{|param| session[param] = params[param.to_s]}
+ $logger.debug("Saving request parameters #{session.inspect}")
+
+ # Redirect the user to the login page if no session
+ # is available with the uaa
+ if uaa_session?(request).nil?
+ redirect '/login'
+ else
+ redirect '/confirm'
+ end
+ end
+
+ # Common login flow for incoming username and password credentials
+ # or incoming authenticated email addresses
+ login = lambda do
+ username = request[:username]
+ password = request[:password]
+
+ uaa_response = nil
+ unless username.nil? && password.nil?
+ # Post the credentials and get a session with the uaa. Save the uaa cookie
+ uaa_response = post("#{UAA_TOKEN_SERVER}/login.do", \
+ {"username" => username, "password" => password})
+ $logger.debug "#{uaa_response.headers.inspect}"
+ else
+ # Get the email address from the openid provider and ask for authorization directly
+ email = session[:authenticated_email]
+ $logger.debug "authenticated email address #{email}"
+ # Post the credentials and get a session with the uaa. Save the uaa cookie
+ uaa_response = post_to_authorize({"login" => "#{Yajl::Encoder.encode("username" => email)}"}, \
+ {:authorization => "bearer #{login_access_token()}"})
+ $logger.debug "#{uaa_response.headers.inspect}"
+ end
+
+ # If the response from the UAA is a redirect ending in error=true, authentication has failed
+ if uaa_response.headers[:location] =~ /\?error=true/
+ redirect '/login?error=true'
+ end
+
+ if uaa_response.nil?
+ $logger.debug("Could not establish session with the uaa")
+ redirect '/login?error=true'
+ end
+
+ # Maintain the cookie with the uaa
+ uaa_cookie = uaa_response.cookies["JSESSIONID"]
+ $logger.debug "UAA Cookie #{uaa_cookie}"
+ response.set_cookie("uaa_cookie", uaa_cookie)
+
+ redirect '/confirm'
+ end
+
+ # A similar authorization flow is implemented for the uaa as well as openid flows
+ get '/authenticatedlogin', &login
+ post '/login', &login
+
+ # Handles authorization confirmation flow
+ get '/confirm' do
+ cookie = uaa_session?(request)
+ $logger.debug("Current values in session #{session.inspect}")
+ unless cookie.nil?
+ [:client_id, :response_type, :scope, :redirect_uri].each do |parameter| \
+ unless session[:parameter].nil?
+ halt 400, "Invalid request. User authenticated but unable to find \"#{parameter}\" parameter \
+ from request. Please see README for directions."
+ end
+ end
+ # Maintaining the UAA cookie from the successful login to the uaa,
+ # post to the /oauth/authorize endpoint to get the confirmation data
+ request_params = {"client_id" => session[:client_id],
+ "response_type" => session[:response_type],
+ "scope" => session[:scope],
+ "redirect_uri" => session[:redirect_uri]}
+ request_params["state"] = session[:state] unless session[:state].nil?
+ uaa_response = post_to_authorize(request_params, {:cookies => { :JSESSIONID => cookie}})
+ $logger.debug "#{uaa_response.inspect}"
+ case uaa_response.code
+ when 200
+ confirmation_info = Yajl::Parser.new.parse(uaa_response.body)
+ $logger.debug "#{confirmation_info.inspect}"
+ session[:confirm_key] = (confirmation_info["options"]["confirm"]["key"] \
+ if confirmation_info["options"] && confirmation_info["options"]["confirm"]) || "user_oauth_approval"
+
+ erb :confirm, :locals => {:client_id => confirmation_info["authorizationRequest"]["clientId"], \
+ :scopes => confirmation_info["authorizationRequest"]["scope"]}
+ when 302
+ # Access confirmation not required, get the code.
+ $logger.debug "#{uaa_response.headers[:location]}"
+
+ redirect uaa_response.headers[:location]
+ else
+ halt 500, "error from the token server #{uaa_response.inspect}"
+ end
+ else
+ halt 500, "invalid state"
+ end
+ end
+
+ # User confirms / denies authorization
+ post '/confirm' do
+ choice = params[:choice]
+ $logger.debug "#{choice}"
+
+ target = '/login'
+
+ # User confirms authorization
+ if choice == "yes"
+ cookie = request.cookies['uaa_cookie']
+
+ # Post confirmation to the uaa and redirect to the target
+ request_params = {"client_id" => session[:client_id],
+ "response_type" => session[:response_type],
+ "scope" => session[:scope],
+ "redirect_uri" => session[:redirect_uri],
+ session[:confirm_key] => "true"}
+ request_params[:state] = session[:state] unless session[:state].nil?
+ uaa_response = post_to_authorize(request_params, {:cookies => { :JSESSIONID => cookie}})
+ $logger.debug "#{uaa_response.headers[:location]}"
+ target = uaa_response.headers[:location]
+ else
+ cleanup(response)
+ end
+
+ redirect target
+ end
+
+ get '/logout' do
+ cleanup(response)
+ end
+
+ helpers do
+ def uaa_session?(request)
+ cookie = request.cookies['uaa_cookie']
+ $logger.debug("Found uaa session cookie #{cookie}")
+ unless cookie.nil?
+ $logger.debug "cookie value #{cookie}"
+ response = post_to_authorize(nil, {:cookies => { :JSESSIONID => cookie}})
+ $logger.debug("uaa_session #{response.headers.inspect}")
+ if response.code == 302 && response.headers[:location] =~ /\/login/
+ cookie = nil
+ end
+ end
+ cookie
+ end
+
+ def cleanup(response)
+ response.delete_cookie('uaa_cookie')
+ session.clear
+ end
+
+ def post_to_authorize(request_params, headers)
+ headers = headers.merge(:accept => :json)
+
+ $logger.debug("Headers to post to authorize #{headers}")
+
+ post("#{UAA_TOKEN_SERVER}/oauth/authorize", request_params, headers)
+ end
+
+ def login_access_token
+ # Get an access token for the login client
+ login_response = post("#{UAA_TOKEN_SERVER}/oauth/token", \
+ {"response_type" => "token", "grant_type" => "client_credentials"}, \
+ {:accept => :json, :authorization => "Basic #{Base64.strict_encode64("login:#{LOGIN_CLIENT_SECRET}")}"})
+ $logger.debug "#{login_response.body.inspect}"
+ access_token = Yajl::Parser.new.parse(login_response.body)["access_token"]
+ end
+
+ def post(url, content, headers = nil)
+ begin
+ response = RestClient.post(url, content, headers) \
+ {|response, request, result, &block| response}
+ rescue => e
+ $logger.error("Error connecting to #{url}, #{e.backtrace}")
+ halt 500, "UAA unavailable."
+ end
+ end
+ end
+
+ configure :development do
+ $logger = Logger.new(STDOUT)
+ RestClient.log = $logger
+ $logger.info("Using token server #{UAA_TOKEN_SERVER}")
+ end
+
+ configure :production do
+ $logger = Logger.new(STDOUT)
+ $logger.info("Using token server #{UAA_TOKEN_SERVER}")
+ end
+
+ get '/' do
+ redirect '/login'
+ end
+
+end
Oops, something went wrong.

0 comments on commit c22e65f

Please sign in to comment.