[ARVADOS] created: 5ff17b27bd039f3b219e18dfc2e1d67d8043e11d

git at public.curoverse.com git at public.curoverse.com
Tue Jul 15 16:23:33 EDT 2014


        at  5ff17b27bd039f3b219e18dfc2e1d67d8043e11d (commit)


commit 5ff17b27bd039f3b219e18dfc2e1d67d8043e11d
Author: Brett Smith <brett at curoverse.com>
Date:   Tue Jul 15 16:16:38 2014 -0400

    2044: Add Workbench interface to manage project sharing.
    
    This empowers administrators to add, modify, and remove permission
    links on projects.

diff --git a/apps/workbench/app/assets/javascripts/select_modal.js b/apps/workbench/app/assets/javascripts/select_modal.js
index 0a58213..fbdf15f 100644
--- a/apps/workbench/app/assets/javascripts/select_modal.js
+++ b/apps/workbench/app/assets/javascripts/select_modal.js
@@ -68,6 +68,11 @@ $(document).on('click', '.selectable', function() {
 });
 $(document).on('page-refresh', function(event, data, status, jqxhr, action_data) {
     window.location.reload();
+}).on('tab-refresh', function(event, data, status, jqxhr, action_data) {
+    tab_name = $('.tab-pane.active')[0].id;
+    tab_pane_valid_state[tab_name] = false;
+    ajaxRefreshTabPane(tab_name);
+    $('body > .modal-container .modal').modal('hide');
 }).on('redirect-to-created-object', function(event, data, status, jqxhr, action_data) {
     window.location.href = data.href.replace(/^[^\/]*\/\/[^\/]*/, '');
 });
diff --git a/apps/workbench/app/controllers/projects_controller.rb b/apps/workbench/app/controllers/projects_controller.rb
index 352b0ae..e45e840 100644
--- a/apps/workbench/app/controllers/projects_controller.rb
+++ b/apps/workbench/app/controllers/projects_controller.rb
@@ -8,7 +8,7 @@ class ProjectsController < ApplicationController
   end
 
   def show_pane_list
-    %w(Contents Permissions Advanced)
+    %w(Contents Sharing Advanced)
   end
 
   def remove_item
@@ -83,6 +83,11 @@ class ProjectsController < ApplicationController
     @share_links = Link.filter([['head_uuid', '=', @object.uuid],
                                 ['link_class', '=', 'permission']])
     @logs = Log.limit(10).filter([['object_uuid', '=', @object.uuid]])
+    @users = User.limit(10000).
+      select(["uuid", "is_active", "first_name", "last_name"]).
+      filter([['is_active', '=', 'true']])
+    @groups = Group.limit(10000).
+      select(["uuid", "name", "description"])
 
     @objects_and_names = []
     @objects.each do |object|
@@ -121,6 +126,20 @@ class ProjectsController < ApplicationController
     end
   end
 
+  helper_method :managed_by_user?
+  def managed_by_user?(user=nil)
+    user ||= current_user
+    if user.nil?
+      false
+    else
+      user_uuid = user.uuid
+      user.is_admin or (user_uuid == @object.owner_uuid) or
+        @share_links.any? { |link|
+          (link.tail_uuid == user_uuid) and (link.name == "can_manage")
+        }
+    end
+  end
+
   def create
     @new_resource_attrs = (params['project'] || {}).merge(group_class: 'project')
     @new_resource_attrs[:name] ||= 'New project'
diff --git a/apps/workbench/app/views/application/_choose.html.erb b/apps/workbench/app/views/application/_choose.html.erb
index fdd3d3c..18df513 100644
--- a/apps/workbench/app/views/application/_choose.html.erb
+++ b/apps/workbench/app/views/application/_choose.html.erb
@@ -30,16 +30,20 @@
           </div>
         </nav>
 
+        <% preview_pane = (params[:preview_pane] != "false")
+           pane_col_class = preview_pane ? "col-sm-6" : "" %>
         <div class="row" style="height: 20em">
-          <div class="col-sm-6 container-fluid arv-filterable-list selectable-container"
+          <div class="<%= pane_col_class %> container-fluid arv-filterable-list selectable-container"
                style="height: 100%; overflow-y: scroll"
                data-infinite-scroller="#choose-scroll"
                id="choose-scroll"
                data-infinite-content-href="<%= @next_page_href %>">
             <%= render partial: 'choose_rows', locals: {multiple: multiple} %>
           </div>
