Merge branch 'main' from workbench2.git
[arvados.git] / services / api / lib / has_uuid.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 module HasUuid
6
7   UUID_REGEX = /^[0-9a-z]{5}-([0-9a-z]{5})-[0-9a-z]{15}$/
8
9   def self.included(base)
10     base.extend(ClassMethods)
11     base.validate :validate_uuid
12     base.before_create :assign_uuid
13     base.before_destroy :destroy_permission_links
14     base.has_many(:links_via_head,
15                   -> { where("not (link_class = 'permission')") },
16                   class_name: 'Link',
17                   foreign_key: :head_uuid,
18                   primary_key: :uuid,
19                   dependent: :destroy)
20     base.has_many(:links_via_tail,
21                   -> { where("not (link_class = 'permission')") },
22                   class_name: 'Link',
23                   foreign_key: :tail_uuid,
24                   primary_key: :uuid,
25                   dependent: :destroy)
26   end
27
28   module ClassMethods
29     def uuid_prefix
30       Digest::MD5.hexdigest(self.to_s).to_i(16).to_s(36)[-5..-1]
31     end
32     def generate_uuid
33       [Rails.configuration.ClusterID,
34        self.uuid_prefix,
35        rand(2**256).to_s(36)[-15..-1]].
36         join '-'
37     end
38   end
39
40   protected
41
42   def respond_to_uuid?
43     self.respond_to? :uuid
44   end
45
46   def validate_uuid
47     if self.respond_to_uuid? and self.uuid_changed?
48       if current_user.andand.is_admin and self.uuid.is_a?(String)
49         if (re = self.uuid.match HasUuid::UUID_REGEX)
50           if re[1] == self.class.uuid_prefix
51             return true
52           else
53             self.errors.add(:uuid, "type field is '#{re[1]}', expected '#{self.class.uuid_prefix}'")
54             return false
55           end
56         else
57           self.errors.add(:uuid, "not a valid Arvados uuid '#{self.uuid}'")
58           return false
59         end
60       else
61         if self.new_record?
62           self.errors.add(:uuid, "assignment not permitted")
63         else
64           self.errors.add(:uuid, "change not permitted")
65         end
66         return false
67       end
68     else
69       return true
70     end
71   end
72
73   def assign_uuid
74     if self.respond_to_uuid? and self.uuid.nil? or self.uuid.empty?
75       self.uuid = self.class.generate_uuid
76     end
77     true
78   end
79
80   def destroy_permission_links
81     if uuid
82       Link.where(['link_class=? and (head_uuid=? or tail_uuid=?)',
83                   'permission', uuid, uuid]).destroy_all
84     end
85   end
86 end