RACK / SSO - Meetupfiles.meetup.com/437842/svrug-november-2009.pdfRACK / SSO and a little bit about...

Preview:

Citation preview

RACK / SSOand a little bit about the Bundler

Tuesday, December 1, 2009

hello hello

COREY DONOHOE@atmos / atmos@atmos.org

Tuesday, December 1, 2009

i’m a software developer and open source participant for a number of yearsthis is me in hawaii last month with a much more awesome beard

ENGINE YARDTuesday, December 1, 2009

i’ve been at engineyard for 2.5 yearsdid support for the first year, now doing internal developmentwe do a lot of microapps

HANCOCK

Tuesday, December 1, 2009

will smith was not involved in the creation of this project

Tuesday, December 1, 2009

like your john hancocknot for everyone but i’m hoping to keep it ongoing as a working example

RACKTuesday, December 1, 2009

it’s what everyone is using now, rails, merb, sinatra, ramazerealizing the power of rack helped me really embrace the simplicity of sinatra

SINATRA IS AWESOME

Tuesday, December 1, 2009

2009 has mainly been sinatra and datamapper for usthere’s still 2 merb apps we maintain, one of them is our SSO provider

GIT://GITHUB.COM/ATMOS/HANCOCK.GIT

Tuesday, December 1, 2009

SINGLE SIGN ON

Tuesday, December 1, 2009

TRADITIONAL OPENID

Tuesday, December 1, 2009

user agent == browserconsumer == app using openid to authenticate clientsprovider == openid provider the consumer talks to

TRADITIONAL OPENID

Tuesday, December 1, 2009

no more specifying who you areno more consumer <-> provider identity negotiationno more choosing of profiles on the server

HANCOCK NEGOTIATION

Tuesday, December 1, 2009

by cutting those out there’s fewer redirectsmuch easier to see what portions of the openid spec are usedstill conforms to the spec so it should be easily accessible from any language

# ~/p/hancock/bin/shotgun -p PORT config.rurequire 'hancock'

DataMapper.setup(:default, "sqlite3://#{File.dirname(__FILE__)}/development.rb")DataMapper.auto_migrate!

Hancock::Consumer.create(:url => 'http://localhost:3000/sso/login', :label => 'Rails Dev', :internal => false)Hancock::Consumer.create(:url => 'http://localhost:9292/sso/login', :label => 'Rack Fans', :internal => false)Hancock::Consumer.create(:url => 'http://localhost:9393/sso/login', :label => 'Shotgun Fans', :internal => false)

class Dragon < Hancock::App get '/' do redirect '/sso/login' unless session['hancock_server_user_id'] erb "<h2>Hello <%= session_user.name %><!-- <%= session.inspect %>" endendrun Dragon

run Hancock::App

Tuesday, December 1, 2009

example rackup file

HANCOCK-CLIENT

Tuesday, December 1, 2009

there’s a client middleware for consumersmicroapps are great for spikes that can expose business valuewith something like this we can spin up microapps quickly

require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config| config.gem 'hancock', :lib => 'hancock'

config.middleware.use Hancock::Client::Middleware do |sso| sso.sso_url = 'http://localhost:20000' end # all your other normal stuffend

hancock-client-rails

Tuesday, December 1, 2009

middleware like this works in rails too in versions > 2.3.xyou can either use the use keyword or the the generators for rails metal

UNDER THE HOODTuesday, December 1, 2009

it’s what everyone is using now, rails, merb, sinatra, ramazerealizing the power of rack helped me really embrace the simplicity of sinatra

HTTP://GITHUB.COM/

Tuesday, December 1, 2009

use

Tuesday, December 1, 2009

specific middlewareuse invokes initialize methodit can take a block

use in Sinatra

Tuesday, December 1, 2009

initialize arity for usefirst parameter is the middleware constantinitializing with a block can do cool things.

#!/usr/bin/env rackup

use EY::SSO do |sso| sso.only_staff!enduse EY::ContactManager

Tuesday, December 1, 2009

use in Rails

Tuesday, December 1, 2009

require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config| config.gem 'hancock', :lib => 'hancock'

config.middleware.use Hancock::Client::Middleware do |sso| sso.sso_url = 'http://localhost:20000' end # all your other normal stuffend

config.middleware.use

Tuesday, December 1, 2009

# Allow the metal piece to run in isolationrequire(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)require 'hancock-client'

class Sso < Hancock::Client::Default disable :raise_errors set :sso_url, 'http://hancock.atmos.org/sso'end

script/generate metal sso

Tuesday, December 1, 2009

works fine for inheriting from Sinatra::Defaultso you can write sinatra in rails if you want :)

map

Tuesday, December 1, 2009

map is a way to ‘mount’ applicationswe use it for everyday kinds of things

helpers do def url(path) request.script_name + path endend

Tuesday, December 1, 2009

#!/usr/bin/env rackup