-          <div class="col-sm-6 modal-dialog-preview-pane" style="height: 100%; overflow-y: scroll">
+          <% if preview_pane %>
+          <div class="<%= pane_col_class %> modal-dialog-preview-pane" style="height: 100%; overflow-y: scroll">
           </div>
+          <% end %>
         </div>
 
         <div class="modal-footer">
diff --git a/apps/workbench/app/views/groups/_choose_rows.html.erb b/apps/workbench/app/views/groups/_choose_rows.html.erb
new file mode 100644
index 0000000..772ef19
--- /dev/null
+++ b/apps/workbench/app/views/groups/_choose_rows.html.erb
@@ -0,0 +1,9 @@
+<% icon_class = fa_icon_class_for_class(Group) %>
+<% @objects.each do |object| %>
+  <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= object.uuid %>">
+    <div class="col-sm-12" style="overflow-x:hidden">
+      <i class="fa fa-fw <%= icon_class %>"></i>
+      <%= object.name %>
+    </div>
+  </div>
+<% end %>
diff --git a/apps/workbench/app/views/projects/_show_permissions.html.erb b/apps/workbench/app/views/projects/_show_permissions.html.erb
deleted file mode 100644
index 0817062..0000000
--- a/apps/workbench/app/views/projects/_show_permissions.html.erb
+++ /dev/null
@@ -1,44 +0,0 @@
-<div>
-  <div class="row">
-    <div class="col-md-6 col-md-push-6">
-      <div class="panel panel-default">
-        <div class="panel-heading">
-          Additional permissions
-        </div>
-        <div class="panel-body">
-          <p>
-            <% if not @share_links.any? %>
-              <span class="deemphasize">(No additional permissions)</span>
-            <% else %>
-              Also shared with:
-              <% @share_links.andand.each do |link| %>
-                <br /><%= link_to_if_arvados_object link.tail_uuid, friendly_name: true %>
-              <% end %>
-            <% end %>
-          </p>
-        </div>
-      </div>
-    </div>
-    <div class="col-md-6 col-md-pull-6">
-      <% if @object.owner %>
-        <div class="panel panel-default">
-          <div class="panel-heading">
-            Inherited permissions
-          </div>
-          <div class="panel-body">
-            <p>Permissions for this project are inherited by the owner or parent project:
-            </p>
-            <p style="margin-left: 4em">
-              <% if User == resource_class_for_uuid(@object.owner_uuid) %>
-                <i class="fa fa-fw fa-user"></i>
-              <% else %>
-                <i class="fa fa-fw fa-folder"></i>
-              <% end %>
-              <%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %>
-            </p>
-          </div>
-        </div>
-      <% end %>
-    </div>
-  </div>
-</div>
diff --git a/apps/workbench/app/views/projects/_show_sharing.html.erb b/apps/workbench/app/views/projects/_show_sharing.html.erb
new file mode 100644
index 0000000..0185d77
--- /dev/null
+++ b/apps/workbench/app/views/projects/_show_sharing.html.erb
@@ -0,0 +1,104 @@
+<%
+   uuid_map = {}
+   [@users, @groups].each do |obj_list|
+     obj_list.each { |o| uuid_map[o.uuid] = o }
+   end
+   perm_name_desc_map = {}
+   perm_desc_name_map = {}
+   perms_json = []
+   ['Read', 'Write', 'Manage'].each do |link_desc|
+     link_name = "can_#{link_desc.downcase}"
+     perm_name_desc_map[link_name] = link_desc
+     perm_desc_name_map[link_desc] = link_name
+     perms_json << {value: link_name, text: link_desc}
+   end
+   perms_json = perms_json.to_json
+   manager = managed_by_user?
+   owner_icon = fa_icon_class_for_uuid(@object.owner_uuid)
+   if owner_icon == "fa-users"
+     owner_icon = "fa-folder"
+     owner_type = "parent project"
+   else
+     owner_type = "owning user"
+   end
+%>
+
+<% if manager %>
+<div class="pull-right">
+  <% ["users", "groups"].each do |share_class| %>
+
+  <%= link_to(send("choose_#{share_class}_path",
+      title: "Share with #{share_class}",
+      preview_pane: false,
+      multiple: true,
+      limit: 10000,
+      action_method: 'post',
+      action_href: share_with_project_path,
+      action_name: 'Add',
+      action_data: {selection_param: 'uuids[]', success: 'tab-refresh'}.to_json),
+      class: "btn btn-primary btn-sm", remote: true, method: 'get') do %>
+  <i class="fa fa-fw fa-plus"></i> Share with <%= share_class %>…
+  <% end %>
+
+  <% end %>
+</div>
+<% end %>
+
+<p>Permissions for this project are inherited from the <%= owner_type %>
+  <i class="fa fa-fw <%= owner_icon %>"></i>
+  <%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %>.
+</p>
+
+<table id="project_sharing" class="topalign table" style="clear: both; margin-top: 1em;">
+  <tr>
+    <th>User/Group Name</th>
+    <th<%= raw(' colspan="2"') if manager %>>Project Access</th>
+  </tr>
+
+  <% @share_links.andand.each do |link|
+       shared_with = uuid_map[link.tail_uuid]
+       if shared_with.nil?
+         link_name = link.tail_uuid
+       elsif shared_with.respond_to?(:full_name)
+         link_name = shared_with.full_name
+       else
+         link_name = shared_with.name
+       end %>
+  <tr data-object-uuid="<%= link.uuid %>">
+    <td>
+      <i class="fa fa-fw <%= fa_icon_class_for_uuid(link.tail_uuid) %>"></i>
+      <%= link_to_if_arvados_object(link.tail_uuid, link_text: link_name) %>
+    </td>
+    <% if manager %>
+    <td><%= link_to perm_name_desc_map[link.name], '#', {
+      "data-emptytext" => "Read",
+      "data-placement" => "bottom",
+      "data-type" => "select",
+      "data-url" => url_for(action: "update", id: link.uuid, controller: "links", merge: true),
+      "data-title" => "Set #{link_name}'s access level",
+      "data-name" => "[name]",
+      "data-pk" => {id: link.tail_uuid, key: "link"}.to_json,
+      "data-value" => link.name,
+      "data-clear" => false,
+      "data-source" => perms_json,
+      "data-tpl" => "<select id=\"share_change_level\"></select>",
+      "class" => "editable form-control",
+      } %>
+    </td>
+    <td>
+      <%= link_to(
+          {action: 'destroy', id: link.uuid, controller: "links"},
+          {title: 'Revoke', class: 'btn btn-default btn-nodecorate', method: :delete,
+           data: {confirm: "Revoke #{link_name}'s access to this project?",
+                  remote: true}}) do %>
+      <i class="fa fa-fw fa-trash-o"></i>
+      <% end %>
+    </td>
+    <% else %>
+    <td>
+      <%= perm_name_desc_map[link.name] %>
+    </td>
+    <% end %>
+  </tr>
+  <% end %>
+</table>
diff --git a/apps/workbench/app/views/users/_choose_rows.html.erb b/apps/workbench/app/views/users/_choose_rows.html.erb
new file mode 100644
index 0000000..a893e5e
--- /dev/null
+++ b/apps/workbench/app/views/users/_choose_rows.html.erb
@@ -0,0 +1,9 @@
+<% icon_class = fa_icon_class_for_class(User) %>
+<% @objects.each do |object| %>
+  <div class="row filterable selectable <%= 'multiple' if multiple %>" data-object-uuid="<%= object.uuid %>">
+    <div class="col-sm-12" style="overflow-x:hidden">
+      <i class="fa fa-fw <%= icon_class %>"></i>
+      <%= object.full_name %>
+    </div>
+  </div>
+<% end %>
diff --git a/apps/workbench/config/routes.rb b/apps/workbench/config/routes.rb
index 9701f41..55e2fda 100644
--- a/apps/workbench/config/routes.rb
+++ b/apps/workbench/config/routes.rb
@@ -24,6 +24,7 @@ ArvadosWorkbench::Application.routes.draw do
   match '/logout' => 'sessions#destroy', via: [:get, :post]
   get '/logged_out' => 'sessions#index'
   resources :users do
