From ee367f165db323da5012eb22ab62a32f1512f37c Mon Sep 17 00:00:00 2001 From: Ward Vandewege Date: Tue, 22 Jan 2013 23:18:53 -0500 Subject: [PATCH] Authenticate against auth.clinicalfuture.com --- .gitignore | 1 + Gemfile | 3 + Gemfile.lock | 21 ++ app/assets/stylesheets/application.css | 168 ++++++++++++ app/controllers/application_controller.rb | 92 +++++++ app/controllers/collections_controller.rb | 1 + app/controllers/nodes_controller.rb | 1 + .../pipeline_invocations_controller.rb | 1 + app/controllers/static_controller.rb | 12 + app/controllers/user_sessions_controller.rb | 72 ++++++ app/helpers/application_helper.rb | 5 + app/models/user.rb | 7 + app/views/layouts/application.html.erb | 31 +++ app/views/static/intro.html.erb | 38 +++ app/views/user_sessions/failure.html.erb | 6 + config/environment.rb | 1 + config/routes.rb | 10 + db/migrate/20130122020042_create_users.rb | 21 ++ db/schema.rb | 17 ++ lib/josh_id.rb | 37 +++ public/index.html | 241 ------------------ test/fixtures/users.yml | 31 +++ test/unit/user_test.rb | 7 + 23 files changed, 583 insertions(+), 241 deletions(-) create mode 100644 app/controllers/static_controller.rb create mode 100644 app/controllers/user_sessions_controller.rb create mode 100644 app/models/user.rb create mode 100644 app/views/static/intro.html.erb create mode 100644 app/views/user_sessions/failure.html.erb create mode 100644 db/migrate/20130122020042_create_users.rb create mode 100644 lib/josh_id.rb delete mode 100644 public/index.html create mode 100644 test/fixtures/users.yml create mode 100644 test/unit/user_test.rb diff --git a/.gitignore b/.gitignore index f61b15942f..0b59422fd8 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ # Some sensitive files /config/api.clinicalfuture.com.* /config/database.yml +/config/initializers/omniauth.rb # asset cache /public/assets/ diff --git a/Gemfile b/Gemfile index a54ddf479e..9d4fc15a97 100644 --- a/Gemfile +++ b/Gemfile @@ -41,3 +41,6 @@ gem 'rvm-capistrano', :group => :test gem 'acts_as_api' gem 'passenger', :group => :production + +gem 'omniauth', '1.1.1' +gem 'omniauth-oauth2', '1.1.1' diff --git a/Gemfile.lock b/Gemfile.lock index 9a1117e07b..11878f2df7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,14 +52,20 @@ GEM erubis (2.7.0) execjs (1.4.0) multi_json (~> 1.0) + faraday (0.8.4) + multipart-post (~> 1.1) fastthread (1.0.7) + hashie (1.2.0) highline (1.6.15) hike (1.2.1) + httpauth (0.2.0) i18n (0.6.1) jquery-rails (2.1.4) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.7.6) + jwt (0.1.5) + multi_json (>= 1.0) libv8 (3.11.8.13) mail (2.3.3) i18n (>= 0.4.0) @@ -67,6 +73,7 @@ GEM treetop (~> 1.4.8) mime-types (1.19) multi_json (1.2.0) + multipart-post (1.1.5) net-scp (1.0.4) net-ssh (>= 1.99.1) net-sftp (2.0.5) @@ -74,6 +81,18 @@ GEM net-ssh (2.6.3) net-ssh-gateway (1.1.0) net-ssh (>= 1.99.1) + oauth2 (0.8.0) + faraday (~> 0.8) + httpauth (~> 0.1) + jwt (~> 0.1.4) + multi_json (~> 1.0) + rack (~> 1.2) + omniauth (1.1.1) + hashie (~> 1.2) + rack + omniauth-oauth2 (1.1.1) + oauth2 (~> 0.8.0) + omniauth (~> 1.0) passenger (3.0.19) daemon_controller (>= 1.0.0) fastthread (>= 1.0.1) @@ -142,6 +161,8 @@ DEPENDENCIES coffee-rails (~> 3.1.1) jquery-rails json + omniauth (= 1.1.1) + omniauth-oauth2 (= 1.1.1) passenger pg rails (= 3.1.10) diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 8d93556acf..4804e908c5 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -9,3 +9,171 @@ .contain-align-left { text-align: left; } + +body { + margin: 0; +} +body > div { + margin: 2px; +} +div#footer { + font-family: Verdana,Arial,sans-serif; + font-size: 12px; + margin-top: 24px; + border-top: 1px solid #ccc; +} +div#footer, div#footer a { + color: #777; +} +div#header { + margin: 0; + padding: .5em 1em; + background: #000; + font-weight: bold; + font-size: 18px; + font-family: Verdana,Arial,sans-serif; + vertical-align: middle; + color: #ddd; +} +div#header > div { + display: inline-block; + font-size: 12px; + line-height: 18px; +} +div#header > .apptitle { + font-size: 18px; +} +div#header a.logout { + color: #fff; + font-weight: normal; +} +div#header button { + font-size: 12px; +} +div#header span.beta { + opacity: 0.5; +} +div#header span.beta > span { + border-top: 1px solid #fff; + border-bottom: 1px solid #fff; + font-size: 0.8em; +} +img.clinicalfuture-logo { + width: 221px; + height: 44px; +} +#intropage { + font-family: Verdana,Arial,sans-serif; +} +#errorpage { + font-family: Verdana,Arial,sans-serif; +} + +div.full-page-tab-set > ul > li { + font-size: 14px; +} +.titlebanner p { + font-size: 16px; +} +p { + font-size: 12px; +} +.small-text { + font-size: 12px; +} +.autoui-icon-float-left { + float: left; + margin-right: .3em; +} +.autoui-pad { + padding: 0 1em; +} +table.datatablesme { + border: 0; + border-collapse: collapse; + width: 100%; +} +.loadinggif { + background: #fff url(/images/ajax-loader-16-fff-aad.gif) no-repeat; +} +.clientprogressgif { + /* warning: depends on 24px outer container. */ + position: absolute; + left: 4px; + top: 4px; + width: 16px; + height: 16px; +} +.counttable { + width: 100%; + display: table; + border-collapse: collapse; + margin-bottom: 0.5em; +} +.counttable > div { + display: table-row; +} +.counttable > div > div { + display: table-cell; + text-align: center; + background: #ccf; + padding: 0 2px; + font-size: 0.8em; +} +.counttable > div > div.counter { + font-size: 2em; + padding: 4px 2px 0 2px; +} +table.admin_table { + border-collapse: collapse; +} +table.admin_table tbody tr { + height: 2.5em; +} +table.admin_table th,table.admin_table td { + text-align: left; + border: 1px solid #bbb; + padding: 3px; +} +table.admin_table tbody tr:hover { + background: #ff8; +} +table.admin_table tbody tr:hover td { + background: transparent; +} + +div.helptopics { + position: fixed; +} +div.helptopics ul { + padding: 0; + margin-left: 1em; + list-style-type: none; +} +div.helptopics ul li { + margin: 0 0 1em 0; +} +div.helpcontent li { + margin-bottom: .5em; +} + +div.preview { + color: red; + font-weight: bold; + text-align: center; +} + +.sudo-warning { + padding: 4px 10px; + background: #ffdd00; + color: red; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px +} + +div#header a.sudo-logout { + color: #000; + font-weight: bold; +} + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index c6ab2b5090..36f4629823 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,6 +4,85 @@ class ApplicationController < ActionController::Base before_filter :find_object_by_uuid, :except => :index before_filter :authenticate_api_token + before_filter :set_remote_ip + before_filter :login_required + + before_filter :catch_redirect_hint + + def catch_redirect_hint + if !current_user + if params.has_key?('redirect_to') then + session[:redirect_to] = params[:redirect_to] + end + end + end + + # Authentication + def login_required + if !current_user + respond_to do |format| + format.html { + redirect_to '/auth/joshid' + } + format.json { + render :json => { 'error' => 'Not logged in' }.to_json + } + end + end + end + + def current_user + return nil unless session[:user_id] + @current_user ||= User.find(session[:user_id]) rescue nil + end + # /Authentication + + before_filter :set_remote_ip + before_filter :login_required + + # Authentication + def login_required + if !current_user + respond_to do |format| + format.html { + redirect_to '/auth/joshid' + } + format.json { + render :json => { 'error' => 'Not logged in' }.to_json + } + end + end + end + + def current_user + return nil unless session[:user_id] + @current_user ||= User.find(session[:user_id]) rescue nil + end + # /Authentication + + before_filter :set_remote_ip + before_filter :login_required + + # Authentication + def login_required + if !current_user + respond_to do |format| + format.html { + redirect_to '/auth/joshid' + } + format.json { + render :json => { 'error' => 'Not logged in' }.to_json + } + end + end + end + + def current_user + return nil unless session[:user_id] + @current_user ||= User.find(session[:user_id]) rescue nil + end + # /Authentication + unless Rails.application.config.consider_all_requests_local rescue_from Exception, :with => :render_error @@ -163,4 +242,17 @@ class ApplicationController < ActionController::Base render_error(Exception.new("Invalid API token")) end end + +private + def set_remote_ip + # Caveat: this is highly dependent on the proxy setup. YMMV. + if request.headers.has_key?('HTTP_X_REAL_IP') then + # We're behind a reverse proxy + @remote_ip = request.headers['HTTP_X_REAL_IP'] + else + # Hopefully, we are not! + @remote_ip = request.env['REMOTE_ADDR'] + end + end + end diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb index 1714f68c99..242b8b394d 100644 --- a/app/controllers/collections_controller.rb +++ b/app/controllers/collections_controller.rb @@ -1,4 +1,5 @@ class CollectionsController < ApplicationController + skip_before_filter :authenticate_api_token def index @objects = model_class.order("created_at desc") end diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb index 5dce1b13cc..ed5ffc0fab 100644 --- a/app/controllers/nodes_controller.rb +++ b/app/controllers/nodes_controller.rb @@ -1,4 +1,5 @@ class NodesController < ApplicationController + skip_before_filter :authenticate_api_token def index @objects = model_class.order("created_at desc") end diff --git a/app/controllers/pipeline_invocations_controller.rb b/app/controllers/pipeline_invocations_controller.rb index de8b9eb6ce..fa8c68a894 100644 --- a/app/controllers/pipeline_invocations_controller.rb +++ b/app/controllers/pipeline_invocations_controller.rb @@ -1,4 +1,5 @@ class PipelineInvocationsController < ApplicationController + skip_before_filter :authenticate_api_token def index @objects = model_class.order("created_at desc") end diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb new file mode 100644 index 0000000000..99cccffabb --- /dev/null +++ b/app/controllers/static_controller.rb @@ -0,0 +1,12 @@ +class StaticController < ApplicationController + + skip_before_filter :uncamelcase_params_hash_keys + skip_before_filter :find_object_by_uuid + skip_before_filter :authenticate_api_token + skip_before_filter :login_required, :only => :home + + def home + render 'intro' + end + +end diff --git a/app/controllers/user_sessions_controller.rb b/app/controllers/user_sessions_controller.rb new file mode 100644 index 0000000000..45ea30c4e5 --- /dev/null +++ b/app/controllers/user_sessions_controller.rb @@ -0,0 +1,72 @@ +class UserSessionsController < ApplicationController + before_filter :login_required, :only => [ :destroy ] + + skip_before_filter :uncamelcase_params_hash_keys + skip_before_filter :find_object_by_uuid + skip_before_filter :authenticate_api_token + + respond_to :html + + # omniauth callback method + def create + omniauth = env['omniauth.auth'] + #logger.debug "+++ #{omniauth}" + + identity_url_ok = (omniauth['info']['identity_url'].length > 0) rescue false + unless identity_url_ok + # Whoa. This should never happen. + + @title = "UserSessionsController.create: omniauth object missing/invalid" + @body = "omniauth.pretty_inspect():\n\n#{omniauth.pretty_inspect()}" + + view_context.fatal_error(@title,@body) + return redirect_to openid_login_error_url + end + + user = User.find_by_identity_url(omniauth['info']['identity_url']) + if not user + # New user registration + user = User.create!(:email => omniauth['info']['email'], + :first_name => omniauth['info']['first_name'], + :last_name => omniauth['info']['last_name'], + :identity_url => omniauth['info']['identity_url']) + else + user.email = omniauth['info']['email'] + user.first_name = omniauth['info']['first_name'] + user.last_name = omniauth['info']['last_name'] + user.save + end + + omniauth.delete('extra') + + session[:user_id] = user.id + + @redirect_to = root_path + if session.has_key?('redirect_to') then + @redirect_to = session[:redirect_to] + session.delete(:redirect_to) + end + redirect_to @redirect_to + end + + # Omniauth failure callback + def failure + flash[:notice] = params[:message] + end + + # logout - Clear our rack session BUT essentially redirect to the provider + # to clean up the Devise session from there too ! + def logout + session[:user_id] = nil + + flash[:notice] = 'You have logged off' + redirect_to "#{CUSTOM_PROVIDER_URL}/users/sign_out?redirect_uri=#{root_url}" + end + + # login - Just bounce to /auth/joshid. The only purpose of this function is + # to save the redirect_to parameter (if it exists; see the application + # controller). /auth/joshid bypasses the application controller. + def login + redirect_to "/auth/joshid" + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be7945c..df9ae84817 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,7 @@ module ApplicationHelper + + def current_user + controller.current_user + end + end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000000..6fc6814ffc --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,7 @@ +class User < ActiveRecord::Base + + def full_name + "#{first_name} #{last_name}" + end + +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 487b6ac7f7..63bc7dc842 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -7,8 +7,39 @@ <%= csrf_meta_tags %> + + <%= yield %> +
+ +<% if current_user or session['invite_code'] %> + +<% end %> + diff --git a/app/views/static/intro.html.erb b/app/views/static/intro.html.erb new file mode 100644 index 0000000000..22eb4d2a4a --- /dev/null +++ b/app/views/static/intro.html.erb @@ -0,0 +1,38 @@ +<% content_for :js do %> +$(function(){ + $('button.login').button().click(function(){window.location=$(this).attr('href')}); +}); +<% end %> +
+ +
+

