Merge branch '2388-bogus-token-error-page'
authorTom Clegg <tom@curoverse.com>
Tue, 15 Apr 2014 03:56:04 +0000 (23:56 -0400)
committerTom Clegg <tom@curoverse.com>
Tue, 15 Apr 2014 03:56:04 +0000 (23:56 -0400)
closes #2388

56 files changed:
apps/workbench/app/assets/javascripts/application.js
apps/workbench/app/controllers/actions_controller.rb
apps/workbench/app/controllers/collections_controller.rb
apps/workbench/app/controllers/users_controller.rb
apps/workbench/app/models/arvados_base.rb
apps/workbench/app/models/link.rb
apps/workbench/config/application.default.yml
doc/admin/cheat_sheet.html.textile.liquid
doc/api/schema/Link.html.textile.liquid
doc/api/schema/Log.html.textile.liquid
doc/install/create-standard-objects.html.textile.liquid
doc/user/topics/tutorial-trait-search.html.textile.liquid
sdk/ruby/lib/arvados.rb
services/api/Gemfile
services/api/Gemfile.lock
services/api/app/controllers/application_controller.rb
services/api/app/controllers/arvados/v1/collections_controller.rb
services/api/app/controllers/arvados/v1/jobs_controller.rb
services/api/app/controllers/arvados/v1/keep_disks_controller.rb
services/api/app/controllers/arvados/v1/links_controller.rb
services/api/app/controllers/arvados/v1/logs_controller.rb
services/api/app/controllers/arvados/v1/nodes_controller.rb
services/api/app/controllers/arvados/v1/repositories_controller.rb
services/api/app/controllers/arvados/v1/user_agreements_controller.rb
services/api/app/controllers/arvados/v1/users_controller.rb
services/api/app/controllers/user_sessions_controller.rb
services/api/app/mailers/user_notifier.rb [new file with mode: 0644]
services/api/app/models/arvados_model.rb
services/api/app/models/collection.rb
services/api/app/models/job.rb
services/api/app/models/keep_disk.rb
services/api/app/models/link.rb
services/api/app/models/log.rb
services/api/app/models/user.rb
services/api/app/views/user_notifier/account_is_setup.text.erb [new file with mode: 0644]
services/api/config/application.default.yml
services/api/config/routes.rb
services/api/db/migrate/20140325175653_remove_kind_columns.rb [new file with mode: 0644]
services/api/db/schema.rb
services/api/lib/kind_and_etag.rb
services/api/script/setup-new-user.rb
services/api/test/fixtures/groups.yml
services/api/test/fixtures/links.yml
services/api/test/fixtures/logs.yml [new file with mode: 0644]
services/api/test/fixtures/virtual_machines.yml
services/api/test/functional/arvados/v1/links_controller_test.rb
services/api/test/functional/arvados/v1/logs_controller_test.rb
services/api/test/functional/arvados/v1/users_controller_test.rb
services/api/test/functional/user_notifier_test.rb [new file with mode: 0644]
services/api/test/integration/permissions_test.rb
services/api/test/integration/valid_links_test.rb [new file with mode: 0644]
services/api/test/unit/log_test.rb
services/api/test/unit/user_notifier_test.rb [new file with mode: 0644]
services/api/test/unit/user_test.rb
services/keep/keep.go
services/keep/keep_test.go