+    get 'choose', :on => :collection
     get 'home', :on => :member
     get 'welcome', :on => :collection
     get 'activity', :on => :collection
@@ -35,7 +36,9 @@ ArvadosWorkbench::Application.routes.draw do
   resources :logs
   resources :factory_jobs
   resources :uploaded_datasets
-  resources :groups
+  resources :groups do
+    get 'choose', on: :collection
+  end
   resources :specimens
   resources :pipeline_templates do
     get 'choose', on: :collection
diff --git a/apps/workbench/test/integration/projects_test.rb b/apps/workbench/test/integration/projects_test.rb
index 5758fc7..846c57a 100644
--- a/apps/workbench/test/integration/projects_test.rb
+++ b/apps/workbench/test/integration/projects_test.rb
@@ -75,13 +75,83 @@ class ProjectsTest < ActionDispatch::IntegrationTest
     find('.modal-footer a,button', text: 'Move').click
     wait_for_ajax
 
-    # Wait for the page to refresh and show the new parent in Permissions panel
-    click_link 'Permissions'
-    find('.panel', text: 'Project 1234')
-
-    assert(find('.panel', text: 'Permissions for this project are inherited by the owner or parent project').
-           all('*', text: 'Project 1234').any?,
+    # Wait for the page to refresh and show the new parent in Sharing panel
+    click_link 'Sharing'
+    assert(page.has_link?("Project 1234"),
            "Project 5678 should now be inside project 1234")
   end
 
+  def show_project_using(auth_key, proj_key='aproject')
+    project_uuid = api_fixture('groups')[proj_key]['uuid']
+    visit(page_with_token(auth_key, "/projects/#{project_uuid}"))
+    assert(page.has_text?("A Project"), "not on expected project page")
+  end
+
+  def count_shares
+    page.all("#project_sharing tr").size
+  end
+
+  def add_share_and_check(share_type, name)
+    start_share_count = count_shares
+    click_on("Share with #{share_type}")
+    find(".selectable", text: name).click
+    find(".modal-footer a,button", text: "Add").click
+    # click_on "Sharing"; puts "FIXME: Manually re-clicking the Sharing tab shouldn't be necessary"
+    assert(page.has_link?(name),
+           "new share was not added to sharing table")
+    assert_equal(start_share_count + 1, count_shares,
+                 "new share did not add row to sharing table")
+  end
+
+  def modify_share_and_check(name)
+    start_share_count = count_shares
+    link_row = all("#project_sharing tr").select { |row| row.has_text?(name) }
+    assert_equal(1, link_row.size, "row with new permission not found")
+    within(link_row.first) do
+      click_on("Read")
+      select("Write", from: "share_change_level")
+      click_on("editable-submit")
+      assert(has_link?("Write"),
+             "failed to change access level on new share")
+      click_on "Revoke"
+    end
+    assert(page.has_no_text?(name),
+           "new share row still exists after being revoked")
+    assert_equal(start_share_count - 1, count_shares,
+                 "revoking share did not remove row from sharing table")
+  end
+
+  test "project viewer can see project sharing, but not change it" do
+    show_project_using("project_viewer")
+    click_on "Sharing"
+    assert(page.has_text?("Project Viewer"), "did not find self on sharing tab")
+    assert(page.has_no_link?(/add a (user|group)/i),
+           "read-only project user given option to add permissions")
+    assert_empty(all("#project_sharing a").
+                 reject { |a| a[:href] =~ %r{/(users|groups)/[-0-9a-z]+$} },
+                 "read-only project user given option to modify permissions")
+  end
+
+  test "project owner can manage sharing for another user" do
+    add_user = api_fixture('users')['future_project_user']
+    new_name = ["first_name", "last_name"].map { |k| add_user[k] }.join(" ")
+
+    show_project_using("active")
+    click_on "Sharing"
+    assert(page.has_no_text?(new_name),
+           "project is already shared with user to add")
+    add_share_and_check("users", new_name)
+    modify_share_and_check(new_name)
+  end
+
+  test "project owner can manage sharing for another group" do
+    new_name = api_fixture('groups')['future_project_viewing_group']['name']
+
+    show_project_using("active")
+    click_on "Sharing"
+    assert(page.has_no_text?(new_name),
+           "project is already shared with group to add")
+    add_share_and_check("groups", new_name)
+    modify_share_and_check(new_name)
+  end
 end

commit c319de3e3107dbd9820f49e1a5f8d019accb8783
Author: Brett Smith <brett at curoverse.com>
Date:   Tue Jul 15 15:39:10 2014 -0400

    2044: Workbench route to share a project with others.
    
    This route is intended to support AJAX requests from the interface to
    share a project with one or more users/groups.

diff --git a/apps/workbench/app/controllers/projects_controller.rb b/apps/workbench/app/controllers/projects_controller.rb
index 91d6e8a..352b0ae 100644
--- a/apps/workbench/app/controllers/projects_controller.rb
+++ b/apps/workbench/app/controllers/projects_controller.rb
@@ -131,4 +131,26 @@ class ProjectsController < ApplicationController
     @updates = params['project']
     super
   end
+
+  def share_with
+    if not params[:uuids].andand.any?
+      @errors = ["No user/group UUIDs specified to share with."]
+      return render_error(status: 422)
+    end
+    results = {"success" => [], "failure" => []}
+    params[:uuids].each do |shared_uuid|
+      begin
+        Link.create(tail_uuid: shared_uuid, link_class: "permission",
+                    name: "can_read", head_uuid: @object.uuid)
+      rescue ArvadosApiClient::ApiError => error
+        results["failure"] << shared_uuid
+      else
+        results["success"] << shared_uuid
+      end
+    end
+    status = (results["failure"].empty?) ? 200 : 422
+    respond_to do |f|
+      f.json { render(json: results, status: status) }
+    end
+  end
 end
diff --git a/apps/workbench/config/routes.rb b/apps/workbench/config/routes.rb
index a4f69b3..9701f41 100644
--- a/apps/workbench/config/routes.rb
+++ b/apps/workbench/config/routes.rb
@@ -61,6 +61,7 @@ ArvadosWorkbench::Application.routes.draw do
     match 'remove/:item_uuid', on: :member, via: :delete, action: :remove_item
     match 'remove_items', on: :member, via: :delete, action: :remove_items
     get 'choose', on: :collection
+    post 'share_with', on: :member
   end
 
   post 'actions' => 'actions#post'
diff --git a/apps/workbench/test/functional/projects_controller_test.rb b/apps/workbench/test/functional/projects_controller_test.rb
index b338aa8..86f525d 100644
--- a/apps/workbench/test/functional/projects_controller_test.rb
+++ b/apps/workbench/test/functional/projects_controller_test.rb
@@ -9,4 +9,17 @@ class ProjectsControllerTest < ActionController::TestCase
     assert_template 'user_agreements/index',
     "Inactive user was not presented with a user agreement at the front page"
   end
+
+  test "sharing a project with a user and group" do
+    uuid_list = [api_fixture("groups")["future_project_viewing_group"]["uuid"],
+                 api_fixture("users")["future_project_user"]["uuid"]]
+    post(:share_with, {
+           id: api_fixture("groups")["asubproject"]["uuid"],
+           format: "json",
+           uuids: uuid_list},
+         session_for(:active))
+    assert_response :success
+    json_response = Oj.load(@response.body)
+    assert_equal(uuid_list, json_response["success"])
+  end
 end
diff --git a/services/api/test/fixtures/groups.yml b/services/api/test/fixtures/groups.yml
index cd6157b..62b9958 100644
--- a/services/api/test/fixtures/groups.yml
+++ b/services/api/test/fixtures/groups.yml
@@ -68,6 +68,17 @@ asubproject:
   description: "Test project belonging to active user's first test project"
   group_class: folder
 
+future_project_viewing_group:
+  uuid: zzzzz-j7d0g-futrprojviewgrp
+  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: 2014-04-21 15:37:48 -0400
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  modified_at: 2014-04-21 15:37:48 -0400
+  updated_at: 2014-04-21 15:37:48 -0400
+  name: Future Project Viewing Group
+  description: "Group used to test granting Group Project viewing"
+
 bad_group_has_ownership_cycle_a:
   uuid: zzzzz-j7d0g-cx2al9cqkmsf1hs
   owner_uuid: zzzzz-j7d0g-0077nzts8c178lw
diff --git a/services/api/test/fixtures/users.yml b/services/api/test/fixtures/users.yml
index f6d5b21..08976b8 100644
--- a/services/api/test/fixtures/users.yml
+++ b/services/api/test/fixtures/users.yml
@@ -50,6 +50,17 @@ project_viewer:
   is_admin: false
   prefs: {}
 
+future_project_user:
+  # Workbench tests give this user permission on aproject.
+  uuid: zzzzz-tpzed-futureprojview2
+  email: future-project-user at arvados.local
+  first_name: Future Project
+  last_name: User
+  identity_url: https://future-project-user.openid.local
+  is_active: true
+  is_admin: false
+  prefs: {}
+
 spectator:
   uuid: zzzzz-tpzed-l1s2piq4t4mps8r
   email: spectator at arvados.local

commit 6ea6e2523b7c9fdd5e8816a3c9d20512d5779ac1
Author: Brett Smith <brett at curoverse.com>
Date:   Tue Jul 15 15:35:12 2014 -0400

    2044: Add more helpers to get FA icons for objects.

diff --git a/apps/workbench/app/helpers/application_helper.rb b/apps/workbench/app/helpers/application_helper.rb
index d1bac0c..c3856c2 100644
--- a/apps/workbench/app/helpers/application_helper.rb
+++ b/apps/workbench/app/helpers/application_helper.rb
@@ -393,38 +393,39 @@ module ApplicationHelper
     end
   end
 
-  def fa_icon_class_for_object object
-    case object.class.to_s.to_sym
-    when :User
-      'fa-user'
-    when :Group
+  RESOURCE_CLASS_ICONS = {
+    "Collection" => "fa-archive",
+    "Group" => "fa-users",
+    "Human" => "fa-male",  # FIXME: Use a more inclusive icon.
+    "Job" => "fa-gears",
+    "KeepDisk" => "fa-hdd-o",
+    "KeepService" => "fa-exchange",
+    "Link" => "fa-arrows-h",
+    "Node" => "fa-cloud",
+    "PipelineInstance" => "fa-gears",
+    "PipelineTemplate" => "fa-gears",
+    "Repository" => "fa-code-fork",
+    "Specimen" => "fa-flask",
+    "Trait" => "fa-clipboard",
+    "User" => "fa-user",
+    "VirtualMachine" => "fa-terminal",
+  }
+  DEFAULT_ICON_CLASS = "fa-cube"
+
+  def fa_icon_class_for_class(resource_class, default=DEFAULT_ICON_CLASS)
+    RESOURCE_CLASS_ICONS.fetch(resource_class.to_s, default)
+  end
+
+  def fa_icon_class_for_uuid(uuid, default=DEFAULT_ICON_CLASS)
+    fa_icon_class_for_class(resource_class_for_uuid(uuid), default)
+  end
+
+  def fa_icon_class_for_object(object, default=DEFAULT_ICON_CLASS)
+    case class_name = object.class.to_s
+    when "Group"
       object.group_class ? 'fa-folder' : 'fa-users'
-    when :Job, :PipelineInstance, :PipelineTemplate
-      'fa-gears'
-    when :Collection
-      'fa-archive'
-    when :Specimen
-      'fa-flask'
-    when :Trait
-      'fa-clipboard'
-    when :Human
-      'fa-male'
-    when :VirtualMachine
-      'fa-terminal'
-    when :Repository
-      'fa-code-fork'
-    when :Link
-      'fa-arrows-h'
-    when :User
-      'fa-user'
-    when :Node
-      'fa-cloud'
-    when :KeepService
-      'fa-exchange'
-    when :KeepDisk
-      'fa-hdd-o'
     else
-      'fa-cube'
+      RESOURCE_CLASS_ICONS.fetch(class_name, default)
     end
   end
 end

commit ff9323418d6a13da701be80aee9e3134e8aa6bab
Author: Brett Smith <brett at curoverse.com>
Date:   Tue Jul 15 14:30:12 2014 -0400

    2044: Let users read permission links for groups they're in.
    
    This lets everyone in a project see basic information about who has
    access to a project.

diff --git a/services/api/app/models/arvados_model.rb b/services/api/app/models/arvados_model.rb
index 1ea9332..9a806d3 100644
--- a/services/api/app/models/arvados_model.rb
+++ b/services/api/app/models/arvados_model.rb
@@ -130,11 +130,11 @@ class ArvadosModel < ActiveRecord::Base
       sql_params += [uuid_list, user_uuids]
 
       if sql_table == "links" and users_list.any?
-        # This row is a 'permission' or 'resources' link class
-        # The uuid for a member of users_list is referenced in either the head
-        # or tail of the link
+        # This row is a 'permission' or 'resources' link class that
+        # references a member of users_list or a group readable by
+        # those users.
         sql_conds += ["(#{sql_table}.link_class in (#{sanitize 'permission'}, #{sanitize 'resources'}) AND (#{sql_table}.head_uuid IN (?) OR #{sql_table}.tail_uuid IN (?)))"]
-        sql_params += [user_uuids, user_uuids]
+        sql_params += [uuid_list, uuid_list]
       end
 
       if sql_table == "logs" and users_list.any?
diff --git a/services/api/test/fixtures/links.yml b/services/api/test/fixtures/links.yml
index 313db7f..1227618 100644
--- a/services/api/test/fixtures/links.yml
+++ b/services/api/test/fixtures/links.yml
@@ -318,6 +318,23 @@ test_timestamps:
   name: test
   properties: {}
 
+admin_can_write_aproject:
+  # Yes, this permission is effectively redundant.
+  # We use it to test that other project admins can see
+  # all the project's sharing.
+  uuid: zzzzz-o0j2j-adminmgsproject
+  owner_uuid: zzzzz-tpzed-000000000000000
+  created_at: 2014-01-24 20:42:26 -0800
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  modified_at: 2014-01-24 20:42:26 -0800
+  updated_at: 2014-01-24 20:42:26 -0800
+  tail_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  link_class: permission
+  name: can_write
+  head_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
+  properties: {}
+
 project_viewer_can_read_project:
   uuid: zzzzz-o0j2j-projviewerreadp
   owner_uuid: zzzzz-tpzed-000000000000000
diff --git a/services/api/test/functional/arvados/v1/links_controller_test.rb b/services/api/test/functional/arvados/v1/links_controller_test.rb
index d5b4266..9dfc350 100644
--- a/services/api/test/functional/arvados/v1/links_controller_test.rb
+++ b/services/api/test/functional/arvados/v1/links_controller_test.rb
@@ -283,4 +283,15 @@ class Arvados::V1::LinksControllerTest < ActionController::TestCase
     }
     assert_response 422
   end
