# Copyright:: Copyright (c) 2018 GitLab Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require_relative '../../package/libraries/helpers/logging_helper'

class LetsEncrypt
  class << self
    def parse_variables
      parse_enable
    end

    # Munge the enable parameter
    #
    # @return [void]
    def parse_enable
      # default for letsencrypt.enable is nil.  If a user has specified anything
      # else leave it alone.
      return unless Gitlab['letsencrypt']['enable'].nil?

      if should_auto_enable? && Gitlab['package']['generate_secrets_json_file'] == false
        LoggingHelper.warning("Writing secrets to `gitlab-secrets.json` file is disabled. Hence, not automatically enabling Let's Encrypt integration.")
        return
      end

      # We get to make a 'guess' as to if we should enable based on other parsed
      # values
      Gitlab['letsencrypt']['enable'] = should_auto_enable?

      # Remember if we auto-enabled, for later runs.  Persisted as a secret
      Gitlab['letsencrypt']['auto_enabled'] = Gitlab['letsencrypt']['enable'] == true
    end

    def rails_listen_https?
      Gitlab['gitlab_rails']['gitlab_https']
    end

    def nginx_enabled?
      [
        Gitlab['nginx']['enable'],
        Gitlab[:node]['gitlab']['nginx']['enable'],
        true
      ].find { |e| !e.nil? }
    end

    def nginx_listen_https?
      Gitlab['nginx']['listen_https'].nil? || Gitlab['nginx']['listen_https']
    end

    def le_auto_enabled?
      # We store value of this setting to gitlab-secrets.json file. This acts
      # as a marker whether the certificate present is something we generated
      # automatically in a previous run, or is something user brought.
      Gitlab['letsencrypt']['auto_enabled']
    end

    def cert_files_present?
      File.exist?(Gitlab['nginx']['ssl_certificate_key']) || File.exist?(Gitlab['nginx']['ssl_certificate'])
    end

    # Should we enable the recipe even if a user didn't specify it?
    #
    # @return [true, false]
    def should_auto_enable?
      # We automatically enable LE if all the following conditions are met
      # 1. Rails component is listening to https
      # 2. Nginx is enabled and is listening over https
      # 3. At least one of the following is true
      #    a. LE was automatically enabled in a previous run
      #    b. No certificate/key files are present
      #    c. If certificate is present, and they are from LE, and is ready for renewal
      #    Note: The last condition here is a failsafe to ensure an expired
      #    certificate always gets renewed.
      #    Check https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4244 for
      #    details. The downside is that when expired certificate issued by LE
      #    is encountered, omnibus-gitlab will attempt to renew it, irrespective
      #    of whether it was automatically generated by a previous reconfigure
      #    run or brought by the user.
      rails_listen_https? && nginx_enabled? && nginx_listen_https? && (le_auto_enabled? || !cert_files_present? || needs_renewal?)
    end

    # Save secrets if they do not have letsencrypt.auto_enabled
    #
    # @return [void]
    def save_auto_enabled
      return unless Gitlab['letsencrypt']['auto_enabled']

      secrets = SecretsHelper.load_gitlab_secrets

      # Avoid writing if the attribute is there and true
      return if secrets.dig('letsencrypt', 'auto_enabled')

      SecretsHelper.write_to_gitlab_secrets
    end

    private

    LETSENCRYPT_ISSUER = %r(/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X[1-4]).freeze

    # Checks wheather the existing Let's Encrypt certificate is expired and needs renewal.
    #
    # @return [true, false]
    def needs_renewal?
      file_name = Gitlab['nginx']['ssl_certificate']
      return false unless File.exist? file_name

      cert = OpenSSL::X509::Certificate.new File.read(file_name)

      cert.issuer.to_s =~ LETSENCRYPT_ISSUER && cert.not_after < Time.now
    end
  end
end
