Authenticate against auth.clinicalfuture.com
authorWard Vandewege <ward@clinicalfuture.com>
Wed, 23 Jan 2013 04:18:53 +0000 (23:18 -0500)
committerWard Vandewege <ward@clinicalfuture.com>
Wed, 23 Jan 2013 04:18:53 +0000 (23:18 -0500)
23 files changed:
.gitignore
Gemfile
Gemfile.lock
app/assets/stylesheets/application.css
app/controllers/application_controller.rb
app/controllers/collections_controller.rb
app/controllers/nodes_controller.rb
app/controllers/pipeline_invocations_controller.rb
app/controllers/static_controller.rb [new file with mode: 0644]
app/controllers/user_sessions_controller.rb [new file with mode: 0644]
app/helpers/application_helper.rb
app/models/user.rb [new file with mode: 0644]
app/views/layouts/application.html.erb
app/views/static/intro.html.erb [new file with mode: 0644]
app/views/user_sessions/failure.html.erb [new file with mode: 0644]
config/environment.rb
config/routes.rb
db/migrate/20130122020042_create_users.rb [new file with mode: 0644]
db/schema.rb
lib/josh_id.rb [new file with mode: 0644]
public/index.html [deleted file]
test/fixtures/users.yml [new file with mode: 0644]
test/unit/user_test.rb [new file with mode: 0644]

index f61b15942ffc0f3ad578d55df46e829a9ad5f3b9..0b59422fd8ccb2e67c35ea330ac57f00a050d057 100644 (file)
@@ -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 a54ddf479ec09524f9cd651baa64eae484fc4112..9d4fc15a977314b065029678be4e30409a83b2eb 100644 (file)
--- 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'
index 9a1117e07b09742771228b7f0792d383d6249e59..11878f2df72727df5832c32d09be777db5ec3fef 100644 (file)
@@ -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)
index 8d93556acf0137d7bd5e04dfc29e8f78feba8395..4804e908c5e6688f10f3c1eb1fa2f17dc83d9212 100644 (file)
@@ -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;
+}
+
index c6ab2b50908432224e3783ed4fac0161382efc46..36f4629823379feda41c61c5654a42230b8ff71d 100644 (file)
@@ -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
index 1714f68c99c74dd3f8f09703b0266eea05c3d6b7..242b8b394dd7d31257979aad7b622dc4e44b640a 100644 (file)
@@ -1,4 +1,5 @@
 class CollectionsController < ApplicationController
+  skip_before_filter :authenticate_api_token
   def index
     @objects = model_class.order("created_at desc")
   end
index 5dce1b13cc5273bc8926d656cf67a48e5f83c5f9..ed5ffc0fab29e4a582cfdfaf30f177f857e361ce 100644 (file)
@@ -1,4 +1,5 @@
 class NodesController < ApplicationController
+  skip_before_filter :authenticate_api_token
   def index
     @objects = model_class.order("created_at desc")
   end
index de8b9eb6ce7072ef2970f1a1c4bd12b3949e32a8..fa8c68a894d104d3c90d507a81c03c991ca50048 100644 (file)
@@ -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 (file)
index 0000000..99cccff
--- /dev/null
@@ -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 (file)
index 0000000..45ea30c
--- /dev/null
@@ -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
index de6be7945c6a59798eb0ace177df38b05e98c2f0..df9ae848172e65c3414eade408210d09aaa23006 100644 (file)
@@ -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 (file)
index 0000000..6fc6814
--- /dev/null
@@ -0,0 +1,7 @@
+class User < ActiveRecord::Base
+
+  def full_name
+    "#{first_name} #{last_name}"
+  end
+
+end
index 487b6ac7f793eecaa9b429eb41751ce7d6c13f53..63bc7dc842288fe9b63f4f2125758c4f1def310a 100644 (file)
@@ -7,8 +7,39 @@
   <%= csrf_meta_tags %>
 </head>
 <body>
+<div id="header">
+  <div class="apptitle">ORVOS <span class="beta"><span>BETA</span></span></div>
+  <div style="float:right">
+    <% if current_user %>
+    <%= current_user.full_name %>
+    <% if current_user.is_admin %>
+    &nbsp;&bull;&nbsp;
+    <a class="logout" href="/admin/users">Admin</a>
+    <% end %>
+    &nbsp;&bull;&nbsp;
+    <a class="logout" href="/logout">Log out</a>
+    <% else %>
+    <a class="logout" href="/auth/joshid">Log in</a>
+    <% end %>
+
+    <% if current_user and session[:real_uid] and session[:switch_back_to] and User.find(session[:real_uid].to_i).verify_userswitch_cookie(session[:switch_back_to]) %>
+    &nbsp;&bull;&nbsp;
+    <span class="sudo-warning">Logged in as <b><%= current_user.full_name %></b>. <%= link_to "Back to #{User.find(session[:real_uid]).full_name}", switch_to_user_path(session[:real_uid]), :method => :post, :class => 'sudo-logout' %></span>
+    <% end %>
+  </div>
+</div>
+
 
 <%= yield %>
 
