11453: Authorize tokens issued by remote servers.
[arvados.git] / services / api / app / models / api_client_authorization.rb
index 3e90c0eb304674fc2b6ad283af42838894f06ac1..e7903d46ddc8465b0f04fceac66222193707bd06 100644 (file)
@@ -6,6 +6,7 @@ class ApiClientAuthorization < ArvadosModel
   include HasUuid
   include KindAndEtag
   include CommonApiTemplate
+  extend CurrentApiClient
 
   belongs_to :api_client
   belongs_to :user
@@ -82,22 +83,67 @@ class ApiClientAuthorization < ArvadosModel
     ["#{table_name}.id desc"]
   end
 
-  def self.validate(remote_id:)
-    token = Thread.current[:supplied_token]
+  def self.remote_host(uuid:)
+    Rails.configuration.remote_hosts[uuid[0..4]] ||
+      (Rails.configuration.remote_hosts_via_dns &&
+       uuid[0..4]+".arvadosapi.com")
+  end
+
+  def self.validate(token:, remote:)
     return nil if !token
-    version, uuid, secret = token.split(',')
-    return nil if version != 'v2'
-    auth = ApiClientAuthorization.
-           includes(:user).
-           where('uuid=? and (expires_at is null or expires_at > CURRENT_TIMESTAMP)', uuid).
-           first
-    if auth && secret == OpenSSL::HMAC.hexdigest('sha1', auth.api_token, remote_id)
-      return auth
+    remote ||= Rails.configuration.uuid_prefix
+
+    case token[0..2]
+    when 'v2/'
+      _, uuid, secret = token.split('/')
+      auth = ApiClientAuthorization.
+             includes(:user, :api_client).
+             where('uuid=? and (expires_at is null or expires_at > CURRENT_TIMESTAMP)', uuid).
+             first
+      if auth && auth.user &&
+         (secret == auth.api_token ||
+          secret == OpenSSL::HMAC.hexdigest('sha1', auth.api_token, remote))
+        return auth
+      elsif uuid[0..4] != Rails.configuration.uuid_prefix
+        # Token was issued by a different cluster. If it's expired or
+        # missing in our database, ask the originating cluster to
+        # [re]validate it.
+        arv = Arvados.new(api_host: remote_host(uuid: uuid),
+                          api_token: token)
+        remote_user = arv.user.current(remote_id: Rails.configuration.uuid_prefix)
+        if remote_user && remote_user[:uuid][0..4] == uuid[0..4]
+          act_as_system_user do
+            # Add/update user and token in our database so we can
+            # validate subsequent requests faster.
+            user = User.find_or_create_by(uuid: remote_user[:uuid])
+            user.update_attributes!(remote_user)
+            auth = ApiClientAuthorization.
+                   includes(:user).
+                   find_or_create_by(uuid: uuid,
+                                     api_token: token,
+                                     user: user,
+                                     api_client_id: 0)
+            # Accept this token (and don't reload the user record) for
+            # 5 minutes. TODO: Request the actual api_client_auth
+            # record from the remote server in case it wants the token
+            # to expire sooner.
+            auth.update_attributes!(expires_at: Time.now + 5.minutes)
+          end
+          return auth
+        end
+      end
     else
-      return nil
+      auth = ApiClientAuthorization.
+             includes(:user, :api_client).
+             where('api_token=? and (expires_at is null or expires_at > CURRENT_TIMESTAMP)', token).
+             first
+      if auth && auth.user
+        return auth
+      end
     end
+    return nil
   end
-    
+
   protected
 
   def permission_to_create