add user#is_active flag
authorTom Clegg <tom@clinicalfuture.com>
Wed, 26 Jun 2013 02:22:19 +0000 (22:22 -0400)
committerTom Clegg <tom@clinicalfuture.com>
Wed, 26 Jun 2013 02:22:19 +0000 (22:22 -0400)
17 files changed:
apps/workbench/app/views/layouts/application.html.erb
apps/workbench/app/views/users/_request_activation.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/home.html.erb
apps/workbench/config/environments/development.rb.example
apps/workbench/config/environments/production.rb
apps/workbench/config/environments/test.rb
doc/admin/cheat_sheet.textile
doc/install/install-api-server.md
services/api/app/controllers/user_sessions_controller.rb
services/api/app/models/api_client_authorization.rb
services/api/app/models/arvados_model.rb
services/api/app/models/user.rb
services/api/config/environments/development.rb.example
services/api/config/environments/production.rb.example
services/api/config/environments/test.rb.example
services/api/db/migrate/20130626002829_add_is_active_to_users.rb [new file with mode: 0644]
services/api/db/schema.rb

index 8df4bbf600f9e960a14e7335203490092bd1d6ba..8e7039c54477384969cf2b66afa1a0d67e715721 100644 (file)
@@ -37,6 +37,7 @@
         </a>
         <a class="brand" href="/"><%= Rails.configuration.site_name rescue Rails.application.class.parent_name %></a>
         <div class="nav-collapse collapse">
+          <% if current_user.andand.is_active %>
           <ul class="nav">
             <li class="dropdown">
               <a href="#" class="dropdown-toggle" data-toggle="dropdown">
@@ -60,6 +61,7 @@
               </ul>
             </li>
          </ul>
+          <% end %>
          <ul class="nav pull-right">
       <% if current_user -%>
             <li><span class="badge badge-info" style="margin: 10px auto 10px; padding-top: 4px; padding-bottom: 4px"><%= current_user.email %></span></li>
diff --git a/apps/workbench/app/views/users/_request_activation.html.erb b/apps/workbench/app/views/users/_request_activation.html.erb
new file mode 100644 (file)
index 0000000..d214827
--- /dev/null
@@ -0,0 +1,11 @@
+<h4>Your account</h4>
+<div class="well clearfix">
+  <div>
+    Status: <strong>New / inactive</strong>
+  </div>
+  <br />
+  We will send you an email when your account is activated. If this
+  hasn&rsquo;t happened after 24 hours, please let us know.
+  <br />
+  <%= link_to raw('Contact us &#x2709;'), Rails.configuration.activation_contact_link, class: "pull-right btn btn-primary" %>
+</div>
index 6f52694070f64b4fd577544f33bb3db13998d774..28e6a5bcc271cb801a95225b43c0baec9df57231 100644 (file)
 </div>
 <% end %>
 
+<% if current_user.andand.is_active %>
+<% content_for :manage_access do %>
+<h4>Setup</h4>
+<div class="well clearfix">
+  <div>
+    <strong>SSH keys</strong>
+    <span class="badge <%= 'badge-success' if @my_ssh_keys.any? %> pull-right"><%= @my_ssh_keys.count %></span>
+  </div>
+  You&rsquo;ll use public key authentication when logging in to a
+  VM or use a hosted git repository.
+  <br />
+  <%= link_to raw('Add/edit keys &#x279c;'), authorized_keys_path, class: "pull-right btn #{'btn-primary' if @my_ssh_keys.empty?}" %>
+</div>
+<div class="well clearfix">
+  <div>
+    <strong>Virtual machines</strong>
+    <span class="badge <%= 'badge-success' if @my_vm_perms.any? %> pull-right"><%= @my_vm_perms.collect(&:head_uuid).uniq.count %></span>
+  </div>
+  Arvados includes virtual machines with SDKs installed and ready to use.
+  <br />
+  <% if @my_vm_perms.any? %>
+  <%= link_to raw('Show VMs &#x279c;'), virtual_machines_path, class: "pull-right btn" %>
+  <% elsif @my_ssh_keys.any? %>
+  <%= link_to raw('Request a VM &#x279c;'), virtual_machines_path, class: "pull-right btn btn-primary" %>
+  <% else %>
+  <%= link_to raw('Request a VM &#x279c;'), virtual_machines_path, { :class => "pull-right btn disabled", :"data-toggle" => "tooltip", :"data-placement" => "bottom", :title => "Add an SSH public key first!" } %>
+  <% end %>
+</div>
+<div class="well clearfix">
+  <div>
+    <strong>Git repositories</strong>
+    <span class="badge <%= 'badge-success' if @my_repo_perms.any? %> pull-right"><%= @my_repo_perms.collect(&:head_uuid).uniq.count %></span>
+  </div>
+  In order to run jobs using your own code, you need to push your code to a git repository. We provide hosted git repositories to make this easy.
+  <br />
+  <% if @my_repo_perms.any? %>
+  <%= link_to raw('Show repositories &#x279c;'), repositories_path, class: "pull-right btn" %>
+  <% elsif @my_ssh_keys.any? %>
+  <%= link_to raw('Request a repository &#x279c;'), repositories_path, class: "pull-right btn btn-primary" %>
+  <% else %>
+  <%= link_to raw('Request a repository &#x279c;'), repositories_path, { :class => "pull-right btn disabled", :"data-toggle" => "tooltip", :"data-placement" => "bottom", :title => "Add an SSH public key first!" } %>
+  <% end %>
+</div>
+<% end %>
+<% end %>
+
 <div class="row-fluid">
   <div class="col span4">
