18113: Merge branch 'main' into 18113-change-cloudops-defaults
[arvados.git] / services / api / app / models / group.rb
index 36814a3163e074433dfb3f5beeffb77510740475..8565b2a417efc0a28d611c2ff1ed873acfc39a8c 100644 (file)
@@ -17,6 +17,8 @@ class Group < ArvadosModel
   attribute :properties, :jsonbHash, default: {}
 
   validate :ensure_filesystem_compatible_name
+  validate :check_group_class
+  validate :check_filter_group_filters
   before_create :assign_name
   after_create :after_ownership_change
   after_create :update_trash
@@ -24,6 +26,8 @@ class Group < ArvadosModel
   before_update :before_ownership_change
   after_update :after_ownership_change
 
+  after_create :add_role_manage_link
+
   after_update :update_trash
   before_destroy :clear_permissions_and_trash
 
@@ -39,13 +43,57 @@ class Group < ArvadosModel
   end
 
   def ensure_filesystem_compatible_name
-    # project groups need filesystem-compatible names, but others
+    # project and filter groups need filesystem-compatible names, but others
     # don't.
-    super if group_class == 'project'
+    super if group_class == 'project' || group_class == 'filter'
+  end
+
+  def check_group_class
+    if group_class != 'project' && group_class != 'role' && group_class != 'filter'
+      errors.add :group_class, "value must be one of 'project', 'role' or 'filter', was '#{group_class}'"
+    end
+    if group_class_changed? && !group_class_was.nil?
+      errors.add :group_class, "cannot be modified after record is created"
+    end
+  end
+
+  def check_filter_group_filters
+    if group_class == 'filter'
+      if !self.properties.key?("filters")
+        errors.add :properties, "filters property missing, it must be an array of arrays, each with 3 elements"
+        return
+      end
+      if !self.properties["filters"].is_a?(Array)
+        errors.add :properties, "filters property must be an array of arrays, each with 3 elements"
+        return
+      end
+      self.properties["filters"].each do |filter|
+        if !filter.is_a?(Array)
+          errors.add :properties, "filters property must be an array of arrays, each with 3 elements"
+          return
+        end
+        if filter.length() != 3
+          errors.add :properties, "filters property must be an array of arrays, each with 3 elements"
+          return
+        end
+        if !filter[0].include?(".") and filter[0].downcase != "uuid"
+          errors.add :properties, "filter attribute must be 'uuid' or contain a dot (e.g. groups.name)"
+          return
+        end
+        if (filter[0].downcase != "uuid" and filter[1].downcase == "is_a")
+          errors.add :properties, "when filter operator is 'is_a', attribute must be 'uuid'"
+          return
+        end
+        if ! ["=","<","<=",">",">=","!=","like","ilike","in","not in","is_a","exists","contains"].include?(filter[1].downcase)
+          errors.add :properties, "filter operator is not valid (must be =,<,<=,>,>=,!=,like,ilike,in,not in,is_a,exists,contains)"
+          return
+        end
+      end
+    end
   end
 
   def update_trash
-    if trash_at_changed? or owner_uuid_changed?
+    if saved_change_to_trash_at? or saved_change_to_owner_uuid?
       # The group was added or removed from the trash.
       #
       # Strategy:
@@ -85,7 +133,7 @@ on conflict (group_uuid) do update set trash_at=EXCLUDED.trash_at;
   end
 
   def after_ownership_change
-    if owner_uuid_changed?
+    if saved_change_to_owner_uuid?
       update_permissions self.owner_uuid, self.uuid, CAN_MANAGE_PERM
     end
   end
@@ -104,4 +152,33 @@ delete from trashed_groups where group_uuid=$1
     end
     true
   end
+
+  def ensure_owner_uuid_is_permitted
+    if group_class == "role"
+      @requested_manager_uuid = nil
+      if new_record?
+        @requested_manager_uuid = owner_uuid
+        self.owner_uuid = system_user_uuid
+        return true
+      end
+      if self.owner_uuid != system_user_uuid
+        raise "Owner uuid for role must be system user"
+      end
+      raise PermissionDeniedError unless current_user.can?(manage: uuid)
+      true
+    else
+      super
+    end
+  end
+
+  def add_role_manage_link
+    if group_class == "role" && @requested_manager_uuid
+      act_as_system_user do
+       Link.create!(tail_uuid: @requested_manager_uuid,
+                    head_uuid: self.uuid,
+                    link_class: "permission",
+                    name: "can_manage")
+      end
+    end
+  end
 end