index 3ad7944385b82a6f2a7a668334b0cc41107a7411..6afc8c3b040c31cf83375ac7051dc63328851902 100644 (file)
@@ -90,7 +90,6 @@ jQuery(function($){
                            {dataType: 'json',
                             type: $(this).attr('data-remote-method'),
                             data: {
-                                'link[head_kind]': 'arvados#collection',
                                 'link[head_uuid]': tag_head_uuid,
                                 'link[link_class]': 'tag',
                                 'link[name]': new_tag
@@ -130,7 +129,7 @@ jQuery(function($){
             });
         }
     }
-    
+
     var fixer = new HeaderRowFixer('.table-fixed-header-row');
     fixer.duplicateTheadTr();
     fixer.fixThead();
index 74e5831235cf9e881df0df18c3f7ec4c9f1c91ff..8a817f03cd78e52dc61ab7c57bff4dc12ff3fe8a 100644 (file)
@@ -76,9 +76,7 @@ class ActionsController < ApplicationController
 
     chash.each do |k,v|
       l = Link.new({
-                     tail_kind: "arvados#collection",
                      tail_uuid: k,
-                     head_kind: "arvados#collection", 
                      head_uuid: newuuid,
                      link_class: "provenance",
                      name: "provided"
index 01abbb4ee06828f8cf7b529f6bbf77b9bdead716..f24a77ad1fd126b94e2851bebf9aab292a14db6a 100644 (file)
@@ -104,7 +104,7 @@ class CollectionsController < ApplicationController
     Link.where(tail_uuid: @sourcedata.keys).each do |link|
       if link.link_class == 'data_origin'
         @sourcedata[link.tail_uuid][:data_origins] ||= []
-        @sourcedata[link.tail_uuid][:data_origins] << [link.name, link.head_kind, link.head_uuid]
+        @sourcedata[link.tail_uuid][:data_origins] << [link.name, link.head_uuid]
       end
     end
     Collection.where(uuid: @sourcedata.keys).each do |collection|
@@ -112,17 +112,17 @@ class CollectionsController < ApplicationController
         @sourcedata[collection.uuid][:collection] = collection
       end
     end
-    
+
     Collection.where(uuid: @object.uuid).each do |u|
       puts request
-      @prov_svg = ProvenanceHelper::create_provenance_graph(u.provenance, "provenance_svg", 
+      @prov_svg = ProvenanceHelper::create_provenance_graph(u.provenance, "provenance_svg",
                                                             {:request => request,
-                                                              :direction => :bottom_up, 
+                                                              :direction => :bottom_up,
                                                               :combine_jobs => :script_only}) rescue nil
-      @used_by_svg = ProvenanceHelper::create_provenance_graph(u.used_by, "used_by_svg", 
+      @used_by_svg = ProvenanceHelper::create_provenance_graph(u.used_by, "used_by_svg",
                                                                {:request => request,
-                                                                 :direction => :top_down, 
-                                                                 :combine_jobs => :script_only, 
+                                                                 :direction => :top_down,
+                                                                 :combine_jobs => :script_only,
                                                                  :pdata_only => true}) rescue nil
     end
   end
index 5ace8d68193d50cd278d87167fdfaede3194c8c5..3077c2f6d36839da7c140847ef767f4e33d706b9 100644 (file)
@@ -91,9 +91,6 @@ class UsersController < ApplicationController
   def home
     @showallalerts = false
     @my_ssh_keys = AuthorizedKey.where(authorized_user_uuid: current_user.uuid)
-    # @my_vm_perms = Link.where(tail_uuid: current_user.uuid, head_kind: 'arvados#virtual_machine', link_class: 'permission', name: 'can_login')
-    # @my_repo_perms = Link.where(tail_uuid: current_user.uuid, head_kind: 'arvados#repository', link_class: 'permission', name: 'can_write')
-
     @my_tag_links = {}
 
     @my_jobs = Job.
@@ -149,6 +146,7 @@ class UsersController < ApplicationController
     respond_to do |format|
       if current_user.andand.is_admin
         setup_params = {}
+        setup_params[:send_notification_email] = "#{Rails.configuration.send_user_setup_notification_email}"
         if params['user_uuid'] && params['user_uuid'].size>0
           setup_params[:uuid] = params['user_uuid']
         end
index 3c224aaba0560f492f923e138da7b421c3a17d59..55932213445febe52f3b17587e5fe274e24ea3c0 100644 (file)
@@ -32,9 +32,7 @@ class ArvadosBase < ActiveRecord::Base
       'modified_by_user_uuid' => '004',
       'modified_by_client_uuid' => '005',
       'name' => '050',
-      'tail_kind' => '100',
       'tail_uuid' => '100',
-      'head_kind' => '101',
       'head_uuid' => '101',
       'info' => 'zzz-000',
       'updated_at' => 'zzz-999'
@@ -164,14 +162,12 @@ class ArvadosBase < ActiveRecord::Base
       true
     end
   end
-      
+
   def links(*args)
     o = {}
     o.merge!(args.pop) if args[-1].is_a? Hash
     o[:link_class] ||= args.shift
     o[:name] ||= args.shift
-    o[:head_kind] ||= args.shift
-    o[:tail_kind] = self.kind
     o[:tail_uuid] = self.uuid
     if all_links
       return all_links.select do |m|
index 899a80022ced45b28ce618f2fc847f268550750a..5e7b42a60b0ea985dcda2daf6822d8dcb22c1dec 100644 (file)
@@ -2,6 +2,6 @@ class Link < ArvadosBase
   attr_accessor :head
   attr_accessor :tail
   def self.by_tail(t, opts={})
-    where(opts.merge :tail_kind => t.kind, :tail_uuid => t.uuid)
+    where(opts.merge :tail_uuid => t.uuid)
   end
 end
index dfc4f03fd8e0d10939d095396b68c3e74810abea..c80b7f960a66f0fe11099688074ae6df289120b2 100644 (file)
@@ -73,3 +73,4 @@ common:
   show_user_agreement_inline: false
   secret_token: ~
   default_openid_prefix: https://www.google.com/accounts/o8/id
+  send_user_setup_notification_email: true
index daaf012bd19a75c579f6333b359f5176c89e9b89..83fa5e8f1be5f6340f0d01993bca0138ac695891 100644 (file)
@@ -38,9 +38,7 @@ target_username=xxxxxxxchangeme
 
 read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"
 {
-"tail_kind":"arvados#user",
 "tail_uuid":"$user_uuid",
-"head_kind":"arvados#virtualMachine",
 "head_uuid":"$vm_uuid",
 "link_class":"permission",
 "name":"can_login",
@@ -60,9 +58,7 @@ repo_username=xxxxxxxchangeme
 
 read -rd $'\000' newlink <<EOF; arv link create --link "$newlink"
 {
-"tail_kind":"arvados#user",
 "tail_uuid":"$user_uuid",
-"head_kind":"arvados#repository",
 "head_uuid":"$repo_uuid",
 "link_class":"permission",
 "name":"can_write",
index 355605578759982a245360a2f58b27118897e117..dec33bf83272c31f35294b4f1122b5af20fe244b 100644 (file)
@@ -27,11 +27,9 @@ Each link has, in addition to the usual "attributes of Arvados resources":{{site
 table(table table-bordered table-condensed).
 |_. Attribute|_. Type|_. Description|
 |tail_uuid|string|Object UUID at the tail (start, source, origin) of this link|
-|tail_kind|string|Object kind at the tail (start, source, origin) of this link|
 |link_class|string|Class (see below)|
 |name|string|Link type (see below)|
 |head_uuid|string|Object UUID at the head (end, destination, target) of this link|
-|head_kind|string|Object kind at the head (end, destination, target) of this link|
 |properties{}|list|Additional information, expressed as a key&rarr;value hash. Key: string. Value: string, number, array, or hash.|
 
 h2. Link classes
index 3f00339a9c2570853a8083f46d0e3d64fcdde0f2..4d781dc3847b61bc9fcfea5bd32e3b4324842a46 100644 (file)
@@ -30,7 +30,6 @@ Each Log has, in addition to the usual "attributes of Arvados resources":{{site.
 
 table(table table-bordered table-condensed).
 |_. Attribute|_. Type|_. Description|_. Example|
-|object_kind|string|||
 |object_uuid|string|||
 |event_at|datetime|||
 |event_type|string|A user-defined category or type for this event.|@LOGIN@|
index 678127bbfad97fbb0b90e9e7e725746d2042b9c8..b56a503436d1bed7fa8973809706ed2951624898 100644 (file)
@@ -33,9 +33,7 @@ echo "Arvados repository uuid is $repo_uuid"
 
 read -rd $'\000' newlink <<EOF; arv link create --link "$newlink" 
 {
- "tail_kind":"arvados#group",
  "tail_uuid":"$all_users_group_uuid",
- "head_kind":"arvados#repository",
  "head_uuid":"$repo_uuid",
  "link_class":"permission",
  "name":"can_read" 
index 001fbbc08267fe00e9c5508b661134895657a544..a79495e377e415c88088eb1cc65d0777086415b1 100644 (file)
@@ -67,22 +67,22 @@ h2. Finding humans with the selected trait
 We query the "links" resource to find humans that report the selected trait.  Links are directional connections between Arvados data items, for example, from a human to their reported traits.
 
 <notextile>
-<pre><code>&gt;&gt;&gt; <span class="userinput">trait_query = {
-    'link_class': 'human_trait',
-    'tail_kind': 'arvados#human',
-    'head_uuid': non_melanoma_cancer
-  }
+<pre><code>&gt;&gt;&gt; <span class="userinput">trait_filter = [
+    ['link_class', '=', 'human_trait'],
+    ['tail_uuid', 'is_a', 'arvados#human'],
+    ['head_uuid', '=', non_melanoma_cancer],
+  ]
 </code></pre>
 </notextile>
 
-* @'link_class'@ queries for links that describe the traits of a particular human.
-* @'tail_kind'@ queries for links where the tail of the link is a human.
-* @'head_uuit'@ queries for links where the head of the link is a specific data item.
+* @['link_class', '=', 'human_trait']@ filters on links that connect phenotype traits to individuals in the database.
+* @['tail_uuid', 'is_a', 'arvados#human']@ filters that the "tail" must be a "human" database object.
+* @['head_uuid', '=', non_melanoma_cancer]@ filters that the "head" of the link must connect to the "trait" database object non_melanoma_cancer .
 
 The query will return links that match all three conditions.
 
 <notextile>
-<pre><code>&gt;&gt;&gt; <span class="userinput">trait_links = arvados.api().links().list(limit=1000, where=trait_query).execute()</span>
+<pre><code>&gt;&gt;&gt; <span class="userinput">trait_links = arvados.api().links().list(limit=1000, filters=trait_filter).execute()</span>
 </code></pre>
 </notextile>
 
index 5d1f4897022dd8b1a9a7cdd358731270c0c2e629..a94eb1db4224bca9245b805c9d58e18be5c1f04a 100644 (file)
@@ -152,22 +152,26 @@ class Arvados
     config['ARVADOS_API_HOST_INSECURE'] = ENV['ARVADOS_API_HOST_INSECURE']
     config['ARVADOS_API_VERSION']       = ENV['ARVADOS_API_VERSION']
 
-    expanded_path = File.expand_path config_file_path
-    if File.exist? expanded_path
-      # Load settings from the config file.
-      lineno = 0
-      File.open(expanded_path).each do |line|
-        lineno = lineno + 1
-        # skip comments and blank lines
-        next if line.match('^\s*#') or not line.match('\S')
-        var, val = line.chomp.split('=', 2)
-        # allow environment settings to override config files.
-        if var and val
-          config[var] ||= val
-        else
-          warn "#{expanded_path}: #{lineno}: could not parse `#{line}'"
+    begin
+      expanded_path = File.expand_path config_file_path
+      if File.exist? expanded_path
+        # Load settings from the config file.
+        lineno = 0
+        File.open(expanded_path).each do |line|
+          lineno = lineno + 1
+          # skip comments and blank lines
+          next if line.match('^\s*#') or not line.match('\S')
+          var, val = line.chomp.split('=', 2)
+          # allow environment settings to override config files.
+          if var and val
+            config[var] ||= val
+          else
+            warn "#{expanded_path}: #{lineno}: could not parse `#{line}'"
+          end
         end
       end
+    rescue
+      debuglog "HOME environment variable (#{ENV['HOME']}) not set, not using #{config_file_path}", 0
     end
 
     @@config = config
index 5932778657ff2b6f2917e237e675aa15e7e1ff64..e50802ef9920585c00574691eda90b68b230df13 100644 (file)
@@ -15,7 +15,7 @@ end
 gem 'pg'
 
 # Start using multi_json once we are on Rails 3.2;
-# Rails 3.1 has a dependency on multi_json < 1.3.0 but we need version 1.3.4 to 
+# Rails 3.1 has a dependency on multi_json < 1.3.0 but we need version 1.3.4 to
 # fix bug https://github.com/collectiveidea/json_spec/issues/27
 gem 'multi_json'
 gem 'oj'
@@ -63,4 +63,6 @@ gem 'test_after_commit', :group => :test
 gem 'google-api-client', '~> 0.6.3'
 gem 'trollop'
 
+gem 'themes_for_rails'
+
 gem 'arvados-cli', '>= 0.1.20140328152103'
index 39718bda445f29624ccd9d118c2a5b88e299656d..7a516d5725c30c349797966e658e66be0925aadd 100644 (file)
@@ -1,12 +1,12 @@
 GEM
   remote: https://rubygems.org/
   specs:
-    actionmailer (3.2.15)
-      actionpack (= 3.2.15)
+    actionmailer (3.2.17)
+      actionpack (= 3.2.17)
       mail (~> 2.5.4)
-    actionpack (3.2.15)
-      activemodel (= 3.2.15)
-      activesupport (= 3.2.15)
+    actionpack (3.2.17)
+      activemodel (= 3.2.17)
+      activesupport (= 3.2.17)
       builder (~> 3.0.0)
       erubis (~> 2.7.0)
       journey (~> 1.0.4)
@@ -14,33 +14,33 @@ GEM
       rack-cache (~> 1.2)
       rack-test (~> 0.6.1)
       sprockets (~> 2.2.1)
-    activemodel (3.2.15)
-      activesupport (= 3.2.15)
+    activemodel (3.2.17)
+      activesupport (= 3.2.17)
       builder (~> 3.0.0)
-    activerecord (3.2.15)
-      activemodel (= 3.2.15)
-      activesupport (= 3.2.15)
+    activerecord (3.2.17)
+      activemodel (= 3.2.17)
+      activesupport (= 3.2.17)
       arel (~> 3.0.2)
       tzinfo (~> 0.3.29)
-    activeresource (3.2.15)
-      activemodel (= 3.2.15)
-      activesupport (= 3.2.15)
-    activesupport (3.2.15)
+    activeresource (3.2.17)
+      activemodel (= 3.2.17)
+      activesupport (= 3.2.17)
+    activesupport (3.2.17)
       i18n (~> 0.6, >= 0.6.4)
       multi_json (~> 1.0)
-    acts_as_api (0.4.1)
+    acts_as_api (0.4.2)
       activemodel (>= 3.0.0)
       activesupport (>= 3.0.0)
       rack (>= 1.1.0)
-    addressable (2.3.5)
+    addressable (2.3.6)
     andand (1.3.3)
-    arel (3.0.2)
-    arvados (0.1.20140328152103)
+    arel (3.0.3)
+    arvados (0.1.20140414145041)
       activesupport (>= 3.2.13)
       andand
       google-api-client (~> 0.6.3)
       json (>= 1.7.7)
-    arvados-cli (0.1.20140328152103)
+    arvados-cli (0.1.20140414145041)
       activesupport (~> 3.2, >= 3.2.13)
       andand (~> 1.3, >= 1.3.3)
       arvados (~> 0.1.0)
@@ -66,13 +66,13 @@ GEM
     coffee-script (2.2.0)
       coffee-script-source
       execjs
-    coffee-script-source (1.6.3)
+    coffee-script-source (1.7.0)
     curb (0.8.5)
-    daemon_controller (1.1.7)
+    daemon_controller (1.2.0)
     erubis (2.7.0)
     execjs (2.0.2)
     extlib (0.9.16)
-    faraday (0.8.8)
+    faraday (0.8.9)
       multipart-post (~> 1.2.0)
     google-api-client (0.6.4)
       addressable (>= 2.3.2)
@@ -85,16 +85,16 @@ GEM
       signet (~> 0.4.5)
       uuidtools (>= 2.1.0)
     hashie (1.2.0)
-    highline (1.6.20)
+    highline (1.6.21)
     hike (1.2.3)
-    httpauth (0.2.0)
-    i18n (0.6.5)
+    httpauth (0.2.1)
+    i18n (0.6.9)
     journey (1.0.4)
-    jquery-rails (3.0.4)
+    jquery-rails (3.1.0)
       railties (>= 3.0, < 5.0)
       thor (>= 0.14, < 2.0)
     json (1.8.1)
-    jwt (0.1.8)
+    jwt (0.1.11)
       multi_json (>= 1.5)
     launchy (2.4.2)
       addressable (~> 2.3)
@@ -102,14 +102,14 @@ GEM
     mail (2.5.4)
       mime-types (~> 1.16)
       treetop (~> 1.4.8)
-    mime-types (1.25)
-    multi_json (1.8.2)
+    mime-types (1.25.1)
+    multi_json (1.9.2)
     multipart-post (1.2.0)
-    net-scp (1.1.2)
+    net-scp (1.2.0)
       net-ssh (>= 2.6.5)
     net-sftp (2.1.2)
       net-ssh (>= 2.6.5)
-    net-ssh (2.7.0)
+    net-ssh (2.8.0)
     net-ssh-gateway (1.2.0)
       net-ssh (>= 2.6.5)
     oauth2 (0.8.1)
@@ -118,49 +118,49 @@ GEM
       jwt (~> 0.1.4)
       multi_json (~> 1.0)
       rack (~> 1.2)
-    oj (2.1.7)
+    oj (2.7.3)
     omniauth (1.1.1)
       hashie (~> 1.2)
       rack
     omniauth-oauth2 (1.1.1)
       oauth2 (~> 0.8.0)
       omniauth (~> 1.0)
-    passenger (4.0.23)
-      daemon_controller (>= 1.1.0)
+    passenger (4.0.41)
+      daemon_controller (>= 1.2.0)
       rack
       rake (>= 0.8.1)
-    pg (0.17.0)
-    polyglot (0.3.3)
+    pg (0.17.1)
+    polyglot (0.3.4)
     rack (1.4.5)
     rack-cache (1.2)
       rack (>= 0.4)
-    rack-ssl (1.3.3)
+    rack-ssl (1.3.4)
       rack
     rack-test (0.6.2)
       rack (>= 1.0)
-    rails (3.2.15)
-      actionmailer (= 3.2.15)
-      actionpack (= 3.2.15)
-      activerecord (= 3.2.15)
-      activeresource (= 3.2.15)
-      activesupport (= 3.2.15)
+    rails (3.2.17)
+      actionmailer (= 3.2.17)
+      actionpack (= 3.2.17)
+      activerecord (= 3.2.17)
+      activeresource (= 3.2.17)
+      activesupport (= 3.2.17)
       bundler (~> 1.0)
-      railties (= 3.2.15)
-    railties (3.2.15)
-      actionpack (= 3.2.15)
-      activesupport (= 3.2.15)
+      railties (= 3.2.17)
+    railties (3.2.17)
+      actionpack (= 3.2.17)
+      activesupport (= 3.2.17)
       rack-ssl (~> 1.3.2)
       rake (>= 0.8.7)
       rdoc (~> 3.4)
       thor (>= 0.14.6, < 2.0)
-    rake (10.1.0)
+    rake (10.2.2)
     rdoc (3.12.2)
       json (~> 1.4)
-    redis (3.0.5)
+    redis (3.0.7)
     ref (1.0.5)
     rvm-capistrano (1.5.1)
       capistrano (~> 2.15.4)
-    sass (3.2.12)
+    sass (3.3.4)
     sass-rails (3.2.6)
       railties (~> 3.2.0)
       sass (>= 3.1.10)
@@ -175,19 +175,21 @@ GEM
       multi_json (~> 1.0)
       rack (~> 1.0)
       tilt (~> 1.1, != 1.3.0)
-    sqlite3 (1.3.8)
-    test_after_commit (0.2.2)
-    therubyracer (0.12.0)
+    sqlite3 (1.3.9)
+    test_after_commit (0.2.3)
+    themes_for_rails (0.5.1)
+      rails (>= 3.0.0)
+    therubyracer (0.12.1)
       libv8 (~> 3.16.14.0)
       ref
-    thor (0.18.1)
+    thor (0.19.1)
     tilt (1.4.1)
     treetop (1.4.15)
       polyglot
       polyglot (>= 0.3.1)
     trollop (2.0)
-    tzinfo (0.3.38)
-    uglifier (2.3.0)
+    tzinfo (0.3.39)
+    uglifier (2.5.0)
       execjs (>= 0.3.0)
       json (>= 1.8.0)
     uuidtools (2.1.4)
@@ -214,6 +216,7 @@ DEPENDENCIES
   sass-rails (>= 3.2.0)
   sqlite3
   test_after_commit
+  themes_for_rails
   therubyracer
   trollop
   uglifier (>= 1.0.3)
index dffdd5d150cffce738b91da99c476700a8d138aa..4b13fca1de45757292592e80152700efe8c83954 100644 (file)
@@ -1,5 +1,6 @@
 class ApplicationController < ActionController::Base
   include CurrentApiClient
+  include ThemesForRails::ActionController
 
   respond_to :json
   protect_from_forgery
@@ -20,6 +21,8 @@ class ApplicationController < ActionController::Base
                                                    :render_error,
                                                    :render_not_found]
 
+  theme :select_theme
+
   attr_accessor :resource_attrs
 
   def index
@@ -36,22 +39,16 @@ class ApplicationController < ActionController::Base
 
   def create
     @object = model_class.new resource_attrs
-    if @object.save
-      show
-    else
-      raise "Save failed"
-    end
+    @object.save!
+    show
   end
 
   def update
     attrs_to_update = resource_attrs.reject { |k,v|
       [:kind, :etag, :href].index k
     }
-    if @object.update_attributes attrs_to_update
-      show
-    else
-      raise "Update failed"
-    end
+    @object.update_attributes! attrs_to_update
+    show
   end
 
   def destroy
@@ -124,12 +121,14 @@ class ApplicationController < ActionController::Base
   end
 
   def load_filters_param
+    @filters ||= []
     if params[:filters].is_a? Array
-      @filters = params[:filters]
+      @filters += params[:filters]
     elsif params[:filters].is_a? String and !params[:filters].empty?
       begin
-        @filters = Oj.load params[:filters]
-        raise unless @filters.is_a? Array
+        f = Oj.load params[:filters]
+        raise unless f.is_a? Array
+        @filters += f
       rescue
         raise ArgumentError.new("Could not parse \"filters\" param as an array")
       end
@@ -166,6 +165,19 @@ class ApplicationController < ActionController::Base
             cond_out << "#{table_name}.#{attr} IN (?)"
             param_out << operand
           end
+        when 'is_a'
+          operand = [operand] unless operand.is_a? Array
+          cond = []
+          operand.each do |op|
+              cl = ArvadosModel::kind_class op
+              if cl
+                cond << "#{table_name}.#{attr} like ?"
+                param_out << cl.uuid_like_pattern
+              else
+                cond << "1=0"
+              end
+          end
+          cond_out << cond.join(' OR ')
         end
       end
       if cond_out.any?
@@ -244,7 +256,7 @@ class ApplicationController < ActionController::Base
       end
     else
       @offset = 0
-    end      
+    end
 
     orders = []
     if params[:order]
@@ -461,7 +473,7 @@ class ApplicationController < ActionController::Base
       order: { type: 'string', required: false }
     }
   end
-  
+
   def client_accepts_plain_text_stream
     (request.headers['Accept'].split(' ') &
      ['text/plain', '*/*']).count > 0
@@ -480,4 +492,8 @@ class ApplicationController < ActionController::Base
     end
     super *opts
   end
+
+  def select_theme
+    return Rails.configuration.arvados_theme
+  end
 end
index c0cd419819124f34f4de532e641fa013b1907c90..8db93c36c2171fa310e6939ae00ddd830dd06ee7 100644 (file)
@@ -6,11 +6,6 @@ class Arvados::V1::CollectionsController < ApplicationController
     # exist) giving the current user (or specified owner_uuid)
     # permission to read it.
     owner_uuid = resource_attrs.delete(:owner_uuid) || current_user.uuid
-    owner_kind = if owner_uuid.match(/-(\w+)-/)[1] == User.uuid_prefix
-                   'arvados#user'
-                 else
-                   'arvados#group'
-                 end
     unless current_user.can? write: owner_uuid
       logger.warn "User #{current_user.andand.uuid} tried to set collection owner_uuid to #{owner_uuid}"
       raise ArvadosModel::PermissionDeniedError
@@ -36,9 +31,7 @@ class Arvados::V1::CollectionsController < ApplicationController
           owner_uuid: owner_uuid,
           link_class: 'permission',
           name: 'can_read',
-          head_kind: 'arvados#collection',
           head_uuid: @object.uuid,
-          tail_kind: owner_kind,
           tail_uuid: owner_uuid
         }
         ActiveRecord::Base.transaction do
index 178b48f173d58e47b5c590149d8b7f966b872dec..40f2def5dcbfd3a7546e55e68ebfcdc3547f4961 100644 (file)
@@ -57,7 +57,7 @@ class Arvados::V1::JobsController < ApplicationController
 
   def cancel
     reload_object_before_update
-    @object.update_attributes cancelled_at: Time.now
+    @object.update_attributes! cancelled_at: Time.now
     show
   end
 
index 7db295dbb2250be51f524969227bd3b7af086fc7..3d9191641ee5a1d0f92a65b6e76cb7cd7b99c86a 100644 (file)
@@ -12,15 +12,18 @@ class Arvados::V1::KeepDisksController < ApplicationController
       service_ssl_flag: true
     }
   end
+
   def ping
     params[:service_host] ||= request.env['REMOTE_ADDR']
-    if not @object.ping params
-      return render_not_found "object not found"
+    act_as_system_user do
+      if not @object.ping params
+        return render_not_found "object not found"
+      end
+      # Render the :superuser view (i.e., include the ping_secret) even
+      # if !current_user.is_admin. This is safe because @object.ping's
+      # success implies the ping_secret was already known by the client.
+      render json: @object.as_api_response(:superuser)
     end
-    # Render the :superuser view (i.e., include the ping_secret) even
-    # if !current_user.is_admin. This is safe because @object.ping's
-    # success implies the ping_secret was already known by the client.
-    render json: @object.as_api_response(:superuser)
   end
 
   def find_objects_for_index
index deeda2869b3e494f20f02a1d25a324b9ef5531aa..563804ef15d05df2c2187a56eaa1a80aa612573d 100644 (file)
@@ -1,10 +1,57 @@
 class Arvados::V1::LinksController < ApplicationController
-  def index
-    if params[:tail_uuid]
-      params[:where] = Oj.load(params[:where]) if params[:where].is_a?(String)
-      params[:where] ||= {}
-      params[:where][:tail_uuid] = params[:tail_uuid]
+
+  def create
+    if resource_attrs[:head_kind] and ArvadosModel::resource_class_for_uuid(resource_attrs[:head_uuid]).kind != resource_attrs[:head_kind]
+      errors.add(attr, "'#{resource_attrs[:head_kind]}' does not match '#{head_uuid}'")
     end
+
+    if resource_attrs[:tail_kind] and ArvadosModel::resource_class_for_uuid(resource_attrs[:tail_uuid]).kind != resource_attrs[:tail_kind]
+      errors.add(attr, "'#{resource_attrs[:tail_kind]}' does not match '#{tail_uuid}'")
+    end
+
+    resource_attrs.delete :head_kind
+    resource_attrs.delete :tail_kind
+    super
+  end
+
+  protected
+
+  # Overrides ApplicationController load_where_param
+  def load_where_param
     super
+
+    # head_kind and tail_kind columns are now virtual,
+    # equivilent functionality is now provided by
+    # 'is_a', so fix up any old-style 'where' clauses.
+    if @where
+      @filters ||= []
+      if @where[:head_kind]
+        @filters << ['head_uuid', 'is_a', @where[:head_kind]]
+        @where.delete :head_kind
+      end
+      if @where[:tail_kind]
+        @filters << ['tail_uuid', 'is_a', @where[:tail_kind]]
+        @where.delete :tail_kind
+      end
+    end
+  end
+
+  # Overrides ApplicationController load_filters_param
+  def load_filters_param
+    super
+
+    # head_kind and tail_kind columns are now virtual,
+    # equivilent functionality is now provided by
+    # 'is_a', so fix up any old-style 'filter' clauses.
+    @filters = @filters.map do |k|
+      if k[0] == 'head_kind' and k[1] == '='
+        ['head_uuid', 'is_a', k[2]]
+      elsif k[0] == 'tail_kind' and k[1] == '='
+        ['tail_uuid', 'is_a', k[2]]
+      else
+        k
+      end
+    end
   end
+
 end
index dffe662e7f1819c762eaca1a42bd408584f5e0bb..925eee523ed616adc69d7a0cb3354d536ef9cf23 100644 (file)
@@ -1,2 +1,34 @@
 class Arvados::V1::LogsController < ApplicationController
+  # Overrides ApplicationController load_where_param
+  def load_where_param
+    super
+
+    # object_kind and column is now virtual,
+    # equivilent functionality is now provided by
+    # 'is_a', so fix up any old-style 'where' clauses.
+    if @where
+      @filters ||= []
+      if @where[:object_kind]
+        @filters << ['object_uuid', 'is_a', @where[:object_kind]]
+        @where.delete :object_kind
+      end
+    end
+  end
+
+  # Overrides ApplicationController load_filters_param
+  def load_filters_param
+    super
+
+    # object_kind and column is now virtual,
+    # equivilent functionality is now provided by
+    # 'is_a', so fix up any old-style 'filter' clauses.
+    @filters = @filters.map do |k|
+      if k[0] == 'object_kind' and k[1] == '='
+        ['object_uuid', 'is_a', k[2]]
+      else
+        k
+      end
+    end
+  end
+
 end
index 1461eeccaa1481fc568eb2a0a8d91a8be8b18562..4415a511631df1b4646b974c7595816b9f9db71d 100644 (file)
@@ -13,18 +13,21 @@ class Arvados::V1::NodesController < ApplicationController
   def self._ping_requires_parameters
     { ping_secret: true }
   end
+
   def ping
-    @object = Node.where(uuid: (params[:id] || params[:uuid])).first
-    if !@object
-      return render_not_found
-    end
-    @object.ping({ ip: params[:local_ipv4] || request.env['REMOTE_ADDR'],
-                   ping_secret: params[:ping_secret],
-                   ec2_instance_id: params[:instance_id] })
-    if @object.info[:ping_secret] == params[:ping_secret]
-      render json: @object.as_api_response(:superuser)
-    else
-      raise "Invalid ping_secret after ping"
+    act_as_system_user do 
+      @object = Node.where(uuid: (params[:id] || params[:uuid])).first
+      if !@object
+        return render_not_found
+      end
+      @object.ping({ ip: params[:local_ipv4] || request.env['REMOTE_ADDR'],
+                     ping_secret: params[:ping_secret],
+                     ec2_instance_id: params[:instance_id] })
+      if @object.info[:ping_secret] == params[:ping_secret]
+        render json: @object.as_api_response(:superuser)
+      else
+        raise "Invalid ping_secret after ping"
+      end
     end
   end
 
index 19504e10c8d83d6e5a06a20bfd8a57e17e556e28..390aa73324fd4a3eba0b56a245819b587f26d9f9 100644 (file)
@@ -14,7 +14,7 @@ class Arvados::V1::RepositoriesController < ApplicationController
       gitolite_permissions = ''
       perms = []
       repo.permissions.each do |perm|
-        if perm.tail_kind == 'arvados#group'
+        if ArvadosModel::resource_class_for_uuid(perm.tail_uuid) == Group
           @users.each do |user_uuid, user|
             user.group_permissions.each do |group_uuid, perm_mask|
               if perm_mask[:write]
index 4ad959e86aae9079554c9f0e24d77c840dea7482..32adde9507554ee9195bbc812b51cc1d86d753ba 100644 (file)
@@ -19,12 +19,12 @@ class Arvados::V1::UserAgreementsController < ApplicationController
     else
       current_user_uuid = current_user.uuid
       act_as_system_user do
-        uuids = Link.where(owner_uuid: system_user_uuid,
-                           link_class: 'signature',
-                           name: 'require',
-                           tail_kind: 'arvados#user',
-                           tail_uuid: system_user_uuid,
-                           head_kind: 'arvados#collection').
+        uuids = Link.where("owner_uuid = ? and link_class = ? and name = ? and tail_uuid = ? and head_uuid like ?",
+                           system_user_uuid,
+                           'signature',
+                           'require',
+                           system_user_uuid,
+                           Collection.uuid_like_pattern).
           collect &:head_uuid
         @objects = Collection.where('uuid in (?)', uuids)
       end
@@ -37,12 +37,12 @@ class Arvados::V1::UserAgreementsController < ApplicationController
     current_user_uuid = (current_user.andand.is_admin && params[:uuid]) ||
       current_user.uuid
     act_as_system_user do
-      @objects = Link.where(owner_uuid: system_user_uuid,
-                            link_class: 'signature',
-                            name: 'click',
-                            tail_kind: 'arvados#user',
-                            tail_uuid: current_user_uuid,
-                            head_kind: 'arvados#collection')
+      @objects = Link.where("owner_uuid = ? and link_class = ? and name = ? and tail_uuid = ? and head_uuid like ?",
+                            system_user_uuid,
+                            'signature',
+                            'click',
+                            current_user_uuid,
+                            Collection.uuid_like_pattern)
     end
     @response_resource_name = 'link'
     render_list
@@ -53,9 +53,7 @@ class Arvados::V1::UserAgreementsController < ApplicationController
     act_as_system_user do
       @object = Link.create(link_class: 'signature',
                             name: 'click',
-                            tail_kind: 'arvados#user',
                             tail_uuid: current_user_uuid,
-                            head_kind: 'arvados#collection',
                             head_uuid: params[:uuid])
     end
     show
index 58661a0e9a3c970712eccc054b8d2e9060dc249e..08368cb5ef110d6181dcdd0f4f696adacba48eee 100644 (file)
@@ -60,18 +60,17 @@ class Arvados::V1::UsersController < ApplicationController
         raise ArgumentError.new "Cannot activate without being invited."
       end
       act_as_system_user do
-        required_uuids = Link.where(owner_uuid: system_user_uuid,
-                                    link_class: 'signature',
-                                    name: 'require',
-                                    tail_uuid: system_user_uuid,
-                                    head_kind: 'arvados#collection').
+        required_uuids = Link.where("owner_uuid = ? and link_class = ? and name = ? and tail_uuid = ? and head_uuid like ?",
+                                    system_user_uuid,
+                                    'signature',
+                                    'require',
+                                    system_user_uuid,
+                                    Collection.uuid_like_pattern).
           collect(&:head_uuid)
         signed_uuids = Link.where(owner_uuid: system_user_uuid,
                                   link_class: 'signature',
                                   name: 'click',
-                                  tail_kind: 'arvados#user',
                                   tail_uuid: @object.uuid,
-                                  head_kind: 'arvados#collection',
                                   head_uuid: required_uuids).
           collect(&:head_uuid)
         todo_uuids = required_uuids - signed_uuids
@@ -131,7 +130,12 @@ class Arvados::V1::UsersController < ApplicationController
                     params[:repo_name], params[:vm_uuid]
     end
 
-    render json: { kind: "arvados#HashList", items: @response }
+    # setup succeeded. send email to user
+    if params[:send_notification_email] == true || params[:send_notification_email] == 'true'
+      UserNotifier.account_is_setup(@object).deliver
+    end
+
+    render json: { kind: "arvados#HashList", items: @response.as_api_response(nil) }
   end
 
   # delete user agreements, vm, repository, login links; set state to inactive
@@ -141,4 +145,12 @@ class Arvados::V1::UsersController < ApplicationController
     show
   end
 
+  protected
+
+  def self._setup_requires_parameters 
+    {
+      send_notification_email: { type: 'boolean', required: true },
+    }  
+  end
+
 end
index 3674c010cb7bcd97ae808483997ef5118554042f..a7391bd73266a2b0b52decec09fa48057f62db2d 100644 (file)
@@ -24,11 +24,11 @@ class UserSessionsController < ApplicationController
     if not user
       # Check for permission to log in to an existing User record with
       # a different identity_url
-      Link.where(link_class: 'permission',
-                 name: 'can_login',
-                 tail_kind: 'email',
-                 tail_uuid: omniauth['info']['email'],
-                 head_kind: 'arvados#user').each do |link|
+      Link.where("link_class = ? and name = ? and tail_uuid = ? and head_uuid like ?",
+                 'permission',
+                 'can_login',
+                 omniauth['info']['email'],
+                 User.uuid_like_pattern).each do |link|
         if prefix = link.properties['identity_url_prefix']
           if prefix == omniauth['info']['identity_url'][0..prefix.size-1]
             user = User.find_by_uuid(link.head_uuid)
diff --git a/services/api/app/mailers/user_notifier.rb b/services/api/app/mailers/user_notifier.rb
new file mode 100644 (file)
index 0000000..759325a
--- /dev/null
@@ -0,0 +1,8 @@
+class UserNotifier < ActionMailer::Base
+  default from: Rails.configuration.user_notifier_email_from
+
+  def account_is_setup(user)
+    @user = user
+    mail(to: user.email, subject: 'Welcome to Curoverse')
+  end
+end
index 921d2b17c5152ed04fc742459cf927652056e14e..dbb807c0b6b4005193babfef0a93481f1f75ef56 100644 (file)
@@ -12,6 +12,7 @@ class ArvadosModel < ActiveRecord::Base
   before_create :ensure_permission_to_create
   before_update :ensure_permission_to_update
   before_destroy :ensure_permission_to_destroy
+
   before_create :update_modified_by_fields
   before_update :maybe_update_modified_by_fields
   after_create :log_create
@@ -19,6 +20,7 @@ class ArvadosModel < ActiveRecord::Base
   after_destroy :log_destroy
   validate :ensure_serialized_attribute_type
   validate :normalize_collection_uuids
+  validate :ensure_valid_uuids
 
   has_many :permissions, :foreign_key => :head_uuid, :class_name => 'Link', :primary_key => :uuid, :conditions => "link_class = 'permission'"
 
@@ -35,7 +37,7 @@ class ArvadosModel < ActiveRecord::Base
   end
 
   def self.kind_class(kind)
-    kind.match(/^arvados\#(.+?)(_list|List)?$/)[1].pluralize.classify.constantize rescue nil
+    kind.match(/^arvados\#(.+)$/)[1].classify.safe_constantize rescue nil
   end
 
   def href
@@ -59,18 +61,18 @@ class ArvadosModel < ActiveRecord::Base
     self.columns.select { |col| col.name == attr.to_s }.first
   end
 
-  def eager_load_associations
-    self.class.columns.each do |col|
-      re = col.name.match /^(.*)_kind$/
-      if (re and
-          self.respond_to? re[1].to_sym and
-          (auuid = self.send((re[1] + '_uuid').to_sym)) and
-          (aclass = self.class.kind_class(self.send(col.name.to_sym))) and
-          (aobject = aclass.where('uuid=?', auuid).first))
-        self.instance_variable_set('@'+re[1], aobject)
-      end
-    end
-  end
+  def eager_load_associations
+    self.class.columns.each do |col|
+      re = col.name.match /^(.*)_kind$/
+      if (re and
+          self.respond_to? re[1].to_sym and
+          (auuid = self.send((re[1] + '_uuid').to_sym)) and
+          (aclass = self.class.kind_class(self.send(col.name.to_sym))) and
+          (aobject = aclass.where('uuid=?', auuid).first))
+        self.instance_variable_set('@'+re[1], aobject)
+      end
+    end
+  end
 
   def self.readable_by user
     uuid_list = [user.uuid, *user.groups_i_can(:read)]
@@ -148,7 +150,7 @@ class ArvadosModel < ActiveRecord::Base
   end
 
   def maybe_update_modified_by_fields
-    update_modified_by_fields if self.changed?
+    update_modified_by_fields if self.changed? or self.new_record?
   end
 
   def update_modified_by_fields
@@ -179,6 +181,10 @@ class ArvadosModel < ActiveRecord::Base
     attributes.keys.select { |a| a.match /_uuid$/ }
   end
 
+  def skip_uuid_read_permission_check
+    %w(modified_by_client_uuid)
+  end
+
   def normalize_collection_uuids
     foreign_key_attributes.each do |attr|
       attr_value = send attr
@@ -193,6 +199,58 @@ class ArvadosModel < ActiveRecord::Base
     end
   end
 
+  @@UUID_REGEX = /^[0-9a-z]{5}-([0-9a-z]{5})-[0-9a-z]{15}$/
+
+  @@prefixes_hash = nil
+  def self.uuid_prefixes
+    unless @@prefixes_hash
+      @@prefixes_hash = {}
+      ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |k|
+        if k.respond_to?(:uuid_prefix)
+          @@prefixes_hash[k.uuid_prefix] = k
+        end
+      end
+    end
+    @@prefixes_hash
+  end
+
+  def self.uuid_like_pattern
+    "_____-#{uuid_prefix}-_______________"
+  end
+
+  def ensure_valid_uuids
+    specials = [system_user_uuid, 'd41d8cd98f00b204e9800998ecf8427e+0']
+
+    foreign_key_attributes.each do |attr|
+      if new_record? or send (attr + "_changed?")
+        attr_value = send attr
+        r = ArvadosModel::resource_class_for_uuid attr_value if attr_value
+        r = r.readable_by(current_user) if r and not skip_uuid_read_permission_check.include? attr
+        if r and r.where(uuid: attr_value).count == 0 and not specials.include? attr_value
+          errors.add(attr, "'#{attr_value}' not found")
+        end
+      end
+    end
+  end
+
+  class Email
+    def self.kind
+      "email"
+    end
+
+    def kind
+      self.class.kind
+    end
+
+    def self.readable_by (u)
+      self
+    end
+
+    def self.where (u)
+      [{:uuid => u[:uuid]}]
+    end
+  end
+
   def self.resource_class_for_uuid(uuid)
     if uuid.is_a? ArvadosModel
       return uuid.class
@@ -206,15 +264,14 @@ class ArvadosModel < ActiveRecord::Base
     resource_class = nil
 
     Rails.application.eager_load!
-    uuid.match /^[0-9a-z]{5}-([0-9a-z]{5})-[0-9a-z]{15}$/ do |re|
-      ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |k|
-        if k.respond_to?(:uuid_prefix)
-          if k.uuid_prefix == re[1]
-            return k
-          end
-        end
-      end
+    uuid.match @@UUID_REGEX do |re|
+      return uuid_prefixes[re[1]] if uuid_prefixes[re[1]]
+    end
+
+    if uuid.match /.+@.+/
+      return Email
     end
+
     nil
   end
 
index 620b74a96ab90c6fce71a16864b2160dcc71558e..600c07511b67e249cb0ae312ebbf9ad5e5c8b9cb 100644 (file)
@@ -128,6 +128,10 @@ class Collection < ArvadosModel
     end
   end
 
+  def self.uuid_like_pattern
+    "________________________________+%"
+  end
+
   def self.normalize_uuid uuid
     hash_part = nil
     size_part = nil
index e3881ba6e1f4bfdfc492975eac82bcccaeaba1e4..0b2247bc21d99196b839f58a0dca37a2d10c284f 100644 (file)
@@ -70,6 +70,10 @@ class Job < ArvadosModel
     super + %w(output log)
   end
 
+  def skip_uuid_read_permission_check
+    super + %w(cancelled_by_client_uuid)
+  end
+
   def ensure_script_version_is_commit
     if self.is_locked_by_uuid and self.started_at
       # Apparently client has already decided to go for it. This is
index 0998fcd84a1b5f9ba855aebbcf4440426f598487..77fc6278eba531f6baa1acf997044aaf893121c6 100644 (file)
@@ -22,6 +22,10 @@ class KeepDisk < ArvadosModel
     t.add :ping_secret
   end
 
+  def foreign_key_attributes
+    super.reject { |a| a == "filesystem_uuid" }
+  end
+
   def ping(o)
     raise "must have :service_host and :ping_secret" unless o[:service_host] and o[:ping_secret]
 
@@ -31,7 +35,7 @@ class KeepDisk < ArvadosModel
     end
 
     @bypass_arvados_authorization = true
-    self.update_attributes(o.select { |k,v|
+    self.update_attributes!(o.select { |k,v|
                              [:service_host,
                               :service_port,
                               :service_ssl_flag,
index 1d4e13d18618eddb14a98aac80aa60f591b5c58a..26e7183be385dba38da43b85eec02d6649311c6d 100644 (file)
@@ -8,19 +8,15 @@ class Link < ArvadosModel
   after_update :maybe_invalidate_permissions_cache
   after_create :maybe_invalidate_permissions_cache
   after_destroy :maybe_invalidate_permissions_cache
-
-  attr_accessor :head
-  attr_accessor :tail
+  attr_accessor :head_kind, :tail_kind
 
   api_accessible :user, extend: :common do |t|
-    t.add :tail_kind
     t.add :tail_uuid
     t.add :link_class
     t.add :name
-    t.add :head_kind
     t.add :head_uuid
-    t.add :head, :if => :head
-    t.add :tail, :if => :tail
+    t.add :head_kind
+    t.add :tail_kind
     t.add :properties
   end
 
@@ -29,6 +25,18 @@ class Link < ArvadosModel
     super
   end
 
+  def head_kind
+    if k = ArvadosModel::resource_class_for_uuid(head_uuid)
+      k.kind
+    end
+  end
+
+  def tail_kind
+    if k = ArvadosModel::resource_class_for_uuid(tail_uuid)
+      k.kind
+    end
+  end
+
   protected
 
   def permission_to_attach_to_objects
@@ -43,7 +51,7 @@ class Link < ArvadosModel
 
     # All users can grant permissions on objects they own
     head_obj = self.class.
-      kind_class(self.head_kind).
+      kind_class(self.head_uuid).
       where('uuid=?',head_uuid).
       first
     if head_obj
index af0b533c7525c8ad20fcbfb252c5d25047d01662..f8e337b201018a7f2638fa551e1b9304e6334211 100644 (file)
@@ -4,20 +4,25 @@ class Log < ArvadosModel
   include CommonApiTemplate
   serialize :properties, Hash
   before_validation :set_default_event_at
-  attr_accessor :object
+  attr_accessor :object, :object_kind
 
   api_accessible :user, extend: :common do |t|
-    t.add :object_kind
     t.add :object_uuid
     t.add :object, :if => :object
+    t.add :object_kind
     t.add :event_at
     t.add :event_type
     t.add :summary
     t.add :properties
   end
 
+  def object_kind
+    if k = ArvadosModel::resource_class_for_uuid(object_uuid)
+      k.kind
+    end
+  end
+
   def fill_object(thing)
-    self.object_kind ||= thing.kind
     self.object_uuid ||= thing.uuid
     self.summary ||= "#{self.event_type} of #{thing.uuid}"
     self
@@ -60,4 +65,8 @@ class Log < ArvadosModel
   def log_change(event_type)
     # Don't log changes to logs.
   end
+
+  def ensure_valid_uuids
+    # logs can have references to deleted objects
+  end
 end
index 5c03eda519533d839abfca77b07b80f6342bfc2c..50dc668448a9eee7a81a0924cd1c0b27b7b8b18b 100644 (file)
@@ -80,10 +80,11 @@ class User < ArvadosModel
         Group.where('owner_uuid in (?)', lookup_uuids).each do |group|
           newgroups << [group.owner_uuid, group.uuid, 'can_manage']
         end
-        Link.where('tail_uuid in (?) and link_class = ? and head_kind in (?)',
+        Link.where('tail_uuid in (?) and link_class = ? and (head_uuid like ? or head_uuid like ?)',
                    lookup_uuids,
                    'permission',
-                   ['arvados#group', 'arvados#user']).each do |link|
+                   Group.uuid_like_pattern,
+                   User.uuid_like_pattern).each do |link|
           newgroups << [link.tail_uuid, link.head_uuid, link.name]
         end
         newgroups.each do |tail_uuid, head_uuid, perm_name|
@@ -128,7 +129,6 @@ class User < ArvadosModel
   def unsetup
     # delete oid_login_perms for this user
     oid_login_perms = Link.where(tail_uuid: self.email,
-                                 head_kind: 'arvados#user',
                                  link_class: 'permission',
                                  name: 'can_login')
     oid_login_perms.each do |perm|
@@ -137,7 +137,6 @@ class User < ArvadosModel
 
     # delete repo_perms for this user
     repo_perms = Link.where(tail_uuid: self.uuid,
-                            head_kind: 'arvados#repository',
                             link_class: 'permission',
                             name: 'can_write')
     repo_perms.each do |perm|
@@ -146,7 +145,6 @@ class User < ArvadosModel
 
     # delete vm_login_perms for this user
     vm_login_perms = Link.where(tail_uuid: self.uuid,
-                                head_kind: 'arvados#virtualMachine',
                                 link_class: 'permission',
                                 name: 'can_login')
     vm_login_perms.each do |perm|
@@ -159,7 +157,6 @@ class User < ArvadosModel
     end.first
     group_perms = Link.where(tail_uuid: self.uuid,
                              head_uuid: group[:uuid],
-                             head_kind: 'arvados#group',
                              link_class: 'permission',
                              name: 'can_read')
     group_perms.each do |perm|
@@ -168,7 +165,6 @@ class User < ArvadosModel
 
     # delete any signatures by this user
     signed_uuids = Link.where(link_class: 'signature',
-                              tail_kind: 'arvados#user',
                               tail_uuid: self.uuid)
     signed_uuids.each do |sign|
       Link.delete sign
@@ -253,17 +249,14 @@ class User < ArvadosModel
 
     # Check oid_login_perm
     oid_login_perms = Link.where(tail_uuid: self.email,
-                                   head_kind: 'arvados#user',
                                    link_class: 'permission',
-                                   name: 'can_login')
+                                   name: 'can_login').where("head_uuid like ?", User.uuid_like_pattern)
 
     if !oid_login_perms.any?
       # create openid login permission
       oid_login_perm = Link.create(link_class: 'permission',
                                    name: 'can_login',
-                                   tail_kind: 'email',
                                    tail_uuid: self.email,
-                                   head_kind: 'arvados#user',
                                    head_uuid: self.uuid,
                                    properties: login_perm_props
                                   )
@@ -290,7 +283,6 @@ class User < ArvadosModel
 
       # Look for existing repository access for this repo
       repo_perms = Link.where(tail_uuid: self.uuid,
-                              head_kind: 'arvados#repository',
                               head_uuid: repo[:uuid],
                               link_class: 'permission',
                               name: 'can_write')
@@ -305,9 +297,7 @@ class User < ArvadosModel
     repo ||= Repository.create(name: repo_name)
     logger.info { "repo uuid: " + repo[:uuid] }
 
-    repo_perm = Link.create(tail_kind: 'arvados#user',
-                            tail_uuid: self.uuid,
-                            head_kind: 'arvados#repository',
+    repo_perm = Link.create(tail_uuid: self.uuid,
                             head_uuid: repo[:uuid],
                             link_class: 'permission',
                             name: 'can_write')
@@ -335,7 +325,6 @@ class User < ArvadosModel
 
       login_perms = Link.where(tail_uuid: self.uuid,
                               head_uuid: vm[:uuid],
-                              head_kind: 'arvados#virtualMachine',
                               link_class: 'permission',
                               name: 'can_login')
 
@@ -348,9 +337,7 @@ class User < ArvadosModel
       end
 
       if !perm_exists
-        login_perm = Link.create(tail_kind: 'arvados#user',
-                                 tail_uuid: self.uuid,
-                                 head_kind: 'arvados#virtualMachine',
+        login_perm = Link.create(tail_uuid: self.uuid,
                                  head_uuid: vm[:uuid],
                                  link_class: 'permission',
                                  name: 'can_login',
@@ -379,14 +366,11 @@ class User < ArvadosModel
 
       group_perms = Link.where(tail_uuid: self.uuid,
                               head_uuid: group[:uuid],
-                              head_kind: 'arvados#group',
                               link_class: 'permission',
                               name: 'can_read')
 
       if !group_perms.any?
-        group_perm = Link.create(tail_kind: 'arvados#user',
-                                 tail_uuid: self.uuid,
-                                 head_kind: 'arvados#group',
+        group_perm = Link.create(tail_uuid: self.uuid,
                                  head_uuid: group[:uuid],
                                  link_class: 'permission',
                                  name: 'can_read')
@@ -406,9 +390,7 @@ class User < ArvadosModel
     act_as_system_user do
       Link.create(link_class: 'permission',
                   name: 'can_manage',
-                  tail_kind: 'arvados#group',
                   tail_uuid: system_group_uuid,
-                  head_kind: 'arvados#user',
                   head_uuid: self.uuid)
     end
   end
diff --git a/services/api/app/views/user_notifier/account_is_setup.text.erb b/services/api/app/views/user_notifier/account_is_setup.text.erb
new file mode 100644 (file)
index 0000000..0f584c7
--- /dev/null
@@ -0,0 +1,6 @@
+<%= @user.full_name %>,
+
+Your Arvados account has been set up. You can log in here using your
+Google account (<%= @user.email %>):
+
+<%= Rails.configuration.workbench_address %>
index 298b001c547ae7cba2df994a73982040b022a311..37bb1c380f9d48091c76ec167093c7b532709124 100644 (file)
@@ -85,6 +85,7 @@ common:
   new_users_are_active: false
   admin_notifier_email_from: arvados@example.com
   email_subject_prefix: "[ARVADOS] "
+  user_notifier_email_from: arvados@example.com
 
   # Visitors to the API server will be redirected to the workbench
   workbench_address: https://workbench.local:3001/
@@ -109,3 +110,5 @@ common:
 
   # Version of your assets, change this if you want to expire all your assets
   assets.version: "1.0"
+
+  arvados_theme: default
index 4bc5de8c8462f1ffca9e75663652866e5c77fdfe..211701a05455edc6e3b05d03bd0823137247acec 100644 (file)
@@ -1,4 +1,6 @@
 Server::Application.routes.draw do
+  themes_for_rails
+
   resources :humans
   resources :traits
   resources :repositories
diff --git a/services/api/db/migrate/20140325175653_remove_kind_columns.rb b/services/api/db/migrate/20140325175653_remove_kind_columns.rb
new file mode 100644 (file)
index 0000000..eae2a2c
--- /dev/null
@@ -0,0 +1,27 @@
+class RemoveKindColumns < ActiveRecord::Migration
+  include CurrentApiClient
+
+  def up
+    remove_column :links, :head_kind
+    remove_column :links, :tail_kind
+    remove_column :logs, :object_kind
+  end
+
+  def down
+    add_column :links, :head_kind, :string
+    add_column :links, :tail_kind, :string
+    add_column :logs, :object_kind, :string
+
+    act_as_system_user do
+      Link.all.each do |l|
+        l.head_kind = ArvadosModel::resource_class_for_uuid(l.head_uuid).kind if l.head_uuid
+        l.tail_kind = ArvadosModel::resource_class_for_uuid(l.tail_uuid).kind if l.tail_uuid
+        l.save
+      end
+      Log.all.each do |l|
+        l.object_kind = ArvadosModel::resource_class_for_uuid(l.object_uuid).kind if l.object_uuid
+        l.save
+      end
+    end
+  end
+end
index 2ef90d27322bc0cf7e52d3651b78d0925f619818..e2301e5be971717b13a58ec67ecea0fe3d4e365f 100644 (file)
 ActiveRecord::Schema.define(:version => 20140407184311) 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   "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",                                            :null => false
-    t.datetime "updated_at",                                            :null => false
+    t.datetime "created_at",                                          :null => false
+    t.datetime "updated_at",                                          :null => false
     t.string   "default_owner_uuid"
     t.text     "scopes",                  :default => "---\n- all\n", :null => false
   end
@@ -70,7 +70,7 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
   create_table "collections", :force => true do |t|
     t.string   "locator"
     t.string   "owner_uuid"
-    t.datetime "created_at",                          :null => false
+    t.datetime "created_at"
     t.string   "modified_by_client_uuid"
     t.string   "modified_by_user_uuid"
     t.datetime "modified_at"
@@ -80,7 +80,7 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
     t.string   "redundancy_confirmed_by_client_uuid"
     t.datetime "redundancy_confirmed_at"
     t.integer  "redundancy_confirmed_as"
-    t.datetime "updated_at",                          :null => false
+    t.datetime "updated_at"
     t.string   "uuid"
     t.text     "manifest_text"
   end
@@ -104,8 +104,8 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
     t.string   "repository_name"
     t.string   "sha1"
     t.string   "message"
-    t.datetime "created_at",      :null => false
-    t.datetime "updated_at",      :null => false
+    t.datetime "created_at"
+    t.datetime "updated_at"
   end
 
   add_index "commits", ["repository_name", "sha1"], :name => "index_commits_on_repository_name_and_sha1", :unique => true
@@ -133,8 +133,8 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
     t.string   "modified_by_user_uuid"
     t.datetime "modified_at"
     t.text     "properties"
-    t.datetime "created_at",              :null => false
-    t.datetime "updated_at",              :null => false
+    t.datetime "created_at"
+    t.datetime "updated_at"
   end
 
   add_index "humans", ["uuid"], :name => "index_humans_on_uuid", :unique => true
@@ -182,8 +182,8 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
     t.boolean  "running"
     t.boolean  "success"
     t.string   "output"
-    t.datetime "created_at",                                  :null => false
-    t.datetime "updated_at",                                  :null => false
+    t.datetime "created_at"
+    t.datetime "updated_at"
     t.string   "priority"
     t.string   "is_locked_by_uuid"
     t.string   "log"
@@ -235,25 +235,23 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
   create_table "links", :force => true do |t|
     t.string   "uuid"
     t.string   "owner_uuid"
-    t.datetime "created_at",              :null => false
+    t.datetime "created_at"
     t.string   "modified_by_client_uuid"
     t.string   "modified_by_user_uuid"
     t.datetime "modified_at"
     t.string   "tail_uuid"
-    t.string   "tail_kind"
     t.string   "link_class"
     t.string   "name"
     t.string   "head_uuid"
     t.text     "properties"
-    t.datetime "updated_at",              :null => false
+    t.datetime "updated_at"
     t.string   "head_kind"
+    t.string   "tail_kind"
   end
 
   add_index "links", ["created_at"], :name => "index_links_on_created_at"
-  add_index "links", ["head_kind"], :name => "index_links_on_head_kind"
   add_index "links", ["head_uuid"], :name => "index_links_on_head_uuid"
   add_index "links", ["modified_at"], :name => "index_links_on_modified_at"
-  add_index "links", ["tail_kind"], :name => "index_links_on_tail_kind"
   add_index "links", ["tail_uuid"], :name => "index_links_on_tail_uuid"
   add_index "links", ["uuid"], :name => "index_links_on_uuid", :unique => true
 
@@ -262,7 +260,6 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
     t.string   "owner_uuid"
     t.string   "modified_by_client_uuid"
     t.string   "modified_by_user_uuid"
-    t.string   "object_kind"
     t.string   "object_uuid"
     t.datetime "event_at"
     t.string   "event_type"
@@ -277,7 +274,6 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
   add_index "logs", ["event_at"], :name => "index_logs_on_event_at"
   add_index "logs", ["event_type"], :name => "index_logs_on_event_type"
   add_index "logs", ["modified_at"], :name => "index_logs_on_modified_at"
-  add_index "logs", ["object_kind"], :name => "index_logs_on_object_kind"
   add_index "logs", ["object_uuid"], :name => "index_logs_on_object_uuid"
   add_index "logs", ["summary"], :name => "index_logs_on_summary"
   add_index "logs", ["uuid"], :name => "index_logs_on_uuid", :unique => true
@@ -308,7 +304,7 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
   create_table "pipeline_instances", :force => true do |t|
     t.string   "uuid"
     t.string   "owner_uuid"
-    t.datetime "created_at",                                 :null => false
+    t.datetime "created_at"
     t.string   "modified_by_client_uuid"
     t.string   "modified_by_user_uuid"
     t.datetime "modified_at"
@@ -317,7 +313,7 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
     t.text     "components"
     t.boolean  "success"
     t.boolean  "active",                  :default => false
-    t.datetime "updated_at",                                 :null => false
+    t.datetime "updated_at"
     t.text     "properties"
   end
 
index 9df128983950b17cc744f86731b387f7c55e8dc8..89c01ef3a2e086fdd38f2de2923dc00f7324c4a3 100644 (file)
@@ -5,10 +5,13 @@ module KindAndEtag
   end
 
   module ClassMethods
+    def kind
+      'arvados#' + self.to_s.camelcase(:lower)
+    end
   end
 
   def kind
-    'arvados#' + self.class.to_s.camelcase(:lower)
+    self.class.kind
   end
 
   def etag
index ebff19263e92129e04d93d7aa5898ddb7ac0dd89..af0de13b2c0305b4b41f369e158924950ebf091e 100755 (executable)
@@ -21,6 +21,9 @@ If creating a new user record, require authentication from an OpenID \
 with this OpenID prefix *and* a matching email address in order to \
 claim the account.
   eos
+  opt :send_notification_email, <<-eos, default: 'true'
+Send notification email after successfully setting up the user.
+  eos
 end
 
 log.level = (ENV['DEBUG'] || opts.debug) ? Logger::DEBUG : Logger::WARN
@@ -55,10 +58,12 @@ end
 # Invoke user setup method
 if (found_user)
   user = arv.user.setup uuid: found_user[:uuid], repo_name: user_repo_name,
-        vm_uuid: vm_uuid, openid_prefix: opts.openid_prefix
+          vm_uuid: vm_uuid, openid_prefix: opts.openid_prefix,
+          send_notification_email: opts.send_notification_email
 else
   user = arv.user.setup user: {email: user_arg}, repo_name: user_repo_name,
-        vm_uuid: vm_uuid, openid_prefix: opts.openid_prefix
+          vm_uuid: vm_uuid, openid_prefix: opts.openid_prefix,
+          send_notification_email: opts.send_notification_email
 end
 
 log.info {"user uuid: " + user[:uuid]}
index 958aab9c2f81db1923f08a996cff02a6bff384e9..c2f2dde5520ca050a98995aeb11c3dec3b1a14f8 100644 (file)
@@ -22,6 +22,12 @@ system_owned_group:
   name: System Private
   description: System-owned Group
 
+system_group:
+  uuid: zzzzz-j7d0g-000000000000000
+  owner_uuid: zzzzz-tpzed-000000000000000
+  name: System Private
+  description: System-owned Group
+
 empty_lonely_group:
   uuid: zzzzz-j7d0g-jtp06ulmvsezgyu
   owner_uuid: zzzzz-tpzed-000000000000000
index 0342d3d1aa654219c5315890ee688c7158de8b84..e3f6e2bcfbec47578d33601b83c775bc3e74de8b 100644 (file)
@@ -6,11 +6,9 @@ user_agreement_required:
   modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
   modified_at: 2013-12-26T19:52:21Z
   updated_at: 2013-12-26T19:52:21Z
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-000000000000000
   link_class: signature
   name: require
-  head_kind: arvados#collection
   head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
   properties: {}
 
@@ -22,11 +20,9 @@ user_agreement_readable:
   modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#group
   tail_uuid: zzzzz-j7d0g-fffffffffffffff
   link_class: permission
   name: can_read
-  head_kind: arvados#collection
   head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
   properties: {}
 
@@ -38,11 +34,9 @@ active_user_member_of_all_users_group:
   modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_read
-  head_kind: arvados#group
   head_uuid: zzzzz-j7d0g-fffffffffffffff
   properties: {}
 
@@ -54,11 +48,9 @@ active_user_can_manage_system_owned_group:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-02-03 15:42:26 -0800
   updated_at: 2014-02-03 15:42:26 -0800
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_manage
-  head_kind: arvados#group
   head_uuid: zzzzz-j7d0g-8ulrifv67tve5sx
   properties: {}
 
@@ -70,11 +62,9 @@ user_agreement_signed_by_active:
   modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   modified_at: 2013-12-26T20:52:21Z
   updated_at: 2013-12-26T20:52:21Z
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: signature
   name: click
-  head_kind: arvados#collection
   head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
   properties: {}
 
@@ -86,11 +76,9 @@ user_agreement_signed_by_inactive:
   modified_by_user_uuid: zzzzz-tpzed-7sg468ezxwnodxs
   modified_at: 2013-12-26T20:52:21Z
   updated_at: 2013-12-26T20:52:21Z
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-7sg468ezxwnodxs
   link_class: signature
   name: click
-  head_kind: arvados#collection
   head_uuid: b519d9cb706a29fc7ea24dbea2f05851+249025
   properties: {}
 
@@ -102,11 +90,9 @@ spectator_user_member_of_all_users_group:
   modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
   link_class: permission
   name: can_read
-  head_kind: arvados#group
   head_uuid: zzzzz-j7d0g-fffffffffffffff
   properties: {}
 
@@ -118,11 +104,9 @@ inactive_user_member_of_all_users_group:
   modified_by_user_uuid: zzzzz-tpzed-7sg468ezxwnodxs
   modified_at: 2013-12-26T20:52:21Z
   updated_at: 2013-12-26T20:52:21Z
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-x9kqpd79egh49c7
   link_class: permission
   name: can_read
-  head_kind: arvados#group
   head_uuid: zzzzz-j7d0g-fffffffffffffff
   properties: {}
 
@@ -134,11 +118,9 @@ inactive_signed_ua_user_member_of_all_users_group:
   modified_by_user_uuid: zzzzz-tpzed-7sg468ezxwnodxs
   modified_at: 2013-12-26T20:52:21Z
   updated_at: 2013-12-26T20:52:21Z
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-7sg468ezxwnodxs
   link_class: permission
   name: can_read
-  head_kind: arvados#group
   head_uuid: zzzzz-j7d0g-fffffffffffffff
   properties: {}
 
@@ -150,11 +132,9 @@ foo_file_readable_by_active:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_read
-  head_kind: arvados#collection
   head_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
   properties: {}
 
@@ -166,11 +146,9 @@ foo_file_readable_by_active_duplicate_permission:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_read
-  head_kind: arvados#collection
   head_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
   properties: {}
 
@@ -182,11 +160,9 @@ foo_file_readable_by_active_redundant_permission_via_private_group:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#group
   tail_uuid: zzzzz-j7d0g-22xp1wpjul508rk
   link_class: permission
   name: can_read
-  head_kind: arvados#collection
   head_uuid: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
   properties: {}
 
@@ -198,11 +174,9 @@ bar_file_readable_by_active:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_read
-  head_kind: arvados#collection
   head_uuid: fa7aeb5140e2848d39b416daeef4ffc5+45
   properties: {}
 
@@ -214,11 +188,9 @@ bar_file_readable_by_spectator:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
   link_class: permission
   name: can_read
-  head_kind: arvados#collection
   head_uuid: fa7aeb5140e2848d39b416daeef4ffc5+45
   properties: {}
 
@@ -230,11 +202,9 @@ baz_file_publicly_readable:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#group
   tail_uuid: zzzzz-j7d0g-fffffffffffffff
   link_class: permission
   name: can_read
-  head_kind: arvados#collection
   head_uuid: ea10d51bcf88862dbcc36eb292017dfd+45
   properties: {}
 
@@ -246,11 +216,9 @@ barbaz_job_readable_by_spectator:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
   link_class: permission
   name: can_read
-  head_kind: arvados#job
   head_uuid: zzzzz-8i9sb-cjs4pklxxjykyuq
   properties: {}
 
@@ -262,11 +230,9 @@ foo_repository_readable_by_spectator:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-01-24 20:42:26 -0800
   updated_at: 2014-01-24 20:42:26 -0800
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
   link_class: permission
   name: can_read
-  head_kind: arvados#repository
   head_uuid: zzzzz-2x53u-382brsig8rp3666
   properties: {}
 
@@ -278,11 +244,9 @@ miniadmin_user_is_a_testusergroup_admin:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-04-01 13:53:33 -0400
   updated_at: 2014-04-01 13:53:33 -0400
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-2bg9x0oeydcw5hm
   link_class: permission
   name: can_manage
-  head_kind: arvados#group
   head_uuid: zzzzz-j7d0g-48foin4vonvc2at
   properties: {}
 
@@ -294,11 +258,9 @@ rominiadmin_user_is_a_testusergroup_admin:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-04-01 13:53:33 -0400
   updated_at: 2014-04-01 13:53:33 -0400
-  tail_kind: arvados#user
   tail_uuid: zzzzz-tpzed-4hvxm4n25emegis
   link_class: permission
   name: can_read
-  head_kind: arvados#group
   head_uuid: zzzzz-j7d0g-48foin4vonvc2at
   properties: {}
 
@@ -310,10 +272,8 @@ testusergroup_can_manage_active_user:
   modified_by_user_uuid: zzzzz-tpzed-000000000000000
   modified_at: 2014-04-01 13:56:10 -0400
   updated_at: 2014-04-01 13:56:10 -0400
-  tail_kind: arvados#group
   tail_uuid: zzzzz-j7d0g-48foin4vonvc2at
   link_class: permission
   name: can_manage
-  head_kind: arvados#user
   head_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   properties: {}
diff --git a/services/api/test/fixtures/logs.yml b/services/api/test/fixtures/logs.yml
new file mode 100644 (file)
index 0000000..d805439
--- /dev/null
@@ -0,0 +1,3 @@
+log1:
+  uuid: zzzzz-xxxxx-pshmckwoma9plh7
+  object_uuid: zzzzz-tpzed-l1s2piq4t4mps8r
\ No newline at end of file
index 72e2130563a48bc7c5a55db9114a3d14b1927932..f5e01639b6c5c97bfef133731e2d31dbe8fed696 100644 (file)
@@ -2,3 +2,8 @@ testvm:
   uuid: zzzzz-2x53u-382brsig8rp3064
   owner_uuid: zzzzz-tpzed-d9tiejq69daie8f
   hostname: testvm.shell
+
+testvm2:
+  uuid: zzzzz-2x53u-382brsig8rp3065
+  owner_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  hostname: testvm2.shell
index afecc18bb01d778f536cc9dbdf6ee5bb6799d3f9..f4d65c19921e4f4434c94257620fce6a0b974752 100644 (file)
@@ -7,9 +7,7 @@ class Arvados::V1::LinksControllerTest < ActionController::TestCase
       properties: {username: 'testusername'},
       link_class: 'test',
       name: 'encoding',
-      tail_kind: 'arvados#user',
       tail_uuid: users(:admin).uuid,
-      head_kind: 'arvados#virtualMachine',
       head_uuid: virtual_machines(:testvm).uuid
     }
     authorize_with :admin
@@ -21,5 +19,188 @@ class Arvados::V1::LinksControllerTest < ActionController::TestCase
       assert_equal false, assigns(:object).properties.has_key?(:username)
     end
   end
-  
+
+  test "head must exist" do
+    link = {
+      link_class: 'test',
+      name: 'stuff',
+      tail_uuid: users(:active).uuid,
+      head_uuid: 'zzzzz-tpzed-xyzxyzxerrrorxx'
+    }
+    authorize_with :admin
+    post :create, link: link
+    assert_response 422
+  end
+
+  test "tail must exist" do
+    link = {
+      link_class: 'test',
+      name: 'stuff',
+      head_uuid: users(:active).uuid,
+      tail_uuid: 'zzzzz-tpzed-xyzxyzxerrrorxx'
+    }
+    authorize_with :admin
+    post :create, link: link
+    assert_response 422
+  end
+
+  test "head and tail exist, head_kind and tail_kind are returned" do
+    link = {
+      link_class: 'test',
+      name: 'stuff',
+      head_uuid: users(:active).uuid,
+      tail_uuid: users(:spectator).uuid,
+    }
+    authorize_with :admin
+    post :create, link: link
+    assert_response :success
+    l = JSON.parse(@response.body)
+    assert 'arvados#user', l['head_kind']
+    assert 'arvados#user', l['tail_kind']
+  end
+
+  test "can supply head_kind and tail_kind without error" do
+    link = {
+      link_class: 'test',
+      name: 'stuff',
+      head_uuid: users(:active).uuid,
+      tail_uuid: users(:spectator).uuid,
+      head_kind: "arvados#user",
+      tail_kind: "arvados#user",
+    }
+    authorize_with :admin
+    post :create, link: link
+    assert_response :success
+    l = JSON.parse(@response.body)
+    assert 'arvados#user', l['head_kind']
+    assert 'arvados#user', l['tail_kind']
+  end
+
+  test "tail must be visible by user" do
+    link = {
+      link_class: 'test',
+      name: 'stuff',
+      head_uuid: users(:active).uuid,
+      tail_uuid: virtual_machines(:testvm).uuid
+    }
+    authorize_with :active
+    post :create, link: link
+    assert_response 422
+  end
+
+  test "filter links with 'is_a' operator" do
+    authorize_with :admin
+    get :index, {
+      filters: [ ['tail_uuid', 'is_a', 'arvados#user'] ]
+    }
+    assert_response :success
+    found = assigns(:objects)
+    assert_not_equal 0, found.count
+    assert_equal found.count, (found.select { |f| f.tail_uuid.match /[a-z0-9]{5}-tpzed-[a-z0-9]{15}/}).count
+  end
+
+  test "filter links with 'is_a' operator with more than one" do
+    authorize_with :admin
+    get :index, {
+      filters: [ ['tail_uuid', 'is_a', ['arvados#user', 'arvados#group'] ] ],
+    }
+    assert_response :success
+    found = assigns(:objects)
+    assert_not_equal 0, found.count
+    assert_equal found.count, (found.select { |f| f.tail_uuid.match /[a-z0-9]{5}-(tpzed|j7d0g)-[a-z0-9]{15}/}).count
+  end
+
+  test "filter links with 'is_a' operator with bogus type" do
+    authorize_with :admin
+    get :index, {
+      filters: [ ['tail_uuid', 'is_a', ['arvados#bogus'] ] ],
+    }
+    assert_response :success
+    found = assigns(:objects)
+    assert_equal 0, found.count
+  end
+
+  test "filter links with 'is_a' operator with collection" do
+    authorize_with :admin
+    get :index, {
+      filters: [ ['head_uuid', 'is_a', ['arvados#collection'] ] ],
+    }
+    assert_response :success
+    found = assigns(:objects)
+    assert_not_equal 0, found.count
+    assert_equal found.count, (found.select { |f| f.head_uuid.match /[a-f0-9]{32}\+\d+/}).count
+  end
+
+  test "test can still use where tail_kind" do
+    authorize_with :admin
+    get :index, {
+      where: { tail_kind: 'arvados#user' }
+    }
+    assert_response :success
+    found = assigns(:objects)
+    assert_not_equal 0, found.count
+    assert_equal found.count, (found.select { |f| f.tail_uuid.match /[a-z0-9]{5}-tpzed-[a-z0-9]{15}/}).count
+  end
+
+  test "test can still use where head_kind" do
+    authorize_with :admin
+    get :index, {
+      where: { head_kind: 'arvados#user' }
+    }
+    assert_response :success
+    found = assigns(:objects)
+    assert_not_equal 0, found.count
+    assert_equal found.count, (found.select { |f| f.head_uuid.match /[a-z0-9]{5}-tpzed-[a-z0-9]{15}/}).count
+  end
+
+  test "test can still use filter tail_kind" do
+    authorize_with :admin
+    get :index, {
+      filters: [ ['tail_kind', '=', 'arvados#user'] ]
+    }
+    assert_response :success
+    found = assigns(:objects)
+    assert_not_equal 0, found.count
+    assert_equal found.count, (found.select { |f| f.tail_uuid.match /[a-z0-9]{5}-tpzed-[a-z0-9]{15}/}).count
+  end
+
+  test "test can still use filter head_kind" do
+    authorize_with :admin
+    get :index, {
+      filters: [ ['head_kind', '=', 'arvados#user'] ]
+    }
+    assert_response :success
+    found = assigns(:objects)
+    assert_not_equal 0, found.count
+    assert_equal found.count, (found.select { |f| f.head_uuid.match /[a-z0-9]{5}-tpzed-[a-z0-9]{15}/}).count
+  end
+
+  test "head_kind matches head_uuid" do
+    link = {
+      link_class: 'test',
+      name: 'stuff',
+      head_uuid: groups(:public).uuid,
+      head_kind: "arvados#user",
+      tail_uuid: users(:spectator).uuid,
+      tail_kind: "arvados#user",
+    }
+    authorize_with :admin
+    post :create, link: link
+    assert_response 422
+  end
+
+  test "tail_kind matches tail_uuid" do
+    link = {
+      link_class: 'test',
+      name: 'stuff',
+      head_uuid: users(:active).uuid,
+      head_kind: "arvados#user",
+      tail_uuid: groups(:public).uuid,
+      tail_kind: "arvados#user",
+    }
+    authorize_with :admin
+    post :create, link: link
+    assert_response 422
+  end
+
 end
index 9c410996b84509861f85f6fd8fcc1c9557cb5b4a..a224e2573f4a30ccd49eb7c098f9acf186a42c18 100644 (file)
@@ -1,6 +1,8 @@
 require 'test_helper'
 
 class Arvados::V1::LogsControllerTest < ActionController::TestCase
+  fixtures :logs
+
   test "non-admins can read their own logs" do
     authorize_with :active
     post :create, log: {summary: "test log"}
@@ -12,4 +14,29 @@ class Arvados::V1::LogsControllerTest < ActionController::TestCase
     assert_equal("test log", assigns(:object).summary,
                  "loaded wrong log after creation")
   end
+
+  test "test can still use where object_kind" do
+    authorize_with :admin
+    get :index, {
+      where: { object_kind: 'arvados#user' }
+    }
+    assert_response :success
+    found = assigns(:objects)
+    assert_not_equal 0, found.count
+    assert_equal found.count, (found.select { |f| f.object_uuid.match /[a-z0-9]{5}-tpzed-[a-z0-9]{15}/}).count
+    l = JSON.parse(@response.body)
+    assert_equal 'arvados#user', l['items'][0]['object_kind']
+  end
+
+  test "test can still use filter object_kind" do
+    authorize_with :admin
+    get :index, {
+      filters: [ ['object_kind', '=', 'arvados#user'] ]
+    }
+    assert_response :success
+    found = assigns(:objects)
+    assert_not_equal 0, found.count
+    assert_equal found.count, (found.select { |f| f.object_uuid.match /[a-z0-9]{5}-tpzed-[a-z0-9]{15}/}).count
+  end
+
 end
index 0a2418e4bafccff6dc3ea75709a7b66b9ca26099..6dc5950dd81a18f32a7ad5175d5b1defc06fea65 100644 (file)
@@ -73,7 +73,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
       repo_name: repo_name,
       openid_prefix: 'https://www.google.com/accounts/o8/id',
       user: {
-        uuid: "this_is_agreeable",
+        uuid: 'zzzzz-tpzed-abcdefghijklmno',
         first_name: "in_create_test_first_name",
         last_name: "test_last_name",
         email: "foo@example.com"
@@ -83,9 +83,10 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     response_items = JSON.parse(@response.body)['items']
 
     created = find_obj_in_resp response_items, 'User', nil
+
     assert_equal 'in_create_test_first_name', created['first_name']
     assert_not_nil created['uuid'], 'expected non-null uuid for the new user'
-    assert_equal 'this_is_agreeable', created['uuid']
+    assert_equal 'zzzzz-tpzed-abcdefghijklmno', created['uuid']
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
@@ -112,7 +113,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
       vm_uuid: @vm_uuid,
       openid_prefix: 'https://www.google.com/accounts/o8/id',
       user: {
-        uuid: "this_is_agreeable",
+        uuid: 'zzzzz-tpzed-abcdefghijklmno',
         first_name: "in_create_test_first_name",
         last_name: "test_last_name",
         email: "foo@example.com"
@@ -124,7 +125,7 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     created = find_obj_in_resp response_items, 'User', nil
     assert_equal 'in_create_test_first_name', created['first_name']
     assert_not_nil created['uuid'], 'expected non-null uuid for the new user'
-    assert_equal 'this_is_agreeable', created['uuid']
+    assert_equal 'zzzzz-tpzed-abcdefghijklmno', created['uuid']
     assert_not_nil created['email'], 'expected non-nil email'
     assert_nil created['identity_url'], 'expected no identity_url'
 
@@ -706,6 +707,58 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
           false, false, false, false, false
   end
 
+  test "setup user with send notification param false and verify no email" do
+    authorize_with :admin
+
+    post :setup, {
+      openid_prefix: 'http://www.example.com/account',
+      send_notification_email: 'false',
+      user: {
+        email: "foo@example.com"
+      }
+    }
+
+    assert_response :success
+    response_items = JSON.parse(@response.body)['items']
+    created = find_obj_in_resp response_items, 'User', nil
+    assert_not_nil created['uuid'], 'expected uuid for the new user'
+    assert_equal created['email'], 'foo@example.com', 'expected given email'
+
+    setup_email = ActionMailer::Base.deliveries.last
+    assert_nil setup_email, 'expected no setup email'
+  end
+
+  test "setup user with send notification param true and verify email" do
+    authorize_with :admin
+
+    post :setup, {
+      openid_prefix: 'http://www.example.com/account',
+      send_notification_email: 'true',
+      user: {
+        email: "foo@example.com"
+      }
+    }
+
+    assert_response :success
+    response_items = JSON.parse(@response.body)['items']
+    created = find_obj_in_resp response_items, 'User', nil
+    assert_not_nil created['uuid'], 'expected uuid for the new user'
+    assert_equal created['email'], 'foo@example.com', 'expected given email'
+
+    setup_email = ActionMailer::Base.deliveries.last
+    assert_not_nil setup_email, 'Expected email after setup'
+
+    assert_equal Rails.configuration.user_notifier_email_from, setup_email.from[0]
+    assert_equal 'foo@example.com', setup_email.to[0]
+    assert_equal 'Welcome to Curoverse', setup_email.subject
+    assert (setup_email.body.to_s.include? 'Your Arvados account has been set up'),
+        'Expected Your Arvados account has been set up in email body'
+    assert (setup_email.body.to_s.include? 'foo@example.com'),
+        'Expected user email in email body'
+    assert (setup_email.body.to_s.include? Rails.configuration.workbench_address),
+        'Expected workbench url in email body'
+  end
+
   def verify_num_links (original_links, expected_additional_links)
     links_now = Link.all
     assert_equal expected_additional_links, Link.all.size-original_links.size,
@@ -720,12 +773,12 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
       end
 
       if object_type == 'User'
-        if !x['head_kind']
+        if ArvadosModel::resource_class_for_uuid(x['uuid']) == User
           return_obj = x
           break
         end
       else  # looking for a link
-        if x['head_kind'] == head_kind
+        if x['head_uuid'] and ArvadosModel::resource_class_for_uuid(x['head_uuid']).kind == head_kind
           return_obj = x
           break
         end
@@ -751,19 +804,19 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
       assert [] != object, "expected #{class_name} with name #{head_uuid}"
       head_uuid = object.first[:uuid]
     end
-    assert_equal link['link_class'], link_class,
+    assert_equal link_class, link['link_class'],
         "did not find expected link_class for #{link_object_name}"
 
-    assert_equal link['name'], link_name,
+    assert_equal link_name, link['name'],
         "did not find expected link_name for #{link_object_name}"
 
-    assert_equal link['tail_uuid'], tail_uuid,
+    assert_equal tail_uuid, link['tail_uuid'],
         "did not find expected tail_uuid for #{link_object_name}"
 
-    assert_equal link['head_kind'], head_kind,
+    assert_equal head_kind, link['head_kind'],
         "did not find expected head_kind for #{link_object_name}"
 
-    assert_equal link['head_uuid'], head_uuid,
+    assert_equal head_uuid, link['head_uuid'],
         "did not find expected head_uuid for #{link_object_name}"
   end
 
@@ -771,9 +824,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
       expect_repo_perms, expect_vm_perms, expect_group_perms, expect_signatures
     # verify that all links are deleted for the user
     oid_login_perms = Link.where(tail_uuid: email,
-                                 head_kind: 'arvados#user',
                                  link_class: 'permission',
-                                 name: 'can_login')
+                                 name: 'can_login').where("head_uuid like ?", User.uuid_like_pattern)
     if expect_oid_login_perms
       assert oid_login_perms.any?, "expected oid_login_perms"
     else
@@ -781,9 +833,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     end
 
     repo_perms = Link.where(tail_uuid: uuid,
-                              head_kind: 'arvados#repository',
                               link_class: 'permission',
-                              name: 'can_write')
+                              name: 'can_write').where("head_uuid like ?", Repository.uuid_like_pattern)
     if expect_repo_perms
       assert repo_perms.any?, "expected repo_perms"
     else
@@ -791,9 +842,8 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     end
 
     vm_login_perms = Link.where(tail_uuid: uuid,
-                              head_kind: 'arvados#virtualMachine',
                               link_class: 'permission',
-                              name: 'can_login')
+                              name: 'can_login').where("head_uuid like ?", VirtualMachine.uuid_like_pattern)
     if expect_vm_perms
       assert vm_login_perms.any?, "expected vm_login_perms"
     else
@@ -805,7 +855,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     end.first
     group_read_perms = Link.where(tail_uuid: uuid,
                              head_uuid: group[:uuid],
-                             head_kind: 'arvados#group',
                              link_class: 'permission',
                              name: 'can_read')
     if expect_group_perms
@@ -815,7 +864,6 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
     end
 
     signed_uuids = Link.where(link_class: 'signature',
-                                  tail_kind: 'arvados#user',
                                   tail_uuid: uuid)
 
     if expect_signatures
diff --git a/services/api/test/functional/user_notifier_test.rb b/services/api/test/functional/user_notifier_test.rb
new file mode 100644 (file)
index 0000000..c06f62e
--- /dev/null
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class UserNotifierTest < ActionMailer::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end
index bd9685bd5fe099b88087c16c30a717afbb305753..e3f6cc1b18646738da3ae706878ed2247c0535c5 100644 (file)
@@ -12,11 +12,9 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     post "/arvados/v1/links", {
       :format => :json,
       :link => {
-        tail_kind: 'arvados#user',
         tail_uuid: users(:spectator).uuid,
         link_class: 'permission',
         name: 'can_read',
-        head_kind: 'arvados#collection',
         head_uuid: collections(:foo_file).uuid,
         properties: {}
       }
@@ -27,11 +25,9 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     post "/arvados/v1/links", {
       :format => :json,
       :link => {
-        tail_kind: 'arvados#user',
         tail_uuid: users(:spectator).uuid,
         link_class: 'permission',
         name: 'can_read',
-        head_kind: 'arvados#collection',
         head_uuid: collections(:foo_file).uuid,
         properties: {}
       }
@@ -66,11 +62,9 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     post "/arvados/v1/links", {
       :format => :json,
       :link => {
-        tail_kind: 'arvados#user',
         tail_uuid: users(:spectator).uuid,
         link_class: 'permission',
         name: 'can_read',
-        head_kind: 'arvados#group',
         head_uuid: groups(:private).uuid,
         properties: {}
       }
@@ -85,11 +79,9 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     post "/arvados/v1/links", {
       :format => :json,
       :link => {
-        tail_kind: 'arvados#group',
         tail_uuid: groups(:private).uuid,
         link_class: 'permission',
         name: 'can_read',
-        head_kind: 'arvados#collection',
         head_uuid: collections(:foo_file).uuid,
         properties: {}
       }
@@ -121,11 +113,9 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     post "/arvados/v1/links", {
       :format => :json,
       :link => {
-        tail_kind: 'arvados#group',
         tail_uuid: groups(:private).uuid,
         link_class: 'permission',
         name: 'can_read',
-        head_kind: 'arvados#collection',
         head_uuid: collections(:foo_file).uuid,
         properties: {}
       }
@@ -140,11 +130,9 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     post "/arvados/v1/links", {
       :format => :json,
       :link => {
-        tail_kind: 'arvados#user',
         tail_uuid: users(:spectator).uuid,
         link_class: 'permission',
         name: 'can_read',
-        head_kind: 'arvados#group',
         head_uuid: groups(:private).uuid,
         properties: {}
       }
@@ -175,11 +163,9 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     post "/arvados/v1/links", {
       :format => :json,
       :link => {
-        tail_kind: 'arvados#user',
         tail_uuid: users(:spectator).uuid,
         link_class: 'permission',
         name: 'can_read',
-        head_kind: 'arvados#group',
         head_uuid: groups(:private).uuid,
         properties: {}
       }
@@ -190,11 +176,9 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     post "/arvados/v1/links", {
       :format => :json,
       :link => {
-        tail_kind: 'arvados#group',
         tail_uuid: groups(:private).uuid,
         link_class: 'permission',
         name: 'can_read',
-        head_kind: 'arvados#group',
         head_uuid: groups(:empty_lonely_group).uuid,
         properties: {}
       }
@@ -205,11 +189,9 @@ class PermissionsTest < ActionDispatch::IntegrationTest
     post "/arvados/v1/links", {
       :format => :json,
       :link => {
-        tail_kind: 'arvados#group',
         tail_uuid: groups(:empty_lonely_group).uuid,
         link_class: 'permission',
         name: 'can_read',
-        head_kind: 'arvados#collection',
         head_uuid: collections(:foo_file).uuid,
         properties: {}
       }
diff --git a/services/api/test/integration/valid_links_test.rb b/services/api/test/integration/valid_links_test.rb
new file mode 100644 (file)
index 0000000..657bf02
--- /dev/null
@@ -0,0 +1,39 @@
+require 'test_helper'
+
+class ValidLinksTest < ActionDispatch::IntegrationTest
+  fixtures :all
+
+  test "tail must exist on update" do
+    admin_auth = {'HTTP_AUTHORIZATION' => "OAuth2 #{api_client_authorizations(:admin).api_token}"}
+
+    post "/arvados/v1/links", {
+      :format => :json,
+      :link => {
+        link_class: 'test',
+        name: 'stuff',
+        head_uuid: users(:active).uuid,
+        tail_uuid: virtual_machines(:testvm).uuid
+      }
+    }, admin_auth
+    assert_response :success
+    u = jresponse['uuid']
+
+    put "/arvados/v1/links/#{u}", {
+      :format => :json,
+      :link => {
+        tail_uuid: virtual_machines(:testvm2).uuid
+      }
+    }, admin_auth
+    assert_response :success
+    assert_equal virtual_machines(:testvm2).uuid, (ActiveSupport::JSON.decode @response.body)['tail_uuid']
+
+    put "/arvados/v1/links/#{u}", {
+      :format => :json,
+      :link => {
+        tail_uuid: 'zzzzz-tpzed-xyzxyzxerrrorxx'
+      }
+    }, admin_auth
+    assert_response 422
+  end
+
+end
index 28980335788414076a642c221f39a25fac6cbd65..3876775916f321ba6b7ed5269499f1123baf46f9 100644 (file)
@@ -39,7 +39,6 @@ class LogTest < ActiveSupport::TestCase
                  "log is not 'modified by' current user")
     assert_equal(current_api_client.andand.uuid, log.modified_by_client_uuid,
                  "log is not 'modified by' current client")
-    assert_equal(thing.kind, log.object_kind, "log kind mismatch")
     assert_equal(thing.uuid, log.object_uuid, "log UUID mismatch")
     assert_equal(event_type.to_s, log.event_type, "log event type mismatch")
     time_method, old_props_test, new_props_test = EVENT_TEST_METHODS[event_type]
diff --git a/services/api/test/unit/user_notifier_test.rb b/services/api/test/unit/user_notifier_test.rb
new file mode 100644 (file)
index 0000000..89d10c5
--- /dev/null
@@ -0,0 +1,24 @@
+require 'test_helper'
+class UserNotifierTest < ActionMailer::TestCase
+
+  # Send the email, then test that it got queued
+  test "account is setup" do
+    user = users :active
+    email = UserNotifier.account_is_setup user
+
+    assert_not_nil email
+    # Test the body of the sent email contains what we expect it to
+    assert_equal Rails.configuration.user_notifier_email_from, email.from.first
+    assert_equal user.email, email.to.first
+    assert_equal 'Welcome to Curoverse', email.subject
+    assert (email.body.to_s.include? 'Your Arvados account has been set up'),
+        'Expected Your Arvados account has been set up in email body'
+    assert (email.body.to_s.include? user.email),
+        'Expected user email in email body'
+    assert (email.body.to_s.include? Rails.configuration.workbench_address),
+        'Expected workbench url in email body'
+  end
+
+end
index 16c05a718196f5a3fc3ad9518266be6e44dbb964..6cee757ee1953abbf18b72e956bdd90b16b239cb 100644 (file)
@@ -161,9 +161,7 @@ class UserTest < ActiveSupport::TestCase
     email = 'foo@example.com'
     openid_prefix = 'http://openid/prefix'
 
-    user = User.new
-    user.email = email
-    user.uuid = 'abcdefghijklmnop'
+    user = User.create ({uuid: 'zzzzz-tpzed-abcdefghijklmno', email: email})
 
     vm = VirtualMachine.create
 
@@ -173,8 +171,10 @@ class UserTest < ActiveSupport::TestCase
     verify_user resp_user, email
 
     oid_login_perm = find_obj_in_resp response, 'Link', 'arvados#user'
+
     verify_link oid_login_perm, 'permission', 'can_login', resp_user[:email],
         resp_user[:uuid]
+
     assert_equal openid_prefix, oid_login_perm[:properties][:identity_url_prefix],
         'expected identity_url_prefix not found for oid_login_perm'
 
@@ -194,9 +194,7 @@ class UserTest < ActiveSupport::TestCase
     email = 'foo@example.com'
     openid_prefix = 'http://openid/prefix'
 
-    user = User.new
-    user.email = email
-    user.uuid = 'abcdefghijklmnop'
+    user = User.create ({uuid: 'zzzzz-tpzed-abcdefghijklmno', email: email})
 
     response = User.setup user, openid_prefix
 
@@ -243,16 +241,20 @@ class UserTest < ActiveSupport::TestCase
     verify_link vm_perm, 'permission', 'can_login', resp_user[:uuid], vm.uuid
   end
 
-  def find_obj_in_resp (response, object_type, head_kind=nil)
+  def find_obj_in_resp (response_items, object_type, head_kind=nil)
     return_obj = nil
-    response.each { |x|
-      if x.class.name == object_type
-        if head_kind
-          if x.head_kind == head_kind
-            return_obj = x
-            break
-          end
-        else
+    response_items.each { |x|
+      if !x
+        next
+      end
+
+      if object_type == 'User'
+        if ArvadosModel::resource_class_for_uuid(x['uuid']) == User
+          return_obj = x
+          break
+        end
+      else  # looking for a link
+        if ArvadosModel::resource_class_for_uuid(x['head_uuid']).kind == head_kind
           return_obj = x
           break
         end
@@ -269,18 +271,18 @@ class UserTest < ActiveSupport::TestCase
   end
 
   def verify_link (link_object, link_class, link_name, tail_uuid, head_uuid)
-    assert_not_nil link_object, 'expected link for #{link_class} #{link_name}'
+    assert_not_nil link_object, "expected link for #{link_class} #{link_name}"
     assert_not_nil link_object[:uuid],
-        'expected non-nil uuid for link for #{link_class} #{link_name}'
+        "expected non-nil uuid for link for #{link_class} #{link_name}"
     assert_equal link_class, link_object[:link_class],
-        'expected link_class not found for #{link_class} #{link_name}'
+        "expected link_class not found for #{link_class} #{link_name}"
     assert_equal link_name, link_object[:name],
-        'expected link_name not found for #{link_class} #{link_name}'
+        "expected link_name not found for #{link_class} #{link_name}"
     assert_equal tail_uuid, link_object[:tail_uuid],
-        'expected tail_uuid not found for #{link_class} #{link_name}'
+        "expected tail_uuid not found for #{link_class} #{link_name}"
     if head_uuid
       assert_equal head_uuid, link_object[:head_uuid],
-          'expected head_uuid not found for #{link_class} #{link_name}'
+          "expected head_uuid not found for #{link_class} #{link_name}"
     end
   end
 
index 3adffcf846e3dcd2464adccbd3d94ed1feb057f3..5113727169ffcd83a976d6074c402fe5be822fd1 100644 (file)
@@ -8,6 +8,7 @@ import (
        "flag"
        "fmt"
        "github.com/gorilla/mux"
+       "io"
        "io/ioutil"
        "log"
        "net/http"
@@ -18,6 +19,12 @@ import (
        "time"
 )
 
+// ======================
+// Configuration settings
+//
+// TODO(twp): make all of these configurable via command line flags
+// and/or configuration file settings.
+
 // Default TCP address on which to listen for requests.
 const DEFAULT_ADDR = ":25107"
 
@@ -32,24 +39,32 @@ var PROC_MOUNTS = "/proc/mounts"
 
 var KeepVolumes []string
 
+// ==========
+// Error types.
+//
 type KeepError struct {
        HTTPCode int
-       Err      error
+       ErrMsg   string
 }
 
-const (
-       ErrCollision = 400
-       ErrMD5Fail   = 401
-       ErrCorrupt   = 402
-       ErrNotFound  = 404
-       ErrOther     = 500
-       ErrFull      = 503
+var (
+       CollisionError = &KeepError{400, "Collision"}
+       MD5Error       = &KeepError{401, "MD5 Failure"}
+       CorruptError   = &KeepError{402, "Corruption"}
+       NotFoundError  = &KeepError{404, "Not Found"}
+       GenericError   = &KeepError{500, "Fail"}
+       FullError      = &KeepError{503, "Full"}
+       TooLongError   = &KeepError{504, "Too Long"}
 )
 
 func (e *KeepError) Error() string {
-       return fmt.Sprintf("Error %d: %s", e.HTTPCode, e.Err.Error())
+       return e.ErrMsg
 }
 
+// This error is returned by ReadAtMost if the available
+// data exceeds BLOCKSIZE bytes.
+var ReadErrorTooLong = errors.New("Too long")
+
 func main() {
        // Parse command-line flags:
        //
@@ -162,12 +177,17 @@ func PutBlockHandler(w http.ResponseWriter, req *http.Request) {
        hash := mux.Vars(req)["hash"]
 
        // Read the block data to be stored.
-       // TODO(twp): decide what to do when the input stream contains
-       // more than BLOCKSIZE bytes.
+       // If the request exceeds BLOCKSIZE bytes, issue a HTTP 500 error.
+       //
+       // Note: because req.Body is a buffered Reader, each Read() call will
+       // collect only the data in the network buffer (typically 16384 bytes),
+       // even if it is passed a much larger slice.
+       //
+       // Instead, call ReadAtMost to read data from the socket
+       // repeatedly until either EOF or BLOCKSIZE bytes have been read.
        //
-       buf := make([]byte, BLOCKSIZE)
-       if nread, err := req.Body.Read(buf); err == nil {
-               if err := PutBlock(buf[:nread], hash); err == nil {
+       if buf, err := ReadAtMost(req.Body, BLOCKSIZE); err == nil {
+               if err := PutBlock(buf, hash); err == nil {
                        w.WriteHeader(http.StatusOK)
                } else {
                        ke := err.(*KeepError)
@@ -175,7 +195,13 @@ func PutBlockHandler(w http.ResponseWriter, req *http.Request) {
                }
        } else {
                log.Println("error reading request: ", err)
-               http.Error(w, err.Error(), 500)
+               errmsg := err.Error()
+               if err == ReadErrorTooLong {
+                       // Use a more descriptive error message that includes
+                       // the maximum request size.
+                       errmsg = fmt.Sprintf("Max request size %d bytes", BLOCKSIZE)
+               }
+               http.Error(w, errmsg, 500)
        }
 }
 
@@ -217,7 +243,7 @@ func GetBlock(hash string) ([]byte, error) {
                        //
                        log.Printf("%s: checksum mismatch: %s (actual hash %s)\n",
                                vol, blockFilename, filehash)
-                       return buf, &KeepError{ErrCorrupt, errors.New("Corrupt")}
+                       return buf, CorruptError
                }
 
                // Success!
@@ -225,7 +251,7 @@ func GetBlock(hash string) ([]byte, error) {
        }
 
        log.Printf("%s: not found on any volumes, giving up\n", hash)
-       return buf, &KeepError{ErrNotFound, errors.New("not found: " + hash)}
+       return buf, NotFoundError
 }
 
 /* PutBlock(block, hash)
@@ -259,7 +285,7 @@ func PutBlock(block []byte, hash string) error {
        blockhash := fmt.Sprintf("%x", md5.Sum(block))
        if blockhash != hash {
                log.Printf("%s: MD5 checksum %s did not match request", hash, blockhash)
-               return &KeepError{ErrMD5Fail, errors.New("MD5Fail")}
+               return MD5Error
        }
 
        // If we already have a block on disk under this identifier, return
@@ -271,7 +297,7 @@ func PutBlock(block []byte, hash string) error {
                if bytes.Compare(block, oldblock) == 0 {
                        return nil
                } else {
-                       return &KeepError{ErrCollision, errors.New("Collision")}
+                       return CollisionError
                }
        }
 
@@ -315,10 +341,10 @@ func PutBlock(block []byte, hash string) error {
 
        if allFull {
                log.Printf("all Keep volumes full")
-               return &KeepError{ErrFull, errors.New("Full")}
+               return FullError
        } else {
                log.Printf("all Keep volumes failed")
-               return &KeepError{ErrOther, errors.New("Fail")}
+               return GenericError
        }
 }
 
@@ -365,3 +391,21 @@ func FreeDiskSpace(volume string) (free uint64, err error) {
 
        return
 }
+
+// ReadAtMost
+//     Reads bytes repeatedly from an io.Reader until either
+//     encountering EOF, or the maxbytes byte limit has been reached.
+//     Returns a byte slice of the bytes that were read.
+//
+//     If the reader contains more than maxbytes, returns a nil slice
+//     and an error.
+//
+func ReadAtMost(r io.Reader, maxbytes int) ([]byte, error) {
+       // Attempt to read one more byte than maxbytes.
+       lr := io.LimitReader(r, int64(maxbytes+1))
+       buf, err := ioutil.ReadAll(lr)
+       if len(buf) > maxbytes {
+               return nil, ReadErrorTooLong
+       }
+       return buf, err
+}
index c1f4e658958755704c28ee1690261bce39372d7b..348445e78d105a79a136ea010c6139aad9ae4beb 100644 (file)
@@ -62,13 +62,8 @@ func TestGetBlockMissing(t *testing.T) {
 
        // Check that GetBlock returns failure.
        result, err := GetBlock(TEST_HASH)
-       if err == nil {
-               t.Errorf("GetBlock incorrectly returned success: ", result)
-       } else {
-               ke := err.(*KeepError)
-               if ke.HTTPCode != ErrNotFound {
-                       t.Errorf("GetBlock: %v", ke)
-               }
+       if err != NotFoundError {
+               t.Errorf("Expected NotFoundError, got %v", result)
        }
 }
 
@@ -88,8 +83,8 @@ func TestGetBlockCorrupt(t *testing.T) {
 
        // Check that GetBlock returns failure.
        result, err := GetBlock(TEST_HASH)
-       if err == nil {
-               t.Errorf("GetBlock incorrectly returned success: %s", result)
+       if err != CorruptError {
+               t.Errorf("Expected CorruptError, got %v", result)
        }
 }
 
@@ -113,7 +108,7 @@ func TestPutBlockOK(t *testing.T) {
 
        result, err := GetBlock(TEST_HASH)
        if err != nil {
-               t.Fatalf("GetBlock: %s", err.Error())
+               t.Fatalf("GetBlock returned error: %v", err)
        }
        if string(result) != string(TEST_BLOCK) {
                t.Error("PutBlock/GetBlock mismatch")
@@ -140,7 +135,7 @@ func TestPutBlockOneVol(t *testing.T) {
 
        result, err := GetBlock(TEST_HASH)
        if err != nil {
-               t.Fatalf("GetBlock: %s", err.Error())
+               t.Fatalf("GetBlock: %v", err)
        }
        if string(result) != string(TEST_BLOCK) {
                t.Error("PutBlock/GetBlock mismatch")
@@ -161,19 +156,14 @@ func TestPutBlockMD5Fail(t *testing.T) {
 
        // Check that PutBlock returns the expected error when the hash does
        // not match the block.
-       if err := PutBlock(BAD_BLOCK, TEST_HASH); err == nil {
-               t.Error("PutBlock succeeded despite a block mismatch")
-       } else {
-               ke := err.(*KeepError)
-               if ke.HTTPCode != ErrMD5Fail {
-                       t.Errorf("PutBlock returned the wrong error (%v)", ke)
-               }
+       if err := PutBlock(BAD_BLOCK, TEST_HASH); err != MD5Error {
+               t.Error("Expected MD5Error, got %v", err)
        }
 
        // Confirm that GetBlock fails to return anything.
-       if result, err := GetBlock(TEST_HASH); err == nil {
-               t.Errorf("GetBlock succeded after a corrupt block store, returned '%s'",
-                       string(result))
+       if result, err := GetBlock(TEST_HASH); err != NotFoundError {
+               t.Errorf("GetBlock succeeded after a corrupt block store (result = %s, err = %v)",
+                       string(result), err)
        }
 }
 
@@ -220,7 +210,7 @@ func TestPutBlockCollision(t *testing.T) {
 
        if err := PutBlock(b2, locator); err == nil {
                t.Error("PutBlock did not report a collision")
-       } else if err.(*KeepError).HTTPCode != ErrCollision {
+       } else if err != CollisionError {
                t.Errorf("PutBlock returned %v", err)
        }
 }