Merge branch 'master' into 5383-api-db-current-time
[arvados.git] / services / api / lib / has_uuid.rb
1 module HasUuid
2
3   UUID_REGEX = /^[0-9a-z]{5}-([0-9a-z]{5})-[0-9a-z]{15}$/
4
5   def self.included(base)
6     base.extend(ClassMethods)
7     base.validate :validate_uuid
8     base.before_create :assign_uuid
9     base.before_destroy :destroy_permission_links
10     base.has_many :links_via_head, class_name: 'Link', foreign_key: :head_uuid, primary_key: :uuid, conditions: "not (link_class = 'permission')", dependent: :restrict
11     base.has_many :links_via_tail, class_name: 'Link', foreign_key: :tail_uuid, primary_key: :uuid, conditions: "not (link_class = 'permission')", dependent: :restrict
12   end
13
14   module ClassMethods
15     def uuid_prefix
16       Digest::MD5.hexdigest(self.to_s).to_i(16).to_s(36)[-5..-1]
17     end
18     def generate_uuid
19       [Server::Application.config.uuid_prefix,
20        self.uuid_prefix,
21        rand(2**256).to_s(36)[-15..-1]].
22         join '-'
23     end
24   end
25
26   protected
27
28   def respond_to_uuid?
29     self.respond_to? :uuid
30   end
31
32   def validate_uuid
33     if self.respond_to_uuid? and self.uuid_changed?
34       if current_user.andand.is_admin and self.uuid.is_a?(String)
35         if (re = self.uuid.match HasUuid::UUID_REGEX)
36           if re[1] == self.class.uuid_prefix
37             return true
38           else
39             self.errors.add(:uuid, "type field is '#{re[1]}', expected '#{self.class.uuid_prefix}'")
40             return false
41           end
42         else
43           self.errors.add(:uuid, "not a valid Arvados uuid '#{self.uuid}'")
44           return false
45         end
46       else
47         if self.new_record?
48           self.errors.add(:uuid, "assignment not permitted")
49         else
50           self.errors.add(:uuid, "change not permitted")
51         end
52         return false
53       end
54     else
55       return true
56     end
57   end
58
59   def assign_uuid
60     if self.respond_to_uuid? and self.uuid.nil? or self.uuid.empty?
61       self.uuid = self.class.generate_uuid
62     end
63     true
64   end
65
66   def destroy_permission_links
67     if uuid
68       Link.destroy_all(['link_class=? and (head_uuid=? or tail_uuid=?)',
69                         'permission', uuid, uuid])
70     end
71   end
72 end