+<div style="clear:both"></div>
+
+<% if current_user or session['invite_code'] %>
+<div id="footer">
+  <div style="float:right">Questions &rarr; <a href="mailto:orvos@clinicalfuture.com">orvos@clinicalfuture.com</a></div>
+  <div style="clear:both"></div>
+</div>
+<% end %>
+
 </body>
 </html>
diff --git a/app/views/static/intro.html.erb b/app/views/static/intro.html.erb
new file mode 100644 (file)
index 0000000..22eb4d2
--- /dev/null
@@ -0,0 +1,38 @@
+<% content_for :js do %>
+$(function(){
+  $('button.login').button().click(function(){window.location=$(this).attr('href')});
+});
+<% end %>
+<div id="intropage">
+  <img class="clinicalfuture-logo" src="/images/logo.png" style="display:block; margin:2em auto"/>
+  <div style="width:30em; margin:2em auto 0 auto">
+    <h1>Welcome</h1>
+    <h4>Clinical Future ORVOS</h4>
+
+    <% if !current_user and session['invite_code'] %>
+
+    <p>Clinical Future Orvos lets you manage and process human genomes and exomes.  You can start using the private beta
+    now with your Google account.</p>
+    <p style="float:right;margin-top:1em">
+      <button class="login" href="/auth/joshid">Log in and get started</button>
+    </p>
+
+    <% else %>
+
+    <p>Clinical Future ORVOS is transforming how researchers and
+    clinical geneticists use whole genome sequences. </p>
+    <p>If you&rsquo;re interested in learning more, we&rsquo;d love to hear
+    from you &mdash;
+    contact <a href="mailto:orvos@clinicalfuture.com">orvos@clinicalfuture.com</a>.</p>
+
+    <% if !current_user %>
+    <p style="float:right;margin-top:1em">
+      <a href="/auth/joshid">Log in here.</a>
+    </p>
+    <% end %>
+
+    <% end %>
+
+    <div style="clear:both;height:8em"></div>
+  </div>
+</div>
diff --git a/app/views/user_sessions/failure.html.erb b/app/views/user_sessions/failure.html.erb
new file mode 100644 (file)
index 0000000..cdea96d
--- /dev/null
@@ -0,0 +1,6 @@
+<h1>Fail</h1>
+
+<%= notice %>
+
+<br/>
+<a href="/auth/joshid">Retry Login</a>
index 9c4e0a49933d52b166f01aef48a8cc1949ee9d19..402d79f574c5e1dcef3523fd93b7dc528f73be26 100644 (file)
@@ -1,5 +1,6 @@
 # Load the rails application
 require File.expand_path('../application', __FILE__)
+require 'josh_id'
 
 # Initialize the rails application
 Server::Application.initialize!
