add ApiClient#is_trusted, show list of api tokens in Workbench
authorTom Clegg <tom@clinicalfuture.com>
Mon, 17 Jun 2013 21:40:23 +0000 (17:40 -0400)
committerTom Clegg <tom@clinicalfuture.com>
Mon, 17 Jun 2013 21:40:23 +0000 (17:40 -0400)
23 files changed:
apps/workbench/app/assets/javascripts/api_client_authorizations.js.coffee [new file with mode: 0644]
apps/workbench/app/assets/stylesheets/api_client_authorizations.css.scss [new file with mode: 0644]
apps/workbench/app/controllers/api_client_authorizations_controller.rb [new file with mode: 0644]
apps/workbench/app/helpers/api_client_authorizations_helper.rb [new file with mode: 0644]
apps/workbench/app/models/api_client_authorization.rb [new file with mode: 0644]
apps/workbench/app/models/arvados_base.rb
apps/workbench/app/views/layouts/application.html.erb
apps/workbench/config/routes.rb
apps/workbench/test/fixtures/api_client_authorizations.yml [new file with mode: 0644]
apps/workbench/test/functional/api_client_authorizations_controller_test.rb [new file with mode: 0644]
apps/workbench/test/unit/api_client_authorization_test.rb [new file with mode: 0644]
apps/workbench/test/unit/helpers/api_client_authorizations_helper_test.rb [new file with mode: 0644]
services/api/app/controllers/api_client_authorizations_controller.rb [deleted file]
services/api/app/controllers/application_controller.rb
services/api/app/controllers/arvados/v1/#api_client_authorizations_controller.rb# [new file with mode: 0644]
services/api/app/controllers/arvados/v1/.#api_client_authorizations_controller.rb [new symlink]
services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb [new file with mode: 0644]
services/api/app/models/api_client.rb
services/api/app/models/api_client_authorization.rb
services/api/config/routes.rb
services/api/db/migrate/20130617150007_add_is_trusted_to_api_clients.rb [new file with mode: 0644]
services/api/db/schema.rb
services/api/lib/current_api_client.rb

diff --git a/apps/workbench/app/assets/javascripts/api_client_authorizations.js.coffee b/apps/workbench/app/assets/javascripts/api_client_authorizations.js.coffee
new file mode 100644 (file)
index 0000000..7615679
--- /dev/null
@@ -0,0 +1,3 @@
+# 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/
diff --git a/apps/workbench/app/assets/stylesheets/api_client_authorizations.css.scss b/apps/workbench/app/assets/stylesheets/api_client_authorizations.css.scss
new file mode 100644 (file)
index 0000000..fd2c9d8
--- /dev/null
@@ -0,0 +1,3 @@
+// 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/
diff --git a/apps/workbench/app/controllers/api_client_authorizations_controller.rb b/apps/workbench/app/controllers/api_client_authorizations_controller.rb
new file mode 100644 (file)
index 0000000..8eed552
--- /dev/null
@@ -0,0 +1,2 @@
+class ApiClientAuthorizationsController < ApplicationController
+end
diff --git a/apps/workbench/app/helpers/api_client_authorizations_helper.rb b/apps/workbench/app/helpers/api_client_authorizations_helper.rb
new file mode 100644 (file)
index 0000000..98ddddc
--- /dev/null
@@ -0,0 +1,2 @@
+module ApiClientAuthorizationsHelper
+end
diff --git a/apps/workbench/app/models/api_client_authorization.rb b/apps/workbench/app/models/api_client_authorization.rb
new file mode 100644 (file)
index 0000000..9cf1359
--- /dev/null
@@ -0,0 +1,5 @@
+class ApiClientAuthorization < ArvadosBase
+  def attribute_editable?(attr)
+    ['expires_at', 'default_owner'].index attr
+  end
+end
index 2aa91f735eb67e3299b09c055531d231e569f82c..a9f9a04a40a73b96d29e5fee9d202072a3c3cee4 100644 (file)
@@ -112,7 +112,9 @@ class ArvadosBase < ActiveRecord::Base
     %w(uuid owner created_at
        modified_at modified_by_user modified_by_client
       ).each do |attr|
