--- /dev/null
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
--- /dev/null
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
--- /dev/null
+// Place all the styles related to the ApiClientAuthorizations controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
--- /dev/null
+// Place all the styles related to the ApiClients controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
--- /dev/null
+class ApiClientAuthorizationsController < ApplicationController
+ def index
+ if Thread.current[:api_client_trusted]
+ @objects = model_class.
+ joins(:user, :api_client).
+ where('user_id=?', current_user.id)
+ else
+ @objects = model_class.where('1=0')
+ end
+ end
+end
--- /dev/null
+class ApiClientsController < ApplicationController
+end
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :uncamelcase_params_hash_keys
+ around_filter :thread_with_auth_info, :except => [:render_error, :render_not_found]
before_filter :find_object_by_uuid, :except => :index
- before_filter :authenticate_api_token, :except => :render_not_found
- before_filter :set_remote_ip
+ before_filter :remote_ip
before_filter :login_required, :except => :render_not_found
before_filter :catch_redirect_hint
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
-
unless Rails.application.config.consider_all_requests_local
rescue_from Exception,
:with => :render_error
end
def index
- @objects ||= if params[:where]
+ @objects ||= model_class.
+ joins("LEFT JOIN metadata permissions ON permissions.tail=#{table_name}.uuid AND permissions.head=#{model_class.sanitize Thread.current[:user_uuid]} AND permissions.metadata_class='permission' AND permissions.name='visible_to'").
+ where("#{table_name}.created_by_user=? OR permissions.head IS NOT NULL",
+ Thread.current[:user_uuid])
+ if params[:where]
where = params[:where]
where = JSON.parse(where) if where.is_a?(String)
conditions = ['1=1']
attr.to_s.match(/^[a-z][_a-z0-9]+$/) and
model_class.columns.collect(&:name).index(attr))
if value.is_a? Array
- conditions[0] << " and #{attr} in (?)"
+ conditions[0] << " and #{table_name}.#{attr} in (?)"
conditions << value
else
- conditions[0] << " and #{attr}=?"
+ conditions[0] << " and #{table_name}.#{attr}=?"
conditions << value
end
end
end
- model_class.where(*conditions)
+ if conditions.length > 1
+ conditions[0].sub!(/^1=1 and /, '')
+ @objects = @objects.
+ where(*conditions)
+ end
end
- @objects ||= model_class.all
if params[:eager] and params[:eager] != '0' and params[:eager] != 0 and params[:eager] != ''
@objects.each(&:eager_load_associations)
end
show
end
+ def current_user
+ Thread.current[:user]
+ end
+
protected
+ # 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 thread_with_auth_info
+ begin
+ if params[:api_token]
+ @api_client_auth = ApiClientAuthorization.
+ includes(:api_client, :user).
+ where('api_token=?', params[:api_token]).
+ first
+ if @api_client_auth
+ session[:user_id] = @api_client_auth.user.id
+ session[:user_uuid] = @api_client_auth.user.uuid
+ session[:api_client_uuid] = @api_client_auth.api_client.uuid
+ end
+ end
+ Thread.current[:api_client_trusted] = session[:api_client_trusted]
+ Thread.current[:api_client_ip_address] = remote_ip
+ Thread.current[:api_client_uuid] = session[:api_client_uuid]
+ Thread.current[:user_uuid] = session[:user_uuid]
+ Thread.current[:remote_ip] = remote_ip
+ Thread.current[:user] = User.find(session[:user_id]) rescue nil
+ yield
+ ensure
+ Thread.current[:api_client_trusted] = nil
+ Thread.current[:api_client_ip_address] = nil
+ Thread.current[:api_client_uuid] = nil
+ Thread.current[:user_uuid] = nil
+ Thread.current[:remote_ip] = nil
+ Thread.current[:user] = nil
+ end
+ end
+ # /Authentication
+
def model_class
controller_name.classify.constantize
end
controller_name.singularize
end
+ def table_name
+ controller_name
+ end
+
def find_object_by_uuid
if params[:id] and params[:id].match /\D/
params[:uuid] = params.delete :id
render json: @object_list
end
- def authenticate_api_token
- unless Rails.configuration.
- accept_api_token.
- has_key?(params[:api_token] ||
- cookies[:api_token])
- render_error(Exception.new("Invalid API token"))
- end
- end
-
private
- def set_remote_ip
+ def 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.env['REMOTE_ADDR']
end
end
-
end
class CollectionsController < ApplicationController
- skip_before_filter :authenticate_api_token
- def index
- @objects = model_class.order("created_at desc")
- end
end
class NodesController < ApplicationController
- skip_before_filter :authenticate_api_token
def index
@objects = model_class.order("created_at desc")
end
class PipelineInvocationsController < ApplicationController
- skip_before_filter :authenticate_api_token
- def index
- @objects = model_class.order("created_at desc")
- end
end
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
skip_before_filter :uncamelcase_params_hash_keys
skip_before_filter :find_object_by_uuid
- skip_before_filter :authenticate_api_token
respond_to :html
omniauth.delete('extra')
+ # Give the authenticated user a cookie for direct API access
session[:user_id] = user.id
+ session[:user_uuid] = user.uuid
+ session[:api_client_uuid] = nil
+ session[:api_client_trusted] = true # full permission to see user's secrets
@redirect_to = root_path
- if session.has_key?('redirect_to') then
- @redirect_to = session[:redirect_to]
- session.delete(:redirect_to)
+ if session.has_key? :return_to
+ return send_api_token_to(session.delete :return_to)
end
redirect_to @redirect_to
end
# 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"
+ if current_user and params[:return_to]
+ # Already logged in; just need to send a token to the requesting
+ # API client.
+ #
+ # FIXME: if current_user has never authorized this app before,
+ # ask for confirmation here!
+
+ send_api_token_to(params[:return_to])
+ else
+ # TODO: make joshid propagate return_to as a GET parameter, and
+ # use that GET parameter instead of session[] when redirecting
+ # in create(). Using session[] is inappropriate: completing a
+ # login in browser window A can cause a token to be sent to a
+ # different API client who has requested a token in window B.
+
+ session[:return_to] = params[:return_to]
+ redirect_to "/auth/joshid"
+ end
+ end
+
+ def send_api_token_to(callback_url)
+ # Give the API client a token for making API calls on behalf of
+ # the authenticated user
+
+ # Stub: automatically register all new API clients
+ api_client_url_prefix = callback_url.match(%r{^.*?://[^/]+})[0] + '/'
+ api_client = ApiClient.find_or_create_by_url_prefix(api_client_url_prefix)
+
+ api_client_auth = ApiClientAuthorization.
+ new(user: user,
+ api_client: api_client,
+ created_by_ip_address: Thread.current[:remote_ip])
+ api_client_auth.save!
+
+ if callback_url.index('?')
+ callback_url << '&'
+ else
+ callback_url << '?'
+ end
+ callback_url << 'api_token=' << api_client_auth.api_token
+ redirect_to callback_url
end
end
--- /dev/null
+module ApiClientAuthorizationsHelper
+end
--- /dev/null
+module ApiClientsHelper
+end
--- /dev/null
+class ApiClient < ActiveRecord::Base
+ include AssignUuid
+ include KindAndEtag
+ include CommonApiTemplate
+ has_many :api_client_authorizations
+
+ api_accessible :superuser, :extend => :common do |t|
+ t.add :name
+ t.add :url_prefix
+ end
+end
--- /dev/null
+class ApiClientAuthorization < ActiveRecord::Base
+ belongs_to :api_client
+ belongs_to :user
+ after_initialize :assign_random_api_token
+
+ def assign_random_api_token
+ self.api_token ||= rand(2**256).to_s(36)
+ end
+end
include KindAndEtag
include CommonApiTemplate
serialize :prefs, Hash
+ has_many :api_client_authorizations
api_accessible :superuser, :extend => :common do |t|
t.add :email
--- /dev/null
+<table style="width:100%">
+ <tr class="contain-align-left">
+ <th>
+ API client
+ </th><th>
+ Token
+ </th><th>
+ Created at
+ </th><th>
+ Used at
+ </th><th>
+ Expires
+ </th>
+ </tr>
+
+ <% @objects.each do |o| %>
+
+ <tr>
+ <td>
+ <%= o.api_client.name || o.api_client.url_prefix || o.api_client.uuid %>
+ </td><td>
+ <%= o.api_token %>
+ </td><td>
+ <%= o.created_at %>
+ </td><td>
+ <%= o.last_used_at %>
+ /
+ <%= o.last_used_by_ip_address %>
+ </td><td>
+ <%= o.expires_at %>
+ </td>
+ </tr>
+
+ <% end %>
+
+</table>
});
<% end %>
<div id="intropage">
- <img class="clinicalfuture-logo" src="/images/logo.png" style="display:block; margin:2em auto"/>
+ <img class="clinicalfuture-logo" src="<%= asset_path('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>
Server::Application.routes.draw do
+ resources :api_client_authorizations
+
+ resources :api_clients
+
resources :logs
resources :projects
resources :specimens
--- /dev/null
+class CreateApiClients < ActiveRecord::Migration
+ def change
+ create_table :api_clients do |t|
+ t.string :uuid
+ t.string :created_by_client
+ t.string :created_by_user
+ t.string :modified_by_client
+ t.string :modified_by_user
+ t.datetime :modified_at
+ t.string :name
+ t.string :url_prefix
+
+ t.timestamps
+ end
+ add_index :api_clients, :uuid, :unique => true
+ end
+end
--- /dev/null
+class CreateApiClientAuthorizations < ActiveRecord::Migration
+ def change
+ create_table :api_client_authorizations do |t|
+ t.string :api_token, :null => false
+ t.references :api_client, :null => false
+ t.references :user, :null => false
+ t.string :created_by_ip_address
+ t.string :last_used_by_ip_address
+ t.datetime :last_used_at
+ t.datetime :expires_at
+
+ t.timestamps
+ end
+ add_index :api_client_authorizations, :api_token, :unique => true
+ add_index :api_client_authorizations, :api_client_id
+ add_index :api_client_authorizations, :user_id
+ add_index :api_client_authorizations, :expires_at
+ end
+end
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20130123174514) do
+ActiveRecord::Schema.define(:version => 20130123180228) do
+
+ create_table "api_client_authorizations", :force => true do |t|
+ t.string "api_token", :null => false
+ t.integer "api_client_id", :null => false
+ t.integer "user_id", :null => false
+ t.string "created_by_ip_address"
+ t.string "last_used_by_ip_address"
+ t.datetime "last_used_at"
+ t.datetime "expires_at"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "api_client_authorizations", ["api_client_id"], :name => "index_api_client_authorizations_on_api_client_id"
+ add_index "api_client_authorizations", ["api_token"], :name => "index_api_client_authorizations_on_api_token", :unique => true
+ add_index "api_client_authorizations", ["expires_at"], :name => "index_api_client_authorizations_on_expires_at"
+ add_index "api_client_authorizations", ["user_id"], :name => "index_api_client_authorizations_on_user_id"
+
+ create_table "api_clients", :force => true do |t|
+ t.string "uuid"
+ t.string "created_by_client"
+ t.string "created_by_user"
+ t.string "modified_by_client"
+ t.string "modified_by_user"
+ t.datetime "modified_at"
+ t.string "name"
+ t.string "url_prefix"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "api_clients", ["uuid"], :name => "index_api_clients_on_uuid", :unique => true
create_table "collections", :force => true do |t|
t.string "locator"
--- /dev/null
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
+
+one:
+ api_token: MyString
+ api_client:
+ user:
+
+two:
+ api_token: MyString
+ api_client:
+ user:
--- /dev/null
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
+
+one:
+ uuid: MyString
+ created_by_client: MyString
+ created_by_user: MyString
+ modified_by_client: MyString
+ modified_by_user: MyString
+ modified_at: 2013-01-23 10:02:24
+ name: MyString
+ url_prefix: MyString
+
+two:
+ uuid: MyString
+ created_by_client: MyString
+ created_by_user: MyString
+ modified_by_client: MyString
+ modified_by_user: MyString
+ modified_at: 2013-01-23 10:02:24
+ name: MyString
+ url_prefix: MyString
--- /dev/null
+require 'test_helper'
+
+class ApiClientAuthorizationsControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
--- /dev/null
+require 'test_helper'
+
+class ApiClientsControllerTest < ActionController::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
--- /dev/null
+require 'test_helper'
+
+class ApiClientAuthorizationTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
--- /dev/null
+require 'test_helper'
+
+class ApiClientTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
--- /dev/null
+require 'test_helper'
+
+class ApiClientAuthorizationsHelperTest < ActionView::TestCase
+end
--- /dev/null
+require 'test_helper'
+
+class ApiClientsHelperTest < ActionView::TestCase
+end