+
+  test "project owner sees project's permission links" do
+    authorize_with :active
+    get :index, filters: [['head_uuid', '=', groups(:aproject).uuid]]
+    uuid_list = assigns(:objects).andand.map(&:uuid)
+    assert_not_nil(uuid_list, "no index objects assigned")
+    [:admin_can_write_aproject, :project_viewer_can_read_project].each do |lsym|
+      assert_includes(uuid_list, links(lsym).uuid,
+                      "#{lsym} missing from project permission index")
+    end
+  end
 end

commit 36ed0d796ef167c0c795a1d6ba9d8d25f5c446ff
Author: Brett Smith <brett at curoverse.com>
Date:   Mon Jul 14 17:07:39 2014 -0400

    2044: All users can give all other users folder permissions.
    
    This supports a Workbench sharing button, the same way that the
    changes to users#index does.

diff --git a/services/api/app/models/link.rb b/services/api/app/models/link.rb
index bb069ee..4852461 100644
--- a/services/api/app/models/link.rb
+++ b/services/api/app/models/link.rb
@@ -104,4 +104,14 @@ class Link < ArvadosModel
       super
     end
   end
+
+  # A user can give all other users permissions on folders.
+  def skip_uuid_read_permission_check
+    skipped_attrs = super
+    if (ArvadosModel.resource_class_for_uuid(head_uuid) == Group) and
+        (ArvadosModel.resource_class_for_uuid(tail_uuid) == User)
+      skipped_attrs << "tail_uuid"
+    end
+    skipped_attrs
+  end
 end
