mirror of
				https://github.com/fog/fog.git
				synced 2022-11-09 13:51:43 -05:00 
			
		
		
		
	[openstack|identity] Fix Authentication Implementation
This commit is contained in:
		
							parent
							
								
									7cf6031c81
								
							
						
					
					
						commit
						3e43a571e0
					
				
					 18 changed files with 118 additions and 110 deletions
				
			
		
							
								
								
									
										53
									
								
								.fog
									
										
									
									
									
								
							
							
						
						
									
										53
									
								
								.fog
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,53 +0,0 @@
 | 
			
		|||
:default:
 | 
			
		||||
  :aws_access_key_id:
 | 
			
		||||
  :aws_secret_access_key:
 | 
			
		||||
  :bluebox_api_key:
 | 
			
		||||
  :bluebox_customer_id:
 | 
			
		||||
  :brightbox_client_id:
 | 
			
		||||
  :brightbox_secret:
 | 
			
		||||
  :clodo_api_key:
 | 
			
		||||
  :clodo_username:
 | 
			
		||||
  :go_grid_api_key:
 | 
			
		||||
  :go_grid_shared_secret:
 | 
			
		||||
  :google_storage_access_key_id:
 | 
			
		||||
  :google_storage_secret_access_key:
 | 
			
		||||
  :linode_api_key:
 | 
			
		||||
  :local_root:
 | 
			
		||||
  :new_servers_password:
 | 
			
		||||
  :new_servers_username:
 | 
			
		||||
  :public_key_path:
 | 
			
		||||
  :private_key_path:
 | 
			
		||||
  :openstack_api_key:
 | 
			
		||||
  :openstack_username:
 | 
			
		||||
  :openstack_auth_url:
 | 
			
		||||
  :openstack_tenant:
 | 
			
		||||
  :ovirt_username:
 | 
			
		||||
  :ovirt_password:
 | 
			
		||||
  :ovirt_url:
 | 
			
		||||
  :rackspace_api_key:
 | 
			
		||||
  :rackspace_username:
 | 
			
		||||
  :rackspace_servicenet:
 | 
			
		||||
  :rackspace_cdn_ssl:
 | 
			
		||||
  :slicehost_password:
 | 
			
		||||
  :stormondemand_username:
 | 
			
		||||
  :stormondemand_password:
 | 
			
		||||
  :terremark_username:
 | 
			
		||||
  :terremark_password:
 | 
			
		||||
  :voxel_api_key:
 | 
			
		||||
  :voxel_api_secret:
 | 
			
		||||
  :zerigo_email:
 | 
			
		||||
  :zerigo_token:
 | 
			
		||||
  :dnsimple_email:
 | 
			
		||||
  :dnsimple_password:
 | 
			
		||||
  :dnsmadeeasy_api_key:
 | 
			
		||||
  :dnsmadeeasy_secret_key:
 | 
			
		||||
  :cloudstack_host:
 | 
			
		||||
  :cloudstack_api_key:
 | 
			
		||||
  :cloudstack_secret_access_key:
 | 
			
		||||
  :vsphere_server:
 | 
			
		||||
  :vsphere_username:
 | 
			
		||||
  :vsphere_password:
 | 
			
		||||
  :libvirt_username:
 | 
			
		||||
  :libvirt_password:
 | 
			
		||||
  :libvirt_uri:
 | 
			
		||||
  :libvirt_ip_command:
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -9,6 +9,7 @@
 | 
			
		|||