-      self.send(attr + '=', resp[attr.to_sym])
+      if self.respond_to? "#{attr}=".to_sym
+        self.send(attr + '=', resp[attr.to_sym])
+      end
     end
 
     self
index 56ac2cbacb4dee8f31c7c3bb77772ad25a155a61..c67fb40846b46b6304275834a403de944521e839 100644 (file)
@@ -42,6 +42,7 @@
               [false, 'Keys', authorized_keys_path],
               [false, 'VMs', virtual_machines_path],
               [false, 'Repos', repositories_path],
+              [false, 'Tokens', api_client_authorizations_path],
               [false, 'Jobs', jobs_path]
               ].each do |admin_only, name, path| %>
            <% if !admin_only or (current_user and current_user.is_admin) %>
index b1c67137a8f8f7eba9ed33ff27b1fac1accdbc9a..e93fa42fc3f079db514286ec8fd957fd6ae2e636 100644 (file)
@@ -1,4 +1,5 @@
 ArvadosWorkbench::Application.routes.draw do
+  resources :api_client_authorizations
   resources :repositories
   resources :virtual_machines
   resources :authorized_keys
diff --git a/apps/workbench/test/fixtures/api_client_authorizations.yml b/apps/workbench/test/fixtures/api_client_authorizations.yml
new file mode 100644 (file)
index 0000000..c63aac0
--- /dev/null
@@ -0,0 +1,11 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
+
+# This model initially had no columns defined.  If you add columns to the
+# model remove the '{}' from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+one: {}
+# column: value
+#
+two: {}
+#  column: value
diff --git a/apps/workbench/test/functional/api_client_authorizations_controller_test.rb b/apps/workbench/test/functional/api_client_authorizations_controller_test.rb
new file mode 100644 (file)
index 0000000..3803b8e
--- /dev/null
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class ApiClientAuthorizationsControllerTest < ActionController::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end
diff --git a/apps/workbench/test/unit/api_client_authorization_test.rb b/apps/workbench/test/unit/api_client_authorization_test.rb
new file mode 100644 (file)
index 0000000..b5b07d1
--- /dev/null
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class ApiClientAuthorizationTest < ActiveSupport::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end
diff --git a/apps/workbench/test/unit/helpers/api_client_authorizations_helper_test.rb b/apps/workbench/test/unit/helpers/api_client_authorizations_helper_test.rb
new file mode 100644 (file)
index 0000000..4225e04
--- /dev/null
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class ApiClientAuthorizationsHelperTest < ActionView::TestCase
+end
diff --git a/services/api/app/controllers/api_client_authorizations_controller.rb b/services/api/app/controllers/api_client_authorizations_controller.rb
deleted file mode 100644 (file)
index d1b26fb..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-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
index e385dc8dad20f67d2e468eaccec51832d129f3de..a47ccf78ff5dba0d5ead7469724211c9fae13b93 100644 (file)
@@ -237,7 +237,7 @@ class ApplicationController < ActionController::Base
       if supplied_token
         api_client_auth = ApiClientAuthorization.
           includes(:api_client, :user).
-          where('api_token=?', supplied_token).
+          where('api_token=? and (expires_at is null or expires_at > now())', supplied_token).
           first
         if api_client_auth
           session[:user_id] = api_client_auth.user.id
@@ -256,15 +256,18 @@ class ApplicationController < ActionController::Base
             find session[:api_client_authorization_id]
         end
       end
-      Thread.current[:api_client_trusted] = session[:api_client_trusted]
       Thread.current[:api_client_ip_address] = remote_ip
       Thread.current[:api_client_authorization] = api_client_auth
       Thread.current[:api_client_uuid] = api_client && api_client.uuid
       Thread.current[:api_client] = api_client
       Thread.current[:user] = user
+      if api_client_auth
+        api_client_auth.last_used_at = Time.now
+        api_client_auth.last_used_by_ip_address = remote_ip
+        api_client_auth.save validate: false
+      end
       yield
     ensure
