summaryrefslogtreecommitdiff
path: root/lib/gitlab
diff options
context:
space:
mode:
authorImre Farkas <ifarkas@gitlab.com>2019-04-05 11:45:47 +0000
committerAndreas Brandl <abrandl@gitlab.com>2019-04-05 11:45:47 +0000
commitd9d7237d2ebf101ca35ed8ba2740e7c7093437ea (patch)
tree419b0af4bc8de6de5888feec4f502bcc468df400 /lib/gitlab
parent30fa3cbdb74df2dfeebb2929a10dd301a0dde55e (diff)
downloadgitlab-ce-d9d7237d2ebf101ca35ed8ba2740e7c7093437ea.tar.gz
Move Contribution Analytics related spec in spec/features/groups/group_page_with_external_authorization_service_spec to EE
Diffstat (limited to 'lib/gitlab')
-rw-r--r--lib/gitlab/external_authorization.rb40
-rw-r--r--lib/gitlab/external_authorization/access.rb55
-rw-r--r--lib/gitlab/external_authorization/cache.rb62
-rw-r--r--lib/gitlab/external_authorization/client.rb63
-rw-r--r--lib/gitlab/external_authorization/config.rb47
-rw-r--r--lib/gitlab/external_authorization/logger.rb21
-rw-r--r--lib/gitlab/external_authorization/response.rb38
7 files changed, 326 insertions, 0 deletions
diff --git a/lib/gitlab/external_authorization.rb b/lib/gitlab/external_authorization.rb
new file mode 100644
index 00000000000..25f8b7b3628
--- /dev/null
+++ b/lib/gitlab/external_authorization.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ extend ExternalAuthorization::Config
+
+ RequestFailed = Class.new(StandardError)
+
+ def self.access_allowed?(user, label, project_path = nil)
+ return true unless perform_check?
+ return false unless user
+
+ access_for_user_to_label(user, label, project_path).has_access?
+ end
+
+ def self.rejection_reason(user, label)
+ return unless enabled?
+ return unless user
+
+ access_for_user_to_label(user, label, nil).reason
+ end
+
+ def self.access_for_user_to_label(user, label, project_path)
+ if RequestStore.active?
+ RequestStore.fetch("external_authorisation:user-#{user.id}:label-#{label}") do
+ load_access(user, label, project_path)
+ end
+ else
+ load_access(user, label, project_path)
+ end
+ end
+
+ def self.load_access(user, label, project_path)
+ access = ::Gitlab::ExternalAuthorization::Access.new(user, label).load!
+ ::Gitlab::ExternalAuthorization::Logger.log_access(access, project_path)
+
+ access
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/access.rb b/lib/gitlab/external_authorization/access.rb
new file mode 100644
index 00000000000..e111c41fcc2
--- /dev/null
+++ b/lib/gitlab/external_authorization/access.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Access
+ attr_reader :user,
+ :reason,
+ :loaded_at,
+ :label,
+ :load_type
+
+ def initialize(user, label)
+ @user, @label = user, label
+ end
+
+ def loaded?
+ loaded_at && (loaded_at > ExternalAuthorization::Cache::VALIDITY_TIME.ago)
+ end
+
+ def has_access?
+ @access
+ end
+
+ def load!
+ load_from_cache
+ load_from_service unless loaded?
+ self
+ end
+
+ private
+
+ def load_from_cache
+ @load_type = :cache
+ @access, @reason, @loaded_at = cache.load
+ end
+
+ def load_from_service
+ @load_type = :request
+ response = Client.new(@user, @label).request_access
+ @access = response.successful?
+ @reason = response.reason
+ @loaded_at = Time.now
+ cache.store(@access, @reason, @loaded_at) if response.valid?
+ rescue ::Gitlab::ExternalAuthorization::RequestFailed => e
+ @access = false
+ @reason = e.message
+ @loaded_at = Time.now
+ end
+
+ def cache
+ @cache ||= ExternalAuthorization::Cache.new(@user, @label)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/cache.rb b/lib/gitlab/external_authorization/cache.rb
new file mode 100644
index 00000000000..acdc028b4dc
--- /dev/null
+++ b/lib/gitlab/external_authorization/cache.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Cache
+ VALIDITY_TIME = 6.hours
+
+ def initialize(user, label)
+ @user, @label = user, label
+ end
+
+ def load
+ @access, @reason, @refreshed_at = ::Gitlab::Redis::Cache.with do |redis|
+ redis.hmget(cache_key, :access, :reason, :refreshed_at)
+ end
+
+ [access, reason, refreshed_at]
+ end
+
+ def store(new_access, new_reason, new_refreshed_at)
+ ::Gitlab::Redis::Cache.with do |redis|
+ redis.pipelined do
+ redis.mapped_hmset(
+ cache_key,
+ {
+ access: new_access.to_s,
+ reason: new_reason.to_s,
+ refreshed_at: new_refreshed_at.to_s
+ }
+ )
+
+ redis.expire(cache_key, VALIDITY_TIME)
+ end
+ end
+ end
+
+ private
+
+ def access
+ ::Gitlab::Utils.to_boolean(@access)
+ end
+
+ def reason
+ # `nil` if the cached value was an empty string
+ return unless @reason.present?
+
+ @reason
+ end
+
+ def refreshed_at
+ # Don't try to parse a time if there was no cache
+ return unless @refreshed_at.present?
+
+ Time.parse(@refreshed_at)
+ end
+
+ def cache_key
+ "external_authorization:user-#{@user.id}:label-#{@label}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/client.rb b/lib/gitlab/external_authorization/client.rb
new file mode 100644
index 00000000000..60aab2e7044
--- /dev/null
+++ b/lib/gitlab/external_authorization/client.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+Excon.defaults[:ssl_verify_peer] = false
+
+module Gitlab
+ module ExternalAuthorization
+ class Client
+ include ExternalAuthorization::Config
+
+ REQUEST_HEADERS = {
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json'
+ }.freeze
+
+ def initialize(user, label)
+ @user, @label = user, label
+ end
+
+ def request_access
+ response = Excon.post(
+ service_url,
+ post_params
+ )
+ ::Gitlab::ExternalAuthorization::Response.new(response)
+ rescue Excon::Error => e
+ raise ::Gitlab::ExternalAuthorization::RequestFailed.new(e)
+ end
+
+ private
+
+ def post_params
+ params = { headers: REQUEST_HEADERS,
+ body: body.to_json,
+ connect_timeout: timeout,
+ read_timeout: timeout,
+ write_timeout: timeout }
+
+ if has_tls?
+ params[:client_cert_data] = client_cert
+ params[:client_key_data] = client_key
+ params[:client_key_pass] = client_key_pass
+ end
+
+ params
+ end
+
+ def body
+ @body ||= begin
+ body = {
+ user_identifier: @user.email,
+ project_classification_label: @label
+ }
+
+ if @user.ldap_identity
+ body[:user_ldap_dn] = @user.ldap_identity.extern_uid
+ end
+
+ body
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/config.rb b/lib/gitlab/external_authorization/config.rb
new file mode 100644
index 00000000000..8654a8c1e2e
--- /dev/null
+++ b/lib/gitlab/external_authorization/config.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ module Config
+ extend self
+
+ def timeout
+ application_settings.external_authorization_service_timeout
+ end
+
+ def service_url
+ application_settings.external_authorization_service_url
+ end
+
+ def enabled?
+ application_settings.external_authorization_service_enabled
+ end
+
+ def perform_check?
+ enabled? && service_url.present?
+ end
+
+ def client_cert
+ application_settings.external_auth_client_cert
+ end
+
+ def client_key
+ application_settings.external_auth_client_key
+ end
+
+ def client_key_pass
+ application_settings.external_auth_client_key_pass
+ end
+
+ def has_tls?
+ client_cert.present? && client_key.present?
+ end
+
+ private
+
+ def application_settings
+ ::Gitlab::CurrentSettings.current_application_settings
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/logger.rb b/lib/gitlab/external_authorization/logger.rb
new file mode 100644
index 00000000000..61246cd870e
--- /dev/null
+++ b/lib/gitlab/external_authorization/logger.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Logger < ::Gitlab::Logger
+ def self.log_access(access, project_path)
+ status = access.has_access? ? "GRANTED" : "DENIED"
+ message = ["#{status} #{access.user.email} access to '#{access.label}'"]
+
+ message << "(#{project_path})" if project_path.present?
+ message << "- #{access.load_type} #{access.loaded_at}" if access.load_type == :cache
+
+ info(message.join(' '))
+ end
+
+ def self.file_name_noext
+ 'external-policy-access-control'
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/external_authorization/response.rb b/lib/gitlab/external_authorization/response.rb
new file mode 100644
index 00000000000..4f3fe5882db
--- /dev/null
+++ b/lib/gitlab/external_authorization/response.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ExternalAuthorization
+ class Response
+ include ::Gitlab::Utils::StrongMemoize
+
+ def initialize(excon_response)
+ @excon_response = excon_response
+ end
+
+ def valid?
+ @excon_response && [200, 401, 403].include?(@excon_response.status)
+ end
+
+ def successful?
+ valid? && @excon_response.status == 200
+ end
+
+ def reason
+ parsed_response['reason'] if parsed_response
+ end
+
+ private
+
+ def parsed_response
+ strong_memoize(:parsed_response) { parse_response! }
+ end
+
+ def parse_response!
+ JSON.parse(@excon_response.body)
+ rescue JSON::JSONError
+ # The JSON response is optional, so don't fail when it's missing
+ nil
+ end
+ end
+ end
+end