1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
6 def self.included(base)
7 base.before_validation :set_validation_timestamp
8 base.before_validation :ensure_trash_at_not_in_past
9 base.before_validation :sync_trash_state
10 base.before_validation :default_trash_interval
11 base.validate :validate_trash_and_delete_timing
14 # Use a single timestamp for all validations, even though each
15 # validation runs at a different time.
16 def set_validation_timestamp
17 @validation_timestamp = db_current_time
20 # If trash_at is being changed to a time in the past, change it to
21 # now. This allows clients to say "expires {client-current-time}"
22 # without failing due to clock skew, while avoiding odd log entries
23 # like "expiry date changed to {1 year ago}".
24 def ensure_trash_at_not_in_past
25 if trash_at_changed? && trash_at
26 self.trash_at = [@validation_timestamp, trash_at].max
30 # Caller can move into/out of trash by setting/clearing is_trashed
31 # -- however, if the caller also changes trash_at, then any changes
32 # to is_trashed are ignored.
34 if is_trashed_changed? && !trash_at_changed?
36 self.trash_at = @validation_timestamp
42 self.is_trashed = trash_at && trash_at <= @validation_timestamp || false
46 def default_delete_after_trash_interval
47 Rails.configuration.Collections.DefaultTrashLifetime
50 def minimum_delete_after_trash_interval
51 Rails.configuration.Collections.BlobSigningTTL
54 def default_trash_interval
55 if trash_at_changed? && !delete_at_changed?
56 # If trash_at is updated without touching delete_at,
57 # automatically update delete_at to a sensible value.
61 self.delete_at = trash_at + self.default_delete_after_trash_interval
63 elsif !trash_at || !delete_at || trash_at > delete_at
64 # Not trash, or bogus arguments? Just validate in
65 # validate_trash_and_delete_timing.
66 elsif delete_at_changed? && delete_at >= trash_at
67 # Fix delete_at if needed, so it's not earlier than the expiry
68 # time on any permission tokens that might have been given out.
70 # In any case there are no signatures expiring after now+TTL.
71 # Also, if the existing trash_at time has already passed, we
72 # know we haven't given out any signatures since then.
74 @validation_timestamp,
76 ].compact.min + minimum_delete_after_trash_interval
78 # The previous value of delete_at is also an upper bound on the
79 # longest-lived permission token. For example, if TTL=14,
80 # trash_at_was=now-7, delete_at_was=now+7, then it is safe to
81 # set trash_at=now+6, delete_at=now+8.
82 earliest_delete = [earliest_delete, delete_at_was].compact.min
84 # If delete_at is too soon, use the earliest possible time.
85 if delete_at < earliest_delete
86 self.delete_at = earliest_delete
91 def validate_trash_and_delete_timing
92 if trash_at.nil? != delete_at.nil?
93 errors.add :delete_at, "must be set if trash_at is set, and must be nil otherwise"
94 elsif delete_at && delete_at < trash_at
95 errors.add :delete_at, "must not be earlier than trash_at"
101 module TrashableController
102 def self.included(base)
103 def base._trash_method_description
104 match = name.match(/\b(\w+)Controller$/)
105 "Trash a #{match[1].singularize.underscore.humanize.downcase}."
107 def base._untrash_method_description
108 match = name.match(/\b(\w+)Controller$/)
109 "Untrash a #{match[1].singularize.underscore.humanize.downcase}."
114 if !@object.is_trashed
115 @object.update!(trash_at: db_current_time)
117 earliest_delete = (@object.trash_at + @object.minimum_delete_after_trash_interval)
118 if @object.delete_at > earliest_delete
119 @object.update!(delete_at: earliest_delete)
125 if !@object.is_trashed
126 @object.update!(trash_at: db_current_time)
132 if !@object.is_trashed
133 raise ArvadosModel::InvalidStateTransitionError.new("Item is not trashed, cannot untrash")
136 if db_current_time >= @object.delete_at
137 raise ArvadosModel::InvalidStateTransitionError.new("delete_at time has already passed, cannot untrash")
140 @object.trash_at = nil
142 if params[:ensure_unique_name]
143 @object.save_with_unique_name!