index 68f3a2ad12698779ad37099729970a56ed00b7c9..ef896f6af6ae4c63eb06393e4f1d5249bb00eec1 100644 (file)
@@ -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 (file)
index 0000000..61a60c5
--- /dev/null
@@ -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
index 9d25e7ee281545767b5cbb438622195b743b626d..3a64857324b161c12057b0f0f3620b9c77df48df 100644 (file)
@@ -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 (file)
index 0000000..cf28af9
--- /dev/null
@@ -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 (file)
index 9d9811a..0000000
+++ /dev/null
@@ -1,241 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <title>Ruby on Rails: Welcome aboard</title>
-    <style type="text/css" media="screen">
-      body {
-        margin: 0;
-        margin-bottom: 25px;
-        padding: 0;
-        background-color: #f0f0f0;
-        font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
-        font-size: 13px;
-        color: #333;
-      }
-
-      h1 {
-        font-size: 28px;
-        color: #000;
-      }
-
-      a  {color: #03c}
-      a:hover {
-        background-color: #03c;
-        color: white;
-        text-decoration: none;
-      }
-
-
-      #page {
-        background-color: #f0f0f0;
-        width: 750px;
-        margin: 0;
-        margin-left: auto;
-        margin-right: auto;
-      }
-
-      #content {
-        float: left;
-        background-color: white;
-        border: 3px solid #aaa;
-        border-top: none;
-        padding: 25px;
-        width: 500px;
-      }
-
-      #sidebar {
-        float: right;
-        width: 175px;
-      }
-
-      #footer {
-        clear: both;
-      }
-
-      #header, #about, #getting-started {
-        padding-left: 75px;
-        padding-right: 30px;
-      }
-
-
-      #header {
-        background-image: url("/assets/rails.png");
-        background-repeat: no-repeat;
-        background-position: top left;
-        height: 64px;
-      }
-      #header h1, #header h2 {margin: 0}
-      #header h2 {
-        color: #888;
-        font-weight: normal;
-        font-size: 16px;
-      }
-
-
-      #about h3 {
-        margin: 0;
-        margin-bottom: 10px;
-        font-size: 14px;
-      }
-
-      #about-content {
-        background-color: #ffd;
-        border: 1px solid #fc0;
-        margin-left: -55px;
-        margin-right: -10px;
-      }
-      #about-content table {
-        margin-top: 10px;
-        margin-bottom: 10px;
-        font-size: 11px;
-        border-collapse: collapse;
-      }
-      #about-content td {
-        padding: 10px;
-        padding-top: 3px;
-        padding-bottom: 3px;
-      }
-      #about-content td.name  {color: #555}
-      #about-content td.value {color: #000}
-
-      #about-content ul {
-        padding: 0;
-        list-style-type: none;
-      }
-
-      #about-content.failure {
-        background-color: #fcc;
-        border: 1px solid #f00;
-      }
-      #about-content.failure p {
-        margin: 0;
-        padding: 10px;
-      }
-
-
-      #getting-started {
-        border-top: 1px solid #ccc;
-        margin-top: 25px;
-        padding-top: 15px;
-      }
-      #getting-started h1 {
-        margin: 0;
-        font-size: 20px;
-      }
-      #getting-started h2 {
-        margin: 0;
-        font-size: 14px;
-        font-weight: normal;
-        color: #333;
-        margin-bottom: 25px;
-      }
-      #getting-started ol {
-        margin-left: 0;
-        padding-left: 0;
-      }
-      #getting-started li {
-        font-size: 18px;
-        color: #888;
-        margin-bottom: 25px;
-      }
-      #getting-started li h2 {
-        margin: 0;
-        font-weight: normal;
-        font-size: 18px;
-        color: #333;
-      }
-      #getting-started li p {
-        color: #555;
-        font-size: 13px;
-      }
-
-
-      #sidebar ul {
-        margin-left: 0;
-        padding-left: 0;
-      }
-      #sidebar ul h3 {
-        margin-top: 25px;
-        font-size: 16px;
-        padding-bottom: 10px;
-        border-bottom: 1px solid #ccc;
-      }
-      #sidebar li {
-        list-style-type: none;
-      }
-      #sidebar ul.links li {
-        margin-bottom: 5px;
-      }
-
-      .filename {
-        font-style: italic;
-      }
-    </style>
-    <script type="text/javascript">
-      function about() {
-        info = document.getElementById('about-content');
-        if (window.XMLHttpRequest)
-          { xhr = new XMLHttpRequest(); }
-        else
-          { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
-        xhr.open("GET","rails/info/properties",false);
-        xhr.send("");
-        info.innerHTML = xhr.responseText;
-        info.style.display = 'block'
-      }
-    </script>
-  </head>
-  <body>
-    <div id="page">
-      <div id="sidebar">
-        <ul id="sidebar-items">
-          <li>
-            <h3>Browse the documentation</h3>
-            <ul class="links">
-              <li><a href="http://guides.rubyonrails.org/">Rails Guides</a></li>
-              <li><a href="http://api.rubyonrails.org/">Rails API</a></li>
-              <li><a href="http://www.ruby-doc.org/core/">Ruby core</a></li>
-              <li><a href="http://www.ruby-doc.org/stdlib/">Ruby standard library</a></li>
-            </ul>
-          </li>
-        </ul>
-      </div>
-
-      <div id="content">
-        <div id="header">
-          <h1>Welcome aboard</h1>
-          <h2>You&rsquo;re riding Ruby on Rails!</h2>
-        </div>
-
-        <div id="about">
-          <h3><a href="rails/info/properties" onclick="about(); return false">About your application&rsquo;s environment</a></h3>
-          <div id="about-content" style="display: none"></div>
-        </div>
-
-        <div id="getting-started">
-          <h1>Getting started</h1>
-          <h2>Here&rsquo;s how to get rolling:</h2>
-
-          <ol>
-            <li>
-              <h2>Use <code>rails generate</code> to create your models and controllers</h2>
-              <p>To see all available options, run it without parameters.</p>
-            </li>
-
-            <li>
-              <h2>Set up a default route and remove <span class="filename">public/index.html</span></h2>
-              <p>Routes are set up in <span class="filename">config/routes.rb</span>.</p>
-            </li>
-
-            <li>
-              <h2>Create your database</h2>
-              <p>Run <code>rake db:create</code> to create your database. If you're not using SQLite (the default), edit <span class="filename">config/database.yml</span> with your username and password.</p>
-            </li>
-          </ol>
-        </div>
-      </div>
-
-      <div id="footer">&nbsp;</div>
-    </div>
-  </body>
-</html>
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
new file mode 100644 (file)
index 0000000..d39ebae
--- /dev/null
@@ -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 (file)
index 0000000..82f61e0
--- /dev/null
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class UserTest < ActiveSupport::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end