require File.dirname(__FILE__) + '/lib/setup'require 'gateway/app'require 'migration/app'

use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public"

map "/gateway/" do use EY::SSO run Gateway::Append

map "/migration/" do use Rack::ShowExceptions if ENV["RACK_ENV"] == "production" use EY::SSO do |sso| sso.only_staff! end end run Migration::Append

map "/" do app = lambda do |env| [404, {"Content-Type" => "text/plain", "Content-Length" => "9"}, ["Not found"]] end run append

Tuesday, December 1, 2009

two separate apps, Migration::App and Gateway::App working fine together on subdir mappings

PROBLEMS

http://www.flickr.com/photos/northover/3022796561/

Tuesday, December 1, 2009

FRAGILE

http://www.flickr.com/photos/northover/3022796561/

Tuesday, December 1, 2009

most fragile point is our integration with our CRM system, salesforcewe have datamapper models but keeping things in sync proves to be crazywe use hoptoad and are diligent about resolving any exceptions we receive

CONFUSING

http://www.flickr.com/photos/northover/3022796561/

Tuesday, December 1, 2009

usability and accessibility can easily be neglected, users get confusednot being able to get every system on to something centralized leads to more confusionif you’re going to go this route spend a chunk of time on the UX

TESTING

http://www.flickr.com/photos/northover/3022796561/

Tuesday, December 1, 2009

how does SSO really behave today, will it behave that way tomorrow?providing a decent client API makes it easy to test, provide mocks instead of vice versamake it trivial to test so people actually do, cuts down on support

ADAPTERS MAKE IT EASY

Tuesday, December 1, 2009

adapters allow us to pick the backend we want to test at any timewe don’t write tests to do a full integration test, we just flip a switch to turn on a different adapterthis allows us to iterate quickly on in memory mocks and run everything when the feature works

SECURITY

http://www.flickr.com/photos/northover/3022796561/

Tuesday, December 1, 2009

sessions are cookie sessions, so they’re difficult to expire on consumersthere is https, the consumer whitelist allows and communicates over https :)single sign out sucks because it requires a shared domain cookie

YAY, COOKIE SESSIONS

Tuesday, December 1, 2009

THE BUNDLERTuesday, December 1, 2009

how to deploy your new baby and ensure it’ll play well with othersthis is how we manage lots of apps dependencies in a repeatable mannerthink merb’s bundling that actually works.

THE BUNDLERTuesday, December 1, 2009

people can and will do crazy shit with rubygems.it’s available on github under the wycats usercarl lerche has done a tremendous job keeping all of the insanity in order

DEPLOYING AN APP

gem 'rack_hoptoad', '>=0.0.3'gem 'sinatra', '~>0.9.4', :require_as => 'sinatra/base'gem 'rest-client', :require_as => 'rest_client'gem 'json'gem 'dm-core'gem 'dm-validations'gem 'do_sqlite3'

only :test do gem 'rake' gem 'rspec', :require_as => %w(spec) gem 'rcov' gem 'bundler', '>=0.5.0' gem 'cucumber' gem 'webrat', '~>0.5.0' gem 'rack-test', '~>0.5.0', :require_as => 'rack/test' gem 'fakeweb', '>=1.2.5' gem 'ParseTree', '>=3.0.4', :require_as => 'parse_tree' gem 'randexp', '>=0.1.4'end

disable_system_gems

# vim:ft=ruby

Tuesday, December 1, 2009

specify the gems you need required at runtime in a global scopeall of your normal gem version hacks work as expectedawkward requires can be handled easily as an array or single string

BIN_PATH

gem 'sinatra', '~>0.9.0', :require_as => [ ]gem 'haml', '~>2.2.0', :require_as => [ ]gem 'do_sqlite3', '~>0.9.12', :require_as => [ ]gem 'dm-validations', '~>0.9.11', :require_as => [ ]gem 'dm-timestamps', '~>0.9.11', :require_as => [ ]gem 'dm-types', '~>0.9.11', :require_as => [ ]gem 'ruby-openid', '~>2.1.7', :require_as => [ ]gem 'guid', '~>0.1.1', :require_as => [ ]gem 'rack-contrib', '~>0.9.2', :require_as => [ ]gem 'json', :require_as => [ ]

only :test do gem 'rack-test', '~>0.5.0', :require_as => 'rack/test' gem 'webrat', '~>0.5.0' gem 'rspec', '~>1.2.9', :require_as => 'spec' gem 'rake' gem 'rcov' gem 'cucumber' gem 'dm-aggregates', '~>0.9.11' gem 'dm-sweatshop', '~>0.9.11' gem 'randexp' gem 'ParseTree', :require_as => 'parse_tree' gem 'bundler', '>=0.6.0'end

bin_path 'gbin'disable_system_gems

Tuesday, December 1, 2009

note the bin_path stuff, lots of gems distribute executables in bin, gbin stands for gem bindouble check how you’re bundling executables in your gems, you could easily overwrite your system rake.

