diff --git a/Gemfile b/Gemfile index 8cd4bd8f..601dd204 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,11 @@ gem 'haml', '~> 3.0.18' gem 'rdiscount', '~> 1.6.5' gem 'RocketAMF', '~> 0.2.1' gem 'will_paginate', '~> 3.0.pre2' +gem 'warden', '~> 1.0.1' +gem 'rails_warden', '~> 0.5.2' + +gem 'msgpack', '~> 0.4.3' +gem 'openneo-auth-signatory', '~> 0.0.4' gem 'jammit', '~> 0.5.3' diff --git a/Gemfile.lock b/Gemfile.lock index 3310f168..bf80d8cb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -87,6 +87,9 @@ GEM mime-types treetop (>= 1.4.5) mime-types (1.16) + msgpack (0.4.3) + openneo-auth-signatory (0.0.4) + ruby-hmac polyglot (0.3.1) rack (1.2.1) rack-fiber_pool (0.9.0) @@ -102,6 +105,8 @@ GEM activesupport (= 3.0.0) bundler (~> 1.0.0) railties (= 3.0.0) + rails_warden (0.5.2) + warden railties (3.0.0) actionpack (= 3.0.0) activesupport (= 3.0.0) @@ -121,10 +126,13 @@ GEM rspec-expectations (= 2.0.0.beta.22) rspec-rails (2.0.0.beta.22) rspec (= 2.0.0.beta.22) + ruby-hmac (0.4.0) thor (0.14.2) treetop (1.4.8) polyglot (>= 0.3.1) tzinfo (0.3.23) + warden (1.0.1) + rack (>= 1.0.0) will_paginate (3.0.pre2) yui-compressor (0.9.1) @@ -142,9 +150,13 @@ DEPENDENCIES factory_girl_rails (~> 1.0) haml (~> 3.0.18) jammit (~> 0.5.3) + msgpack (~> 0.4.3) mysqlplus! + openneo-auth-signatory (~> 0.0.4) rack-fiber_pool rails (= 3.0.0) + rails_warden (~> 0.5.2) rdiscount (~> 1.6.5) rspec-rails (~> 2.0.0.beta.22) + warden (~> 1.0.1) will_paginate (~> 3.0.pre2) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e8065d95..6a7cd602 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,19 @@ class ApplicationController < ActionController::Base protect_from_forgery + + helper_method :current_user, :user_signed_in? + + protected + + def current_user + @current_user ||= warden.authenticate + end + + def user_signed_in? + current_user ? true : false + end + + def warden + env['warden'] + end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 00000000..3e234965 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,32 @@ +class SessionsController < ApplicationController + rescue_from Openneo::Auth::Session::InvalidSignature, :with => :invalid_signature + rescue_from Openneo::Auth::Session::MissingParam, :with => :missing_param + + skip_before_filter :verify_authenticity_token, :only => [:create] + + def new + redirect_to Openneo::Auth.remote_auth_url(params, session) + end + + def create + session = Openneo::Auth::Session.from_params(params) + session.save! + render :text => 'Success' + end + + def destroy + warden.logout + redirect_to root_path + end + + protected + + def invalid_signature(exception) + render :text => "Signature did not match. Check secret.", + :status => :unprocessable_entity + end + + def missing_param(exception) + render :text => exception.message, :status => :unprocessable_entity + end +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 00000000..f77ec893 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,15 @@ +class User < ActiveRecord::Base + DefaultAuthServerId = 1 + + def self.find_or_create_from_remote_auth_data(user_data) + user = find_or_initialize_by_remote_id_and_auth_server_id( + user_data['id'], + DefaultAuthServerId + ) + if user.new_record? + user.name = user_data['name'] + user.save + end + user + end +end diff --git a/app/views/layouts/items.html.haml b/app/views/layouts/items.html.haml index 41c92535..31bf6af1 100644 --- a/app/views/layouts/items.html.haml +++ b/app/views/layouts/items.html.haml @@ -7,6 +7,13 @@ = javascript_include_tag "http://#{RemoteImpressHost}/assets/js/analytics.js" %body{:class => params[:action]} #container + - if user_signed_in? + You are logged in as + = current_user.name + == (#{current_user.id}) + = link_to 'Log out', logout_path + - else + = link_to 'Log in', login_path %h1 = link_to items_path do = image_tag 'http://images.neopets.com/items/mall_floatingneggfaerie.gif' diff --git a/config/environments/development.rb b/config/environments/development.rb index 3a320a36..fdce68a7 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -21,3 +21,8 @@ OpenneoImpressItems::Application.configure do end RemoteImpressHost = 'beta.impress.openneo.net' +OPENNEO_AUTH_CONFIG = { + :app => 'beta.items.impress', + :auth_server => 'openneo-id', + :secret => 'zaheh2draswAb8eneca$3an?2ADAsTuwra8h7BujUBr_w--p2-a@e?u!taQux3tr' +} diff --git a/config/environments/development_async.rb b/config/environments/development_async.rb index 6f7f8c49..9b82847a 100644 --- a/config/environments/development_async.rb +++ b/config/environments/development_async.rb @@ -25,3 +25,9 @@ end RemoteImpressHost = 'beta.impress.openneo.net' USE_FIBER_POOL = true + +OPENNEO_AUTH_CONFIG = { + :app => 'beta.items.impress', + :auth_server => 'beta.id.openneo.net', + :secret => 'zaheh2draswAb8eneca$3an?2ADAsTuwra8h7BujUBr_w--p2-a@e?u!taQux3tr' +} diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb new file mode 100644 index 00000000..e1bf1ddc --- /dev/null +++ b/config/initializers/warden.rb @@ -0,0 +1,16 @@ +Rails.configuration.middleware.use RailsWarden::Manager do |manager| + manager.default_strategies :openneo_auth_token, :openneo_auth_redirect + manager.failure_app = SessionsController.action(:failure) +end + +require 'openneo-auth' + +Openneo::Auth.configure do |config| + OPENNEO_AUTH_CONFIG.each do |key, value| + config.send("#{key}=", value) + end + + config.user_finder do |user_data| + User.find_or_create_from_remote_auth_data(user_data) + end +end diff --git a/config/routes.rb b/config/routes.rb index 05aaa14e..b3ff211e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ OpenneoImpressItems::Application.routes.draw do |map| + root :to => 'items#index' match '/' => 'items#index', :as => :items match '/index.js' => 'items#index', :format => :js match '/items.json' => 'items#index', :format => :json @@ -18,5 +19,9 @@ OpenneoImpressItems::Application.routes.draw do |map| resources :pet_attributes, :only => [:index] resources :pets, :only => [:show] + match '/login' => 'sessions#new', :as => :login + match '/logout' => 'sessions#destroy', :as => :logout + match '/users/authorize' => 'sessions#create' + match '/wardrobe' => 'outfits#edit', :as => :wardrobe end diff --git a/lib/openneo-auth.rb b/lib/openneo-auth.rb new file mode 100644 index 00000000..473004cc --- /dev/null +++ b/lib/openneo-auth.rb @@ -0,0 +1,47 @@ +require 'openneo-auth/session' +require 'openneo-auth/strategy' + +Warden::Strategies.add :openneo_auth_token, Openneo::Auth::Strategy + +module Openneo + module Auth + class Config + attr_accessor :app, :auth_server, :secret + + def find_user(data) + raise "Must set a user finder for Openneo Auth to find a user" unless @user_finder + @user_finder.call(data) + end + + def user_finder(&block) + @user_finder = block + end + end + + class << self + def config + @@config ||= Config.new + end + + def configure(&block) + block.call(config) + end + + def remote_auth_url(params, session) + raise "Must set config.app to this app's subdomain" unless config.app + raise "Must set config.auth_server to remote server's hostname" unless config.auth_server + query = { + :app => config.app, + :session_id => session[:session_id], + :path => params[:return_to] || '/' + }.to_query + uri = URI::HTTP.build({ + :host => config.auth_server, + :path => '/', + :query => query + }) + uri.to_s + end + end + end +end diff --git a/lib/openneo-auth/session.rb b/lib/openneo-auth/session.rb new file mode 100644 index 00000000..c4f81998 --- /dev/null +++ b/lib/openneo-auth/session.rb @@ -0,0 +1,84 @@ +require 'active_support/core_ext/hash' +require 'msgpack' +require 'openneo-auth-signatory' + +module Openneo + module Auth + class Session + REMOTE_MSG_KEYS = %w(session_id source user) + TMP_STORAGE_DIR = Rails.root.join('tmp', 'openneo-auth-sessions') + + attr_writer :id + + def save! + FileUtils.mkdir_p TMP_STORAGE_DIR + File.open(tmp_storage_path, 'w') do |file| + file.write MessagePack.pack(@message).force_encoding('utf-8') + end + end + + def destroy! + File.delete(tmp_storage_path) + end + + def load_message! + raise NotFound, "Session #{id} not found" unless File.exists?(tmp_storage_path) + @message = File.open(tmp_storage_path, 'r') do |file| + MessagePack.unpack file.read + end + end + + def params=(params) + unless Auth.config.secret + raise "Must set config.secret to the remote auth server's secret" + end + given_signature = params['signature'] + signatory = Auth::Signatory.new(Auth.config.secret.force_encoding('utf-8')) + REMOTE_MSG_KEYS.each do |key| + unless params.include?(key) + raise MissingParam, "Missing required param #{key.inspect}" + end + end + @message = params.slice(*REMOTE_MSG_KEYS) + correct_signature = signatory.sign(@message) + unless given_signature == correct_signature + raise InvalidSignature, "Signature (#{given_signature}) " + + "did not match message #{@message.inspect} (#{correct_signature})" + end + end + + def user + Auth.config.find_user(@message['user']) + end + + def self.from_params(params) + session = new + session.params = params + session + end + + def self.find(id) + session = new + session.id = id + session.load_message! + session + end + + private + + def id + @id ||= @message[:session_id] + end + + def tmp_storage_path + name = "#{id}.mpac" + File.join TMP_STORAGE_DIR, name + end + + class InvalidSession < ArgumentError;end + class InvalidSignature < InvalidSession;end + class MissingParam < InvalidSession;end + class NotFound < StandardError;end + end + end +end diff --git a/lib/openneo-auth/strategy.rb b/lib/openneo-auth/strategy.rb new file mode 100644 index 00000000..8cd882bb --- /dev/null +++ b/lib/openneo-auth/strategy.rb @@ -0,0 +1,18 @@ +require 'warden' + +module Openneo + module Auth + class Strategy < Warden::Strategies::Base + def authenticate! + begin + auth_session = Session.find session[:session_id] + rescue Session::NotFound => e + fail! e.message + else + auth_session.destroy! + success! auth_session.user + end + end + end + end +end diff --git a/vendor/cache/msgpack-0.4.3.gem b/vendor/cache/msgpack-0.4.3.gem new file mode 100644 index 00000000..03bc9d6e Binary files /dev/null and b/vendor/cache/msgpack-0.4.3.gem differ diff --git a/vendor/cache/openneo-auth-signatory-0.0.4.gem b/vendor/cache/openneo-auth-signatory-0.0.4.gem new file mode 100644 index 00000000..f759b9dd Binary files /dev/null and b/vendor/cache/openneo-auth-signatory-0.0.4.gem differ diff --git a/vendor/cache/rails_warden-0.5.2.gem b/vendor/cache/rails_warden-0.5.2.gem new file mode 100644 index 00000000..373c1902 Binary files /dev/null and b/vendor/cache/rails_warden-0.5.2.gem differ diff --git a/vendor/cache/ruby-hmac-0.4.0.gem b/vendor/cache/ruby-hmac-0.4.0.gem new file mode 100644 index 00000000..5d070197 Binary files /dev/null and b/vendor/cache/ruby-hmac-0.4.0.gem differ diff --git a/vendor/cache/warden-1.0.1.gem b/vendor/cache/warden-1.0.1.gem new file mode 100644 index 00000000..727afe7c Binary files /dev/null and b/vendor/cache/warden-1.0.1.gem differ