bin/*
 | 
			
		||||
!bin/fog
 | 
			
		||||
!bin/rdoc
 | 
			
		||||
.fog
 | 
			
		||||
coverage
 | 
			
		||||
doc/*
 | 
			
		||||
docs/_site/*
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										43
									
								
								.irbrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								.irbrc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
require 'fog'
 | 
			
		||||
 | 
			
		||||
class ConnectionManager < Hash
 | 
			
		||||
  def [](key)
 | 
			
		||||
    $connection_manager_previous_key = key
 | 
			
		||||
    super(key)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def []=(key, value)
 | 
			
		||||
    $connection_manager_previous_key = key
 | 
			
		||||
    super(key, value)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
def connections
 | 
			
		||||
  return @connections if @connections
 | 
			
		||||
  @connections = ConnectionManager.new
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
def connection
 | 
			
		||||
  connections[$connection_manager_previous_key]
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
def connect(username, password, tenant = nil, url = 'http://192.168.27.100:35357/')
 | 
			
		||||
  parameters = {
 | 
			
		||||
    :provider => 'openstack',
 | 
			
		||||
    :openstack_api_key => password,
 | 
			
		||||
    :openstack_username => username,
 | 
			
		||||
    :openstack_auth_url => "#{url}v2.0/tokens"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  parameters.merge!(:openstack_tenant => tenant) if tenant
 | 
			
		||||
 | 
			
		||||
  identity = Fog::Identity.new(parameters)
 | 
			
		||||
  compute = Fog::Compute.new(parameters)
 | 
			
		||||
  image = Fog::Image.new(parameters)
 | 
			
		||||
 | 
			
		||||
  connections[username.to_sym] = {
 | 
			
		||||
    :identity => identity,
 | 
			
		||||
    :compute  => compute ,
 | 
			
		||||
    :image    => image
 | 
			
		||||
  }
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +13,7 @@ require 'fog/providers'
 | 
			
		|||
require 'fog/terremark'
 | 
			
		||||
 | 
			
		||||
require 'fog/compute'
 | 
			
		||||
require 'fog/identity'
 | 
			
		||||
require 'fog/cdn'
 | 
			
		||||
require 'fog/dns'
 | 
			
		||||
require 'fog/storage'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										24
									
								
								lib/fog/identity.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/fog/identity.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
 | 
			
		||||
    def self.[](provider)
 | 
			
		||||
      self.new(:provider => provider)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def self.new(attributes)
 | 
			
		||||
      attributes = attributes.dup # Prevent delete from having side effects
 | 
			
		||||
      case provider = attributes.delete(:provider).to_s.downcase.to_sym
 | 
			
		||||
      when :openstack
 | 
			
		||||
        require 'fog/openstack/identity'
 | 
			
		||||
        Fog::Identity::OpenStack.new(attributes)
 | 
			
		||||
      else
 | 
			
		||||
        raise ArgumentError.new("#{provider} has no identity service")
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def self.providers
 | 
			
		||||
      Fog.services[:idenity]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +41,8 @@ module Fog
 | 
			
		|||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    service(:compute,         'openstack/compute',        'Compute')
 | 
			
		||||
    service(:compute , 'openstack/compute' , 'Compute' )
 | 
			
		||||
    service(:identity, 'openstack/identity', 'Identity')
 | 
			
		||||
 | 
			
		||||
    # legacy v1.0 style auth
 | 
			
		||||
    def self.authenticate_v1(options, connection_options = {})
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +88,8 @@ module Fog
 | 
			
		|||
      req_body['auth']['tenantName'] = @openstack_tenant if @openstack_tenant
 | 
			
		||||
 | 
			
		||||
      body = retrieve_tokens_v2(connection, req_body, uri)
 | 
			
		||||
      svc = body['access']['serviceCatalog'].detect{|x| x['name'] == @compute_service_name}
 | 
			
		||||
      svc = body['access']['serviceCatalog'].
 | 
			
		||||
        detect{|x| @compute_service_name.include?(x['type']) }
 | 
			
		||||
 | 
			
		||||
      unless svc
 | 
			
		||||
        unless @openstack_tenant
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +108,7 @@ module Fog
 | 
			
		|||
 | 
			
		||||
        body = retrieve_tokens_v2(connection, req_body, uri)
 | 
			
		||||
        svc = body['access']['serviceCatalog'].
 | 
			
		||||
          detect {|x| x['endpoints'].detect{|y| y['publicURL'].match(/(1\.1|2\.0)/) } }
 | 
			
		||||
          detect{|x| @compute_service_name.include?(x['type']) }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      mgmt_url = svc['endpoints'].detect{|x| x['publicURL']}['publicURL']
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,8 @@ module Fog
 | 
			
		|||
    class OpenStack < Fog::Service
 | 
			
		||||
 | 
			
		||||
      requires :openstack_api_key, :openstack_username, :openstack_auth_url
 | 
			
		||||
      recognizes :openstack_auth_token, :openstack_management_url, :persistent, :openstack_compute_service_name, :openstack_tenant
 | 
			
		||||
      recognizes :openstack_auth_token, :openstack_management_url,
 | 
			
		||||
                 :persistent, :openstack_compute_service_name, :openstack_tenant
 | 
			
		||||
 | 
			
		||||
      model_path 'fog/openstack/models/compute'
 | 
			
		||||
      model       :address
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +149,7 @@ module Fog
 | 
			
		|||
          @openstack_auth_token = options[:openstack_auth_token]
 | 
			
		||||
          @openstack_management_url       = options[:openstack_management_url]
 | 
			
		||||
          @openstack_must_reauthenticate  = false
 | 
			
		||||
          @openstack_compute_service_name = options[:openstack_compute_service_name] || 'nova'
 | 
			
		||||
          @openstack_compute_service_name = options[:openstack_compute_service_name] || ['nova', 'compute']
 | 
			
		||||
 | 
			
		||||
          @connection_options = options[:connection_options] || {}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,30 +6,30 @@ module Fog
 | 
			
		|||
    class OpenStack < Fog::Service
 | 
			
		||||
 | 
			
		||||
      requires :openstack_api_key, :openstack_username, :openstack_auth_url
 | 
			
		||||
      recognizes :openstack_auth_token, :openstack_management_url, :persistent, :openstack_compute_service_name, :openstack_tenant
 | 
			
		||||
      recognizes :openstack_auth_token, :openstack_management_url, :persistent,
 | 
			
		||||
                 :openstack_compute_service_name, :openstack_tenant
 | 
			
		||||
 | 
			
		||||
      # model_path 'fog/openstack/models/identity'
 | 
			
		||||
      model_path 'fog/openstack/models/identity'
 | 
			
		||||
      # model       :tenant
 | 
			
		||||
      # collection  :tenants
 | 
			
		||||
      # model       :user
 | 
			
		||||
      # collection  :users
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      request_path 'fog/openstack/requests/identity'
 | 
			
		||||
 | 
			
		||||
      request :check_token
 | 
			
		||||
      request :validate_token
 | 
			
		||||
 | 
			
		||||
      request :get_tenants
 | 
			
		||||
      request :get_tenants_by_id
 | 
			
		||||
      request :get_tenants_by_name
 | 
			
		||||
 | 
			
		||||
      request :get_user_by_id
 | 
			
		||||
      request :get_user_by_name
 | 
			
		||||
 | 
			
		||||
      request :list_endpoints_for_token
 | 
			
		||||
      request :list_roles_for_user_on_tenant
 | 
			
		||||
      request :list_user_global_roles
 | 
			
		||||
      request :validate_tokens
 | 
			
		||||
      
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      class Mock
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,16 +41,20 @@ module Fog
 | 
			
		|||
 | 
			
		||||
        def initialize(options={})
 | 
			
		||||
          require 'multi_json'
 | 
			
		||||
          @openstack_api_key = options[:openstack_api_key]
 | 
			
		||||
 | 
			
		||||
          @openstack_api_key  = options[:openstack_api_key]
 | 
			
		||||
          @openstack_username = options[:openstack_username]
 | 
			
		||||
          @openstack_tenant = options[:openstack_tenant]
 | 
			
		||||
          @openstack_compute_service_name = options[:openstack_compute_service_name] || 'nova'
 | 
			
		||||
          @openstack_auth_url = options[:openstack_auth_url]
 | 
			
		||||
          @openstack_tenant   = options[:openstack_tenant]
 | 
			
		||||
          @openstack_auth_uri   = URI.parse(options[:openstack_auth_url])
 | 
			
		||||
          @openstack_auth_token = options[:openstack_auth_token]
 | 
			
		||||
          @openstack_management_url = options[:openstack_management_url]
 | 
			
		||||
          @openstack_must_reauthenticate = false
 | 
			
		||||
          @openstack_management_url       = options[:openstack_management_url]
 | 
			
		||||
          @openstack_must_reauthenticate  = false
 | 
			
		||||
          @openstack_compute_service_name = options[:openstack_compute_service_name] || ['identity']
 | 
			
		||||
 | 
			
		||||
          @connection_options = options[:connection_options] || {}
 | 
			
		||||
 | 
			
		||||
          authenticate
 | 
			
		||||
 | 
			
		||||
          @persistent = options[:persistent] || false
 | 
			
		||||
          @connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}", @persistent, @connection_options)
 | 
			
		||||
        end
 | 
			
		||||
| 
						 | 
				
			
			@ -99,25 +103,28 @@ module Fog
 | 
			
		|||
            options = {
 | 
			
		||||
              :openstack_api_key  => @openstack_api_key,
 | 
			
		||||
              :openstack_username => @openstack_username,
 | 
			
		||||
              :openstack_auth_url => @openstack_auth_url,
 | 
			
		||||
              :openstack_tenant => @openstack_tenant,
 | 
			
		||||
              :openstack_auth_uri => @openstack_auth_uri,
 | 
			
		||||
              :openstack_tenant   => @openstack_tenant,
 | 
			
		||||
              :openstack_compute_service_name => @openstack_compute_service_name
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            credentials = Fog::OpenStack.authenticate_v2(options, @connection_options)
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            @openstack_must_reauthenticate = false
 | 
			
		||||
            @auth_token = credentials[:token]
 | 
			
		||||
            
 | 
			
		||||
            url = @openstack_auth_url
 | 
			
		||||
            uri = URI.parse(url)
 | 
			
		||||
            @openstack_management_url = credentials[:server_management_url]
 | 
			
		||||
            uri = URI.parse(@openstack_management_url)
 | 
			
		||||
          else
 | 
			
		||||
            @auth_token = @openstack_auth_token
 | 
			
		||||
            uri = URI.parse(@openstack_management_url)
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          @host   = uri.host
 | 
			
		||||
          @path   = uri.path
 | 
			
		||||
          @path.sub!(/\/$/, '')
 | 
			
		||||
          @port   = uri.port
 | 
			
		||||
          @scheme = uri.scheme
 | 
			
		||||
          true
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,18 +1,14 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
    class Openstack
 | 
			
		||||
    class OpenStack
 | 
			
		||||
      class Real
 | 
			
		||||
 | 
			
		||||
        def check_token(token_id, tenant_id)
 | 
			
		||||
          
 | 
			
		||||
          request(
 | 
			
		||||
            :expects  => [200, 203],
 | 
			
		||||
            :method   => 'HEAD',
 | 
			
		||||
            :path     => "tokens/#{token_id}?belongsTo=#{tenant_id}"
 | 
			
		||||
            :path     => "tokens/#{token_id}?belongsTo=#{tenant_id}"
 | 
			
		||||
          )
 | 
			
		||||
 | 
			
		||||
          # TODO: Handle 404
 | 
			
		||||
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,9 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
    class Openstack
 | 
			
		||||
    class OpenStack
 | 
			
		||||
      class Real
 | 
			
		||||
 | 
			
		||||
        def get_tenants
 | 
			
		||||
          
 | 
			
		||||
          request(
 | 
			
		||||
            :expects  => [200],
 | 
			
		||||
            :method   => 'GET',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,9 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
    class Openstack
 | 
			
		||||
    class OpenStack
 | 
			
		||||
      class Real
 | 
			
		||||
 | 
			
		||||
        def get_tenants_by_id(tenant_id)
 | 
			
		||||
          
 | 
			
		||||
          request(
 | 
			
		||||
            :expects  => [200],
 | 
			
		||||
            :method   => 'GET',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,9 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
    class Openstack
 | 
			
		||||
    class OpenStack
 | 
			
		||||
      class Real
 | 
			
		||||
 | 
			
		||||
        def get_tenants_by_name(name)
 | 
			
		||||
          
 | 
			
		||||
          request(
 | 
			
		||||
            :expects  => [200],
 | 
			
		||||
            :method   => 'GET',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,9 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
    class Openstack
 | 
			
		||||
    class OpenStack
 | 
			
		||||
      class Real
 | 
			
		||||
 | 
			
		||||
        def get_user_by_id(user_id)
 | 
			
		||||
          
 | 
			
		||||
          request(
 | 
			
		||||
            :expects  => [200, 203],
 | 
			
		||||
            :method   => 'GET',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,9 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
    class Openstack
 | 
			
		||||
    class OpenStack
 | 
			
		||||
      class Real
 | 
			
		||||
 | 
			
		||||
        def get_user_by_name(name)
 | 
			
		||||
          
 | 
			
		||||
          request(
 | 
			
		||||
            :expects  => [200, 203],
 | 
			
		||||
            :method   => 'GET',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,16 +1,14 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
    class Openstack
 | 
			
		||||
    class OpenStack
 | 
			
		||||
      class Real
 | 
			
		||||
 | 
			
		||||
        def list_endpoints_for_token(token_id)
 | 
			
		||||
          
 | 
			
		||||
          request(
 | 
			
		||||
            :expects  => [200, 203],
 | 
			
		||||
            :method   => 'HEAD',
 | 
			
		||||
            :path     => "tokens/#{token_id}/endpoints"
 | 
			
		||||
            :path     => "tokens/#{token_id}/endpoints"
 | 
			
		||||
          )
 | 
			
		||||
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,9 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
    class Openstack
 | 
			
		||||
    class OpenStack
 | 
			
		||||
      class Real
 | 
			
		||||
 | 
			
		||||
        def list_roles_for_user_on_tenant(tenant_id, user_id)
 | 
			
		||||
          
 | 
			
		||||
          request(
 | 
			
		||||
            :expects  => [200],
 | 
			
		||||
            :method   => 'GET',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +1,15 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
    class Openstack
 | 
			
		||||
    class OpenStack
 | 
			
		||||
      class Real
 | 
			
		||||
 | 
			
		||||
        def list_user_global_roles(user_id)
 | 
			
		||||
          
 | 
			
		||||
          request(
 | 
			
		||||
            :expects  => [200],
 | 
			
		||||
            :method   => 'GET',
 | 
			
		||||
            :path     => "users/#{user_id}/roles"
 | 
			
		||||
          )
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      class Mock
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,18 +1,14 @@
 | 
			
		|||
module Fog
 | 
			
		||||
  module Identity
 | 
			
		||||
    class Openstack
 | 
			
		||||
    class OpenStack
 | 
			
		||||
      class Real
 | 
			
		||||
 | 
			
		||||
        def validate_token(token_id, tenant_id)
 | 
			
		||||
          
 | 
			
		||||
          request(
 | 
			
		||||
            :expects  => [200, 203],
 | 
			
		||||
            :method   => 'GET',
 | 
			
		||||
            :path     => "tokens/#{token_id}?belongsTo=#{tenant_id}"
 | 
			
		||||
            :path     => "tokens/#{token_id}?belongsTo=#{tenant_id}"
 | 
			
		||||
          )
 | 
			
		||||
 | 
			
		||||
          # TODO: Handle 404
 | 
			
		||||
          
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue