e7903d46ddc8465b0f04fceac66222193707bd06
[arvados.git] / services / api / app / models / api_client_authorization.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 class ApiClientAuthorization < ArvadosModel
6   include HasUuid
7   include KindAndEtag
8   include CommonApiTemplate
9   extend CurrentApiClient
10
11   belongs_to :api_client
12   belongs_to :user
13   after_initialize :assign_random_api_token
14   serialize :scopes, Array
15
16   api_accessible :user, extend: :common do |t|
17     t.add :owner_uuid
18     t.add :user_id
19     t.add :api_client_id
20     t.add :api_token
21     t.add :created_by_ip_address
22     t.add :default_owner_uuid
23     t.add :expires_at
24     t.add :last_used_at
25     t.add :last_used_by_ip_address
26     t.add :scopes
27   end
28
29   UNLOGGED_CHANGES = ['last_used_at', 'last_used_by_ip_address', 'updated_at']
30
31   def assign_random_api_token
32     self.api_token ||= rand(2**256).to_s(36)
33   end
34
35   def owner_uuid
36     self.user.andand.uuid
37   end
38   def owner_uuid_was
39     self.user_id_changed? ? User.where(id: self.user_id_was).first.andand.uuid : self.user.andand.uuid
40   end
41   def owner_uuid_changed?
42     self.user_id_changed?
43   end
44
45   def modified_by_client_uuid
46     nil
47   end
48   def modified_by_client_uuid=(x) end
49
50   def modified_by_user_uuid
51     nil
52   end
53   def modified_by_user_uuid=(x) end
54
55   def modified_at
56     nil
57   end
58   def modified_at=(x) end
59
60   def scopes_allow?(req_s)
61     scopes.each do |scope|
62       return true if (scope == 'all') or (scope == req_s) or
63         ((scope.end_with? '/') and (req_s.start_with? scope))
64     end
65     false
66   end
67
68   def scopes_allow_request?(request)
69     method = request.request_method
70     if method == 'HEAD'
71       (scopes_allow?(['HEAD', request.path].join(' ')) ||
72        scopes_allow?(['GET', request.path].join(' ')))
73     else
74       scopes_allow?([method, request.path].join(' '))
75     end
76   end
77
78   def logged_attributes
79     super.except 'api_token'
80   end
81
82   def self.default_orders
83     ["#{table_name}.id desc"]
84   end
85
86   def self.remote_host(uuid:)
87     Rails.configuration.remote_hosts[uuid[0..4]] ||
88       (Rails.configuration.remote_hosts_via_dns &&
89        uuid[0..4]+".arvadosapi.com")
90   end
91
92   def self.validate(token:, remote:)
93     return nil if !token
94     remote ||= Rails.configuration.uuid_prefix
95
96     case token[0..2]
97     when 'v2/'
98       _, uuid, secret = token.split('/')
99       auth = ApiClientAuthorization.
100              includes(:user, :api_client).
101              where('uuid=? and (expires_at is null or expires_at > CURRENT_TIMESTAMP)', uuid).
102              first
103       if auth && auth.user &&
104          (secret == auth.api_token ||
105           secret == OpenSSL::HMAC.hexdigest('sha1', auth.api_token, remote))
106         return auth
107       elsif uuid[0..4] != Rails.configuration.uuid_prefix
108         # Token was issued by a different cluster. If it's expired or
109         # missing in our database, ask the originating cluster to
110         # [re]validate it.
111         arv = Arvados.new(api_host: remote_host(uuid: uuid),
112                           api_token: token)
113         remote_user = arv.user.current(remote_id: Rails.configuration.uuid_prefix)
114         if remote_user && remote_user[:uuid][0..4] == uuid[0..4]
115           act_as_system_user do
116             # Add/update user and token in our database so we can
117             # validate subsequent requests faster.
118             user = User.find_or_create_by(uuid: remote_user[:uuid])
119             user.update_attributes!(remote_user)
120             auth = ApiClientAuthorization.
121                    includes(:user).
122                    find_or_create_by(uuid: uuid,
123                                      api_token: token,
124                                      user: user,
125                                      api_client_id: 0)
126             # Accept this token (and don't reload the user record) for
127             # 5 minutes. TODO: Request the actual api_client_auth
128             # record from the remote server in case it wants the token
129             # to expire sooner.
130             auth.update_attributes!(expires_at: Time.now + 5.minutes)
131           end
132           return auth
133         end
134       end
135     else
136       auth = ApiClientAuthorization.
137              includes(:user, :api_client).
138              where('api_token=? and (expires_at is null or expires_at > CURRENT_TIMESTAMP)', token).
139              first
140       if auth && auth.user
141         return auth
142       end
143     end
144     return nil
145   end
146
147   protected
148
149   def permission_to_create
150     current_user.andand.is_admin or (current_user.andand.id == self.user_id)
151   end
152
153   def permission_to_update
154     (permission_to_create and
155      not uuid_changed? and
156      not user_id_changed? and
157      not owner_uuid_changed?)
158   end
159
160   def log_update
161     super unless (changed - UNLOGGED_CHANGES).empty?
162   end
163 end