-      Thread.current[:api_client_trusted] = nil
       Thread.current[:api_client_ip_address] = nil
       Thread.current[:api_client_authorization] = nil
       Thread.current[:api_client_uuid] = nil
diff --git a/services/api/app/controllers/arvados/v1/#api_client_authorizations_controller.rb# b/services/api/app/controllers/arvados/v1/#api_client_authorizations_controller.rb#
new file mode 100644 (file)
index 0000000..dd7aa9f
--- /dev/null
@@ -0,0 +1,36 @@
+class Arvados::V1::ApiClientAuthorizationsController < ApplicationController
+  before_filter :current_api_client_is_trusted
+
+  protected
+
+  def find_objects_for_index
+    # Here we are deliberately less helpful about searching for client
+    # authorizations. Rather than use the generic index/where/order
+    # featuers, we look up tokens belonging to the current user and
+    # filter by exact match on api_token (which we expect in the form
+    # of a where[uuid] parameter to make things easier for API client
+    # libraries).
+    @objects = model_class.
+      includes(:user, :api_client).
+      where('user_id=? and (? or api_token=?)', current_user.id, !@where['uuid'], @where['uuid']).
+      order('created_at desc')
+  end
+
+  def find_object_by_uuid
+    # Again, to make things easier for the client and our own routing,
+    # here we look for the api_token key in a "uuid" (POST) or "id"
+    # (GET) parameter.
+    @object = model_class.where('api_token=?', params[:uuid] || params[:id]).first
+  end
+
+  def current_api_client_is_trusted
+    # Most API clients cannot manipulate tokens. This entire section
+    # of the API is just hidden by default. In the case of a trusted
+    # installation of Workbench, the site administrator will set the
+    # api_client.is_trusted flag so users can use Workbench to
+    # generate API tokens for other applications.
+    unless Thread.current[:api_client].andand.is_trusted
+      render :json => { errors: ['Forbidden: this API client cannot manipulate other clients\' access tokens.'] }.to_json, status: 403
+    end
+  end
+end
diff --git a/services/api/app/controllers/arvados/v1/.#api_client_authorizations_controller.rb b/services/api/app/controllers/arvados/v1/.#api_client_authorizations_controller.rb
new file mode 120000 (symlink)
index 0000000..efdc1d1
--- /dev/null
@@ -0,0 +1 @@
+tom@shuttle.4812:1371491492
\ No newline at end of file
diff --git a/services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb b/services/api/app/controllers/arvados/v1/api_client_authorizations_controller.rb
new file mode 100644 (file)
index 0000000..9722e14
--- /dev/null
@@ -0,0 +1,31 @@
+class Arvados::V1::ApiClientAuthorizationsController < ApplicationController
+  before_filter :current_api_client_is_trusted
+
+  protected
+
+  def find_objects_for_index
+    # Here we are deliberately less helpful about searching for client
+    # authorizations. Rather than use the generic index/where/order
+    # featuers, we look up tokens belonging to the current user and
+    # filter by exact match on api_token (which we expect in the form
+    # of a where[uuid] parameter to make things easier for API client
+    # libraries).
+    @objects = model_class.
+      includes(:user, :api_client).
+      where('user_id=? and (? or api_token=?)', current_user.id, !@where['uuid'], @where['uuid']).
+      order('created_at desc')
+  end
+
+  def find_object_by_uuid
+    # Again, to make things easier for the client and our own routing,
+    # here we look for the api_token key in a "uuid" (POST) or "id"
+    # (GET) parameter.
+    @object = model_class.where('api_token=?', params[:uuid] || params[:id]).first
+  end
+
+  def current_api_client_is_trusted
+    unless Thread.current[:api_client].andand.is_trusted
+      render :json => { errors: ['Forbidden: this API client cannot manipulate other clients\' access tokens.'] }.to_json, status: 403
+    end
+  end
+end
index b3e1b2e3d3bb0a9e9f1594b09147c2ffc1cbcb54..3bda9f3d654b4e3fd5d84fe670dcb22680168c56 100644 (file)
@@ -7,5 +7,6 @@ class ApiClient < ActiveRecord::Base
   api_accessible :superuser, :extend => :common do |t|
     t.add :name
     t.add :url_prefix