DISABLE_SYSTEM_GEMS

gem 'sinatra', '~>0.9.0', :require_as => [ ]gem 'haml', '~>2.2.0', :require_as => [ ]gem 'do_sqlite3', '~>0.9.12', :require_as => [ ]gem 'dm-validations', '~>0.9.11', :require_as => [ ]gem 'dm-timestamps', '~>0.9.11', :require_as => [ ]gem 'dm-types', '~>0.9.11', :require_as => [ ]gem 'ruby-openid', '~>2.1.7', :require_as => [ ]gem 'guid', '~>0.1.1', :require_as => [ ]gem 'rack-contrib', '~>0.9.2', :require_as => [ ]gem 'json', :require_as => [ ]

only :test do gem 'rack-test', '~>0.5.0', :require_as => 'rack/test' gem 'webrat', '~>0.5.0' gem 'rspec', '~>1.2.9', :require_as => 'spec' gem 'rake' gem 'rcov' gem 'cucumber' gem 'dm-aggregates', '~>0.9.11' gem 'dm-sweatshop', '~>0.9.11' gem 'randexp' gem 'ParseTree', :require_as => 'parse_tree' gem 'bundler', '>=0.6.0'end

bin_path 'gbin'disable_system_gems

Tuesday, December 1, 2009

turns out to be very useful as there won’t be any system gem conflictskeeps each application nice, local and repeatably deployed.

SPEC/SPEC_HELPER.RB

Bundler.require_env(:test)require File.join(File.dirname(__FILE__), '..', 'lib', 'myapp')require 'pp'

DataMapper.setup(:default, 'sqlite3://:memory:')DataMapper.auto_migrate!

Spec::Runner.configure do |config| config.include(Rack::Test::Methods) def app MyApp.app endend

Tuesday, December 1, 2009

explicitly call Bundler.require_env(:test) to include that ‘only’ block from beforerequire your application and all is well.

CONFIG.RU

Bundler.require_envrequire File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'my_app'))

DataMapper.setup(:default, 'sqlite3://:memory:')DataMapper.auto_migrate!

run MyApp.app

Tuesday, December 1, 2009

using Bundler.require_env sets you up perfectlyit runs fine under passengerother executables should be used from the bin_path directorytring

DEPLOYING A GEM

gem 'sinatra', '~>0.9.0', :require_as => [ ]gem 'haml', '~>2.2.0', :require_as => [ ]gem 'do_sqlite3', '~>0.9.12', :require_as => [ ]gem 'dm-validations', '~>0.9.11', :require_as => [ ]gem 'dm-timestamps', '~>0.9.11', :require_as => [ ]gem 'dm-types', '~>0.9.11', :require_as => [ ]gem 'ruby-openid', '~>2.1.7', :require_as => [ ]gem 'guid', '~>0.1.1', :require_as => [ ]gem 'rack-contrib', '~>0.9.2', :require_as => [ ]gem 'json', :require_as => [ ]

only :test do gem 'rack-test', '~>0.5.0', :require_as => 'rack/test' gem 'webrat', '~>0.5.0' gem 'rspec', '~>1.2.9', :require_as => 'spec' gem 'rake' gem 'rcov' gem 'cucumber' gem 'dm-aggregates', '~>0.9.11' gem 'dm-sweatshop', '~>0.9.11' gem 'randexp' gem 'ParseTree', :require_as => 'parse_tree' gem 'bundler', '>=0.6.0'end

bin_path 'gbin'disable_system_gems

Tuesday, December 1, 2009

specify the gems you need required at runtime in a global scope but don’t require themyour application code should require them and cause bad deployments.

YOUR RAKE FILE

require 'rake/gempackagetask'require 'rubygems/specification'require 'date'require 'bundler'

spec = Gem::Specification.new do |s| ...

manifest = Bundler::Environment.load(File.dirname(__FILE__) + '/Gemfile') manifest.dependencies.each do |d| next if d.only && d.only.include?('test') s.add_dependency(d.name, d.version) endend

Tuesday, December 1, 2009

you can require the bundler and use your manifest file to gem dependenciesthis way you only maintain it in one spotyou could also do development dependencies if you’d like

IDENTITY

http://www.flickr.com/photos/dullhunk/3953605716/

Tuesday, December 1, 2009

lots of other realms of identity involved to really get it all right.

IDENTITY

http://www.flickr.com/photos/dullhunk/3953605716/

Tuesday, December 1, 2009

security + authentication + information

IDENTITY

http://www.xmlgrrl.com/blog/2009/10/02/a-venn-of-identity-in-web-services-now-with-oauth/

Tuesday, December 1, 2009

a slightly updated version uses oauth, we do a similar thing with the oauth provider.our next wave is to take advantage of

THANKS SO MUCH!

Tuesday, December 1, 2009

QUESTIONS?

COMMENTS?

Tuesday, December 1, 2009

Recommended