diff --git a/services/api/test/unit/link_test.rb b/services/api/test/unit/link_test.rb
index e403265..5bd8038 100644
--- a/services/api/test/unit/link_test.rb
+++ b/services/api/test/unit/link_test.rb
@@ -61,4 +61,29 @@ class LinkTest < ActiveSupport::TestCase
       ob.destroy
     end
   end
+
+  def make_active_perm(perm_attrs)
+    set_user_from_auth :active
+    Link.create({link_class: "permission",
+                 name: "can_read",
+                 head_uuid: groups(:aproject).uuid,
+                }.merge(perm_attrs))
+  end
+
+  test "link granting permission to nonexistent user is invalid" do
+    link = make_active_perm(tail_uuid:
+                            users(:active).uuid.sub(/-\w+$/, "-#{'z' * 15}"))
+    refute link.valid?
+  end
+
+  test "link granting non-project permission to unreadable user is invalid" do
+    link = make_active_perm(tail_uuid: users(:admin).uuid,
+                            head_uuid: collections(:bar_file).uuid)
+    refute link.valid?
+  end
+
+  test "link granting project permissions to unreadable user is valid" do
+    link = make_active_perm(tail_uuid: users(:admin).uuid)
+    assert link.valid?
+  end
 end

commit 1c7011593c295718e03eaf540b3a90074e3d47c8
Author: Brett Smith <brett at curoverse.com>
Date:   Thu Jul 3 14:10:31 2014 -0400

    2044: Workbench supports API select parameter.
    
    This can help ensure symmetry between admin and non-admin requests for
    the user index.