+    t.add :is_trusted
   end
 end
index 0e89869f3c1e8806f0f04dc9bb2f289f6fe28a4e..1c4972c124d695b06de90ae7698062f4e3157e9f 100644 (file)
@@ -1,9 +1,60 @@
-class ApiClientAuthorization < ActiveRecord::Base
+class ApiClientAuthorization < ArvadosModel
+  include KindAndEtag
+  include CommonApiTemplate
+
   belongs_to :api_client
   belongs_to :user
   after_initialize :assign_random_api_token
 
+  api_accessible :superuser, :extend => :common do |t|
+    t.add :owner
+    t.add :user_id
+    t.add :api_client_id
+    t.add :api_token
+    t.add :created_by_ip_address
+    t.add :default_owner
+    t.add :expires_at
+    t.add :last_used_at
+    t.add :last_used_by_ip_address
+  end
+
   def assign_random_api_token
     self.api_token ||= rand(2**256).to_s(36)
   end
+
+  def owner
+    self.user.andand.uuid
+  end
+  def owner_was
+    self.user_id_changed? ? User.find(self.user_id_was).andand.uuid : self.user.andand.uuid
+  end
+  def owner_changed?
+    self.user_id_changed?
+  end
+
+  def uuid
+    self.api_token
+  end
+  def uuid=(x) end
+  def uuid_was
+    self.api_token_was
+  end
+  def uuid_changed?
+    self.api_token_changed?
+  end
+
+  def modified_by_client
+    nil
+  end
+  def modified_by_client=(x) end
+
+  def modified_by_user
+    nil
+  end
+  def modified_by_user=(x) end
+
+  def modified_at
+    nil
+  end
+  def modified_at=(x) end
 end
index 398645462a4b78a233ad921d910f12da02a60bfb..b6132c162d6b9b771ecf3c5ed4adc8176708eb1e 100644 (file)
@@ -95,6 +95,8 @@ Server::Application.routes.draw do
       resources :groups
       resources :logs
       resources :users
+      resources :api_clients
+      resources :api_client_authorizations
       resources :jobs
       resources :job_tasks
       resources :keep_disks
diff --git a/services/api/db/migrate/20130617150007_add_is_trusted_to_api_clients.rb b/services/api/db/migrate/20130617150007_add_is_trusted_to_api_clients.rb
new file mode 100644 (file)
index 0000000..0d9b992
--- /dev/null
@@ -0,0 +1,5 @@
+class AddIsTrustedToApiClients < ActiveRecord::Migration
+  def change
+    add_column :api_clients, :is_trusted, :boolean, :default => false
+  end
+end
index 0a18b88d1b7796db13a166114fb573b5ad3566db..91b7979008f27686122f593c0dc9e2e8627ad615 100644 (file)
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version => 20130612042554) do
+ActiveRecord::Schema.define(:version => 20130617150007) do
 
   create_table "api_client_authorizations", :force => true do |t|
     t.string   "api_token",               :null => false
@@ -41,6 +41,7 @@ ActiveRecord::Schema.define(:version => 20130612042554) do
     t.string   "url_prefix"
     t.datetime "created_at"
     t.datetime "updated_at"
+    t.boolean  "is_trusted",         :default => false
   end
 
   add_index "api_clients", ["created_at"], :name => "index_api_clients_on_created_at"
index ab6d624ea5ea3899ca4a4f8acaabb7e39badd819..bbc39e2296f3619e31dca3f70239a2c256bb725a 100644 (file)
@@ -25,12 +25,6 @@ module CurrentApiClient
     Thread.current[:api_client_ip_address]
   end
 
-  # Is the current client permitted to perform ALL actions on behalf
-  # of the authenticated user?
-  def current_api_client_trusted
-    Thread.current[:api_client_trusted]
-  end
-
   def system_user_uuid
     [Server::Application.config.uuid_prefix,
      User.uuid_prefix,