-    <h4>Setup</h4>
-    <div class="well clearfix">
-      <div>
-        <strong>SSH keys</strong>
-        <span class="badge <%= 'badge-success' if @my_ssh_keys.any? %> pull-right"><%= @my_ssh_keys.count %></span>
-      </div>
-      You&rsquo;ll use public key authentication when logging in to a
-      VM or use a hosted git repository.
-      <br />
-      <%= link_to raw('Add/edit keys &#x279c;'), authorized_keys_path, class: "pull-right btn #{'btn-primary' if @my_ssh_keys.empty?}" %>
-    </div>
-    <div class="well clearfix">
-      <div>
-        <strong>Virtual machines</strong>
-        <span class="badge <%= 'badge-success' if @my_vm_perms.any? %> pull-right"><%= @my_vm_perms.collect(&:head_uuid).uniq.count %></span>
-      </div>
-      Arvados includes virtual machines with SDKs installed and ready to use.
-      <br />
-      <% if @my_vm_perms.any? %>
-      <%= link_to raw('Show VMs &#x279c;'), virtual_machines_path, class: "pull-right btn" %>
-      <% elsif @my_ssh_keys.any? %>
-      <%= link_to raw('Request a VM &#x279c;'), virtual_machines_path, class: "pull-right btn btn-primary" %>
-      <% else %>
-      <%= link_to raw('Request a VM &#x279c;'), virtual_machines_path, { :class => "pull-right btn disabled", :"data-toggle" => "tooltip", :"data-placement" => "bottom", :title => "Add an SSH public key first!" } %>
-      <% end %>
-    </div>
-    <div class="well clearfix">
-      <div>
-        <strong>Git repositories</strong>
-        <span class="badge <%= 'badge-success' if @my_repo_perms.any? %> pull-right"><%= @my_repo_perms.collect(&:head_uuid).uniq.count %></span>
-      </div>
-      In order to run jobs using your own code, you need to push your code to a git repository. We provide hosted git repositories to make this easy.
-      <br />
-      <% if @my_repo_perms.any? %>
-      <%= link_to raw('Show repositories &#x279c;'), repositories_path, class: "pull-right btn" %>
-      <% elsif @my_ssh_keys.any? %>
-      <%= link_to raw('Request a repository &#x279c;'), repositories_path, class: "pull-right btn btn-primary" %>
-      <% else %>
-      <%= link_to raw('Request a repository &#x279c;'), repositories_path, { :class => "pull-right btn disabled", :"data-toggle" => "tooltip", :"data-placement" => "bottom", :title => "Add an SSH public key first!" } %>
-      <% end %>
-    </div>
+    <% if content_for? :manage_access %>
+    <%= yield :manage_access %>
+    <% else %>
+    <%= render partial: 'request_activation' %>
+    <% end %>
   </div>
 
   <% if content_for? :tutorials %>
index 86104aacc9a75eedf88b04a36f4846e1f3d26c97..90422a006c59dea5f96ecde0083fc7357898728c 100644 (file)
@@ -45,4 +45,5 @@ ArvadosWorkbench::Application.configure do
   config.secret_token = File.read('config/.secret_token') if File.exist? 'config/.secret_token'
 
   config.site_name = 'arvados-workbench.example.com'
+  config.activation_contact_link = 'mailto:info@arvados.org'
 end
index ef7865ae250b0250cb2412aca8ed524129d12d8d..07ed2957cb6745a2beaee98450f4d379fb641169 100644 (file)
@@ -79,4 +79,5 @@ ArvadosWorkbench::Application.configure do
   config.vcf_pipeline_uuid = '9ujm1-mxsvm-o62u4mdoxvs0ckp'
 
   config.site_name = 'arvados-workbench.example.com'
+  config.activation_contact_link = 'mailto:info@arvados.org'
 end
index c2d260f2dfab23cfe031e0fd87acc4141302191b..05c8cedd4abafb0469d2ae39ff130ad1106d8906 100644 (file)
@@ -49,4 +49,5 @@ ArvadosWorkbench::Application.configure do
   config.vcf_pipeline_uuid = '9ujm1-mxsvm-o62u4mdoxvs0ckp'
 
   config.site_name = 'arvados-workbench.example.com'
+  config.activation_contact_link = 'mailto:info@arvados.org'
 end
index 0f340aaf2ef4b9ac983015f0900a47116ef26862..053b3672e4b9f3f68b8560871b583ce99c8de2be 100644 (file)
@@ -20,6 +20,14 @@ h3. Create VM
 arv virtual_machine create --virtual_machine '{"hostname":"xxxxxxxchangeme.example.com"}'
 </pre>
 
+h3. Activate user
+
+<pre>
+user_uuid=xxxxxxxchangeme
+
+arv user update --uuid "$user_uuid" --user '{"is_active":true}'
+</pre>
+
 h3. User &rarr; VM
 
 Give @$user_uuid@ permission to log in to @$vm_uuid@ as @$target_username@
index 5106ee86212144db948de4461fe63cc2ce95da8e..c830e173b239eae1a859088b1d12e2e3860ba0b7 100644 (file)
@@ -97,7 +97,7 @@ In the rails console:
 
     Thread.current[:user] = User.find(1)
     Thread.current[:user].is_admin = true
-    User.find(1).update_attributes is_admin: true
+    User.find(1).update_attributes is_admin: true, is_active: true
     User.find(1).is_admin
 
 This should be
index 537c1aa4110e9fecf2a4b06a8238899a048288ca..961ab4e8bb00456f25df093ea1bfb7e130a9aa66 100644 (file)
@@ -28,7 +28,8 @@ class UserSessionsController < ApplicationController
       user = User.new(:email => omniauth['info']['email'],
                       :first_name => omniauth['info']['first_name'],
                       :last_name => omniauth['info']['last_name'],
-                      :identity_url => omniauth['info']['identity_url'])
+                      :identity_url => omniauth['info']['identity_url'],
+                      :is_active => Rails.configuration.new_users_are_active)
     else
       user.email = omniauth['info']['email']
       user.first_name = omniauth['info']['first_name']
index 1c4972c124d695b06de90ae7698062f4e3157e9f..c71d0dce4b7aa7abcc5170e641066bfb448b708e 100644 (file)
@@ -57,4 +57,16 @@ class ApiClientAuthorization < ArvadosModel
     nil
   end
   def modified_at=(x) end
+
+  protected
+
+  def permission_to_create
+    current_user.andand.is_admin or (current_user.andand.id == self.user_id)
+  end
+
+  def permission_to_update
+    (permission_to_create and
+     not self.user_id_changed? and
+     not self.owner_changed?)
+  end
 end
index 0069584f6573a1dfcf2c06ade6e42d127104b230..c5c3dd2579111ba0d17a7f776996d43a30d88422 100644 (file)
@@ -42,7 +42,7 @@ class ArvadosModel < ActiveRecord::Base
   end
 
   def permission_to_create
-    current_user
+    current_user.andand.is_active
   end
 
   def ensure_permission_to_update
@@ -54,6 +54,10 @@ class ArvadosModel < ActiveRecord::Base
       logger.warn "Anonymous user tried to update #{self.class.to_s} #{self.uuid_was}"
       return false
     end
+    if !current_user.is_active
+      logger.warn "Inactive user #{current_user.uuid} tried to update #{self.class.to_s} #{self.uuid_was}"
+      return false
+    end
     if self.uuid_changed?
       logger.warn "User #{current_user.uuid} tried to change uuid of #{self.class.to_s} #{self.uuid_was} to #{self.uuid}"
       return false
index bd4de977cad91301b630ef24d82082f8a31787a8..49eb3c93b43b2c6132cb742941c2a91fd5ce3ee7 100644 (file)
@@ -5,6 +5,7 @@ class User < ArvadosModel
   serialize :prefs, Hash
   has_many :api_client_authorizations
   before_update :prevent_privilege_escalation
+  before_update :prevent_inactive_admin
 
   has_many :authorized_keys, :foreign_key => :authorized_user_uuid, :primary_key => :uuid
 
@@ -14,6 +15,7 @@ class User < ArvadosModel
     t.add :first_name
     t.add :last_name
     t.add :identity_url
+    t.add :is_active
     t.add :is_admin
     t.add :prefs
   end
@@ -53,23 +55,47 @@ class User < ArvadosModel
 
   protected
 
+  def permission_to_update
+    # users must be able to update themselves (even if they are
+    # inactive) in order to create sessions
+    self == current_user or super
+  end
+
   def permission_to_create
-    Thread.current[:user] == self or
-      (Thread.current[:user] and Thread.current[:user].is_admin)
+    current_user.andand.is_admin or
+      (self == current_user and
+       self.is_active == Rails.configuration.new_users_are_active)
   end
 
   def prevent_privilege_escalation
-    if self.is_admin_changed? and !current_user.is_admin
-      if current_user.uuid == self.uuid
-        if self.is_admin != self.is_admin_was
-          logger.warn "User #{self.uuid} tried to change is_admin from #{self.is_admin_was} to #{self.is_admin}"
-          self.is_admin = self.is_admin_was
-        end
+    if current_user.andand.is_admin
+      return true
+    end
+    if self.is_active_changed?
+      if self.is_active != self.is_active_was
+        logger.warn "User #{current_user.uuid} tried to change is_active from #{self.is_admin_was} to #{self.is_admin} for #{self.uuid}"
+        self.is_active = self.is_active_was
+      end
+    end
+    if self.is_admin_changed?
+      if self.is_admin != self.is_admin_was
+        logger.warn "User #{current_user.uuid} tried to change is_admin from #{self.is_admin_was} to #{self.is_admin} for #{self.uuid}"
+        self.is_admin = self.is_admin_was
       end
     end
     true
   end
 
+  def prevent_inactive_admin
+    if self.is_admin and not self.is_active
+      # There is no known use case for the strange set of permissions
+      # that would result from this change. It's safest to assume it's
+      # a mistake and disallow it outright.
+      raise "Admin users cannot be inactive"
+    end
+    true
+  end
+
   def group_permissions
     Rails.cache.fetch "groups_for_user_#{self.uuid}" do
       permissions_from = {}
index 34e1fab9f5fa855e969f1869fcbfcfbf9b4d2074..aea6d06c4998417fb9e671c481cdb43249e985bd 100644 (file)
@@ -51,4 +51,6 @@ Server::Application.configure do
   # Authentication stub: hard code pre-approved API tokens.
   # config.accept_api_token = { rand(2**256).to_s(36) => true }
   config.accept_api_token = {}
+
+  config.new_users_are_active = false
 end
index 2ac0822a2371db3b1acad94f4b5b0ceb15bf75ac..f4d23ed7c7c226508bf1d470b1ea9a8ba22ac8bc 100644 (file)
@@ -82,4 +82,6 @@ Server::Application.configure do
   # Authentication stub: hard code pre-approved API tokens.
   # config.accept_api_token = { rand(2**256).to_s(36) => true }
   config.accept_api_token = {}
+
+  config.new_users_are_active = false
 end
index 0cca2b7140a3d3938ff31deef3930fd5fe4ebd27..4971ef48ede3daeacfd8ff1bdc66db162a08bc46 100644 (file)
@@ -61,4 +61,6 @@ Server::Application.configure do
   # Authentication stub: hard code pre-approved API tokens.
   # config.accept_api_token = { rand(2**256).to_s(36) => true }
   config.accept_api_token = {}
+
+  config.new_users_are_active = false
 end
diff --git a/services/api/db/migrate/20130626002829_add_is_active_to_users.rb b/services/api/db/migrate/20130626002829_add_is_active_to_users.rb
new file mode 100644 (file)
index 0000000..602c9ec
--- /dev/null
@@ -0,0 +1,5 @@
+class AddIsActiveToUsers < ActiveRecord::Migration
+  def change
+    add_column :users, :is_active, :boolean, :default => false
+  end
+end
index 91b7979008f27686122f593c0dc9e2e8627ad615..892afdbd36f4bd51cd4ae2ca6aac3419e380dc6b 100644 (file)
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version => 20130617150007) do
+ActiveRecord::Schema.define(:version => 20130626002829) do
 
   create_table "api_client_authorizations", :force => true do |t|
     t.string   "api_token",               :null => false
@@ -371,6 +371,7 @@ ActiveRecord::Schema.define(:version => 20130617150007) do
     t.text     "prefs"
     t.datetime "updated_at"
     t.string   "default_owner"
+    t.boolean  "is_active",          :default => false
   end
 
   add_index "users", ["created_at"], :name => "index_users_on_created_at"