Welcome

+

Clinical Future ORVOS

+ + <% if !current_user and session['invite_code'] %> + +

Clinical Future Orvos lets you manage and process human genomes and exomes. You can start using the private beta + now with your Google account.

+

+ +

+ + <% else %> + +

Clinical Future ORVOS is transforming how researchers and + clinical geneticists use whole genome sequences.

+

If you’re interested in learning more, we’d love to hear + from you — + contact orvos@clinicalfuture.com.

+ + <% if !current_user %> +

+ Log in here. +

+ <% end %> + + <% end %> + +
+
+
diff --git a/app/views/user_sessions/failure.html.erb b/app/views/user_sessions/failure.html.erb new file mode 100644 index 0000000000..cdea96d440 --- /dev/null +++ b/app/views/user_sessions/failure.html.erb @@ -0,0 +1,6 @@ +

Fail

+ +<%= notice %> + +
+Retry Login diff --git a/config/environment.rb b/config/environment.rb index 9c4e0a4993..402d79f574 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,6 @@ # Load the rails application require File.expand_path('../application', __FILE__) +require 'josh_id' # Initialize the rails application Server::Application.initialize! diff --git a/config/routes.rb b/config/routes.rb index 68f3a2ad12..ef896f6af6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -82,7 +82,17 @@ Server::Application.routes.draw do end end + # omniauth + match '/auth/:provider/callback', :to => 'user_sessions#create' + match '/auth/failure', :to => 'user_sessions#failure' + + # Custom logout + match '/login', :to => 'user_sessions#login' + match '/logout', :to => 'user_sessions#logout' + # Send unroutable requests to an arbitrary controller # (ends up at ApplicationController#render_not_found) match '*a', :to => 'orvos/v1/metadata#render_not_found' + + root :to => 'static#home' end diff --git a/db/migrate/20130122020042_create_users.rb b/db/migrate/20130122020042_create_users.rb new file mode 100644 index 0000000000..61a60c5f7e --- /dev/null +++ b/db/migrate/20130122020042_create_users.rb @@ -0,0 +1,21 @@ +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.string :uuid + t.string :created_by_client + t.string :created_by_user + t.datetime :created_at + t.string :modified_by_client + t.string :modified_by_user + t.datetime :modified_at + t.string :email + t.string :first_name + t.string :last_name + t.string :identity_url + t.boolean :is_admin + t.text :prefs + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 9d25e7ee28..3a64857324 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -167,4 +167,21 @@ ActiveRecord::Schema.define(:version => 20130122221616) do add_index "specimens", ["uuid"], :name => "index_specimens_on_uuid", :unique => true + create_table "users", :force => true do |t| + t.string "uuid" + t.string "created_by_client" + t.string "created_by_user" + t.datetime "created_at" + t.string "modified_by_client" + t.string "modified_by_user" + t.datetime "modified_at" + t.string "email" + t.string "first_name" + t.string "last_name" + t.string "identity_url" + t.boolean "is_admin" + t.text "prefs" + t.datetime "updated_at" + end + end diff --git a/lib/josh_id.rb b/lib/josh_id.rb new file mode 100644 index 0000000000..cf28af9c62 --- /dev/null +++ b/lib/josh_id.rb @@ -0,0 +1,37 @@ +require 'omniauth-oauth2' +module OmniAuth + module Strategies + class JoshId < OmniAuth::Strategies::OAuth2 + + CUSTOM_PROVIDER_URL = 'http://auth.clinicalfuture.com' + #CUSTOM_PROVIDER_URL = 'http://auth.clinicalfuture.com:3001' + + option :client_options, { + :site => CUSTOM_PROVIDER_URL, + :authorize_url => "#{CUSTOM_PROVIDER_URL}/auth/josh_id/authorize", + :access_token_url => "#{CUSTOM_PROVIDER_URL}/auth/josh_id/access_token" + } + + uid { raw_info['id'] } + + info do + { + :first_name => raw_info['info']['first_name'], + :last_name => raw_info['info']['last_name'], + :email => raw_info['info']['email'], + :identity_url => raw_info['info']['identity_url'], + } + end + + extra do + { + 'raw_info' => raw_info + } + end + + def raw_info + @raw_info ||= access_token.get("/auth/josh_id/user.json?oauth_token=#{access_token.token}").parsed + end + end + end +end diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 9d9811a5bf..0000000000 --- a/public/index.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - Ruby on Rails: Welcome aboard - - - - -
- - -
- - - - -
-