diff --git a/apps/workbench/app/models/arvados_base.rb b/apps/workbench/app/models/arvados_base.rb
index 6d427fd..1dac43f 100644
--- a/apps/workbench/app/models/arvados_base.rb
+++ b/apps/workbench/app/models/arvados_base.rb
@@ -131,6 +131,10 @@ class ArvadosBase < ActiveRecord::Base
     ArvadosResourceList.new(self).limit(*args)
   end
 
+  def self.select(*args)
+    ArvadosResourceList.new(self).select(*args)
+  end
+
   def self.eager(*args)
     ArvadosResourceList.new(self).eager(*args)
   end
diff --git a/apps/workbench/app/models/arvados_resource_list.rb b/apps/workbench/app/models/arvados_resource_list.rb
index dedd18c..3164c79 100644
--- a/apps/workbench/app/models/arvados_resource_list.rb
+++ b/apps/workbench/app/models/arvados_resource_list.rb
@@ -26,6 +26,12 @@ class ArvadosResourceList
     self
   end
 
+  def select(columns)
+    @select ||= []
+    @select += columns
+    self
+  end
+
   def filter _filters
     @filters ||= []
     @filters += _filters
@@ -64,6 +70,7 @@ class ArvadosResourceList
     api_params[:eager] = '1' if @eager
     api_params[:limit] = @limit if @limit
     api_params[:offset] = @offset if @offset