Getting started

-

Here’s how to get rolling:

- -
    -
  1. -

    Use rails generate to create your models and controllers

    -

    To see all available options, run it without parameters.

    -
  2. - -
  3. -

    Set up a default route and remove public/index.html

    -

    Routes are set up in config/routes.rb.

    -
  4. - -
  5. -

    Create your database

    -

    Run rake db:create to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.

    -
  6. -
-
-
- - -
- - diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml new file mode 100644 index 0000000000..d39ebae65c --- /dev/null +++ b/test/fixtures/users.yml @@ -0,0 +1,31 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html + +one: + uuid: MyString + created_by_client: MyString + created_by_user: MyString + created_at: 2013-01-21 21:00:42 + modified_by_client: MyString + modified_by_user: MyString + modified_at: 2013-01-21 21:00:42 + email: MyString + first_name: MyString + last_name: MyString + identity_url: MyString + is_admin: false + prefs: MyText + +two: + uuid: MyString + created_by_client: MyString + created_by_user: MyString + created_at: 2013-01-21 21:00:42 + modified_by_client: MyString + modified_by_user: MyString + modified_at: 2013-01-21 21:00:42 + email: MyString + first_name: MyString + last_name: MyString + identity_url: MyString + is_admin: false + prefs: MyText diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb new file mode 100644 index 0000000000..82f61e0109 --- /dev/null +++ b/test/unit/user_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class UserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end -- 2.30.2