+    api_params[:select] = @select if @select
     api_params[:order] = @orderby_spec if @orderby_spec
     api_params[:filters] = @filters if @filters
     res = arvados_api_client.api @resource_class, '', api_params
diff --git a/apps/workbench/test/unit/group_test.rb b/apps/workbench/test/unit/group_test.rb
index 435463a..c555c4a 100644
--- a/apps/workbench/test/unit/group_test.rb
+++ b/apps/workbench/test/unit/group_test.rb
@@ -25,4 +25,13 @@ class GroupTest < ActiveSupport::TestCase
     assert_equal(expect_name, oi.name_for(expect_uuid),
                  "Expected name_for '#{expect_uuid}' to be '#{expect_name}'")
   end
+
+  test "can select specific group columns" do
+    use_token :admin
+    Group.select(["uuid", "name"]).limit(5).each do |user|
+      assert_not_nil user.uuid
+      assert_not_nil user.name
+      assert_nil user.owner_uuid
+    end
+  end
 end
diff --git a/apps/workbench/test/unit/user_test.rb b/apps/workbench/test/unit/user_test.rb
index 20cde7e..89e95df 100644
--- a/apps/workbench/test/unit/user_test.rb
+++ b/apps/workbench/test/unit/user_test.rb
@@ -1,4 +1,12 @@
 require 'test_helper'
 
 class UserTest < ActiveSupport::TestCase
+  test "can select specific user columns" do
+    use_token :admin
+    User.select(["uuid", "is_active"]).limit(5).each do |user|
+      assert_not_nil user.uuid
+      assert_not_nil user.is_active
+      assert_nil user.first_name
+    end
+  end
 end

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list