#!/bin/bash

if [ "$(id -u)" -ne 0 ]; then
  echo "This script must be run as root"
  echo "Usage: sudo bash gitlab-policy-check.sh <domain>"
  exit 1
fi

if [ -z "$1" ]; then
  echo "Usage: sudo bash gitlab-policy-check.sh <domain>"
  echo "Example: sudo bash gitlab-policy-check.sh sncompany.com"
  exit 1
fi

INPUT_DOMAIN="$1"

INPUT_DOMAIN="$INPUT_DOMAIN" gitlab-rails runner "
s = ApplicationSetting.last

\$pass_count = 0
\$fail_count = 0
\$manual_count = 0
\$na_count = 0

def yn(v)
  v ? 'ON' : 'OFF'
end

def vis(v)
  case v
  when 0 then 'Private'
  when 10 then 'Internal'
  when 20 then 'Public'
  else v.to_s
  end
end

def safe(default = 'N/A')
  yield
rescue
  default
end

def normalize_array(v)
  Array(v).map(&:to_s).sort
end

def status(actual, expected)
  return 'MANUAL' if expected == :manual
  return 'N/A' if expected == :na
  return 'N/A' if actual == 'N/A'
  actual == expected ? 'PASS' : 'FAIL'
end

def target_text(expected)
  case expected
  when :manual then '수동 확인'
  when :na then '선택 적용 / 수동 판단'
  else expected.inspect
  end
end

def count_status(st)
  case st
  when 'PASS'
    \$pass_count += 1
  when 'FAIL'
    \$fail_count += 1
  when 'MANUAL'
    \$manual_count += 1
  when 'N/A'
    \$na_count += 1
  end
end

def print_check(label, actual, expected)
  st = status(actual, expected)
  count_status(st)

  puts \"- #{label}\"
  puts \"  status : #{st}\"
  puts \"  actual : #{actual.inspect}\"
  puts \"  target : #{target_text(expected)}\"
  puts
end

def print_check_min(label, actual, minimum)
  st =
    if actual == 'N/A' || actual.nil?
      'N/A'
    elsif actual.to_i >= minimum.to_i
      'PASS'
    else
      'FAIL'
    end

  count_status(st)

  puts \"- #{label}\"
  puts \"  status : #{st}\"
  puts \"  actual : #{actual.inspect}\"
  puts \"  target : #{minimum} 이상\"
  puts
end

def allowed_key_status(v)
  v.nil? || v == 0 ? 'ALLOW' : 'RESTRICTED'
end

puts
puts '=== [GitLab Admin Policy Checklist Check] ==='
puts

# =========================================================
# 목표값 (문서 24. 설정 체크리스트 기준)
# =========================================================
TARGET_ALLOWED_DOMAINS             = [ENV['INPUT_DOMAIN']]
TARGET_RESTRICTED_VISIBILITY       = ['Public']
TARGET_PROJECT_CREATION            = 2
TARGET_GIT_ACCESS_PROTOCOL         = 'ssh+http'
TARGET_2FA_REQUIRED                = 'ON'
TARGET_2FA_GRACE_HOURS             = 48
TARGET_ADMIN_MODE                  = 'ON'
TARGET_PASSWORD_MIN_LENGTH         = 8
TARGET_EMAIL_CONFIRMATION          = 'hard'
TARGET_PAT_EXPIRY_REQUIRED         = 'ON'
TARGET_PAT_MAX_LIFETIME            = 90
TARGET_DSA_RESTRICTION             = 'Forbidden'
TARGET_RSA_MIN_BITS                = 3072
TARGET_ED25519_ALLOWED             = 'ALLOW'
TARGET_ECDSA_ALLOWED               = 'ALLOW'
TARGET_OUTBOUND_WEBHOOKS           = 'OFF'
TARGET_OUTBOUND_SYSTEM_HOOKS       = 'OFF'
TARGET_INSTANCE_RUNNERS            = 'OFF'
TARGET_GRAVATAR                    = 'OFF'
TARGET_SESSION_EXPIRE_MIN          = 480
TARGET_MAX_ARTIFACTS_MB            = 500
TARGET_DOTENV_VARIABLES_LIMIT      = 50
TARGET_MAX_ATTACHMENT_MB           = 100
TARGET_WEB_IDE_FALLBACK            = 'OFF'

# =========================================================
# 실제값 수집
# =========================================================
signup_enabled = yn(safe { s.signup_enabled })
allowed_domains = safe([]) { Array(s.domain_allowlist) }

default_project_visibility = safe { vis(s.default_project_visibility) }
default_group_visibility   = safe { vis(s.default_group_visibility) }
restricted_visibility      = safe([]) { Array(s.restricted_visibility_levels).map { |v| vis(v) } }

default_project_creation = safe { s.default_project_creation }

git_access_protocol_raw = safe(nil) do
  if s.respond_to?(:enabled_git_access_protocol) && !s.enabled_git_access_protocol.nil?
    s.enabled_git_access_protocol.to_s
  else
    nil
  end
end

git_access_protocol =
  case git_access_protocol_raw.to_s.downcase
  when 'all', 'ssh_and_http', 'ssh+http', 'ssh_and_https', 'ssh+https'
    'ssh+http'
  when 'ssh'
    'ssh'
  when 'http', 'https', 'http(s)'
    'http'
  when '', 'nil'
    'N/A'
  else
    git_access_protocol_raw.to_s
  end

require_2fa             = yn(safe { s.require_two_factor_authentication })
two_factor_grace_period = safe { s.two_factor_grace_period }
admin_mode              = yn(safe { s.admin_mode })

unknown_signin_notification = yn(safe { s.notify_on_unknown_sign_in })

minimum_password_length = safe { s.minimum_password_length }
email_confirmation      = safe(nil) { s.email_confirmation_setting.to_s.downcase }

password_complexity_value =
  begin
    lowercase = safe(nil) { s.password_lowercase_required }
    uppercase = safe(nil) { s.password_uppercase_required }
    number    = safe(nil) { s.password_number_required }
    symbol    = safe(nil) { s.password_symbol_required }

    if [lowercase, uppercase, number, symbol].any?(&:nil?)
      'N/A'
    else
      [lowercase, uppercase, number, symbol].all? ? 'ON' : 'OFF'
    end
  rescue
    'N/A'
  end

pat_expiry_required = yn(safe { s.require_personal_access_token_expiry })
pat_max_lifetime    = safe(nil) { s.max_personal_access_token_lifetime }

dsa_key_restriction = safe do
  s.dsa_key_restriction == -1 ? 'Forbidden' : s.dsa_key_restriction
end

rsa_key_restriction     = safe { s.rsa_key_restriction }
ed25519_key_restriction = safe(nil) { s.ed25519_key_restriction }
ecdsa_key_restriction   = safe(nil) { s.ecdsa_key_restriction }

ed25519_key_status = allowed_key_status(ed25519_key_restriction)
ecdsa_key_status   = allowed_key_status(ecdsa_key_restriction)

outbound_webhooks     = yn(safe { s.allow_local_requests_from_web_hooks_and_services })
outbound_system_hooks = yn(safe { s.allow_local_requests_from_system_hooks })

instance_runners_enabled =
  begin
    if s.respond_to?(:instance_runners_enabled)
      yn(s.instance_runners_enabled)
    elsif s.respond_to?(:shared_runners_enabled)
      yn(s.shared_runners_enabled)
    else
      'N/A'
    end
  rescue
    'N/A'
  end

gravatar_enabled     = yn(safe { s.gravatar_enabled })
session_expire_delay = safe { s.session_expire_delay }

repository_size_limit  = safe(nil) { s.repository_size_limit }
max_artifacts_size     = safe(nil) { s.max_artifacts_size }
dotenv_variables_limit = safe('N/A') { Plan.default.actual_limits.dotenv_variables }
max_attachment_size    = safe(nil) { s.max_attachment_size }

web_ide_single_origin_fallback = yn(safe(nil) { s.vscode_extension_marketplace_single_origin_fallback_enabled })

puts '------------------------------------------------------------'
puts '[1] 자동 판별 가능 항목'
puts '------------------------------------------------------------'
puts

print_check('Sign-up enabled', signup_enabled, 'OFF')
print_check('Allowed domains for sign-ups', normalize_array(allowed_domains), normalize_array(TARGET_ALLOWED_DOMAINS))
print_check('Default project visibility', default_project_visibility, 'Private')
print_check('Default group visibility', default_group_visibility, 'Private')
print_check('Restricted visibility levels', normalize_array(restricted_visibility), normalize_array(TARGET_RESTRICTED_VISIBILITY))
print_check('Minimum role for project creation', default_project_creation, TARGET_PROJECT_CREATION)
print_check('Git access protocol', git_access_protocol, TARGET_GIT_ACCESS_PROTOCOL)

print_check('Require two-factor authentication', require_2fa, TARGET_2FA_REQUIRED)
print_check('Two-factor grace period', two_factor_grace_period, TARGET_2FA_GRACE_HOURS)
print_check('Admin Mode', admin_mode, TARGET_ADMIN_MODE)
print_check('Unknown sign-in notification', unknown_signin_notification, 'ON')

print_check('Password minimum length', minimum_password_length, TARGET_PASSWORD_MIN_LENGTH)
print_check('Password complexity', password_complexity_value, :na)
print_check('Email confirmation', email_confirmation, TARGET_EMAIL_CONFIRMATION)

print_check('Personal access token expiry', pat_expiry_required, TARGET_PAT_EXPIRY_REQUIRED)
print_check('Personal access token max lifetime', pat_max_lifetime, TARGET_PAT_MAX_LIFETIME)

print_check('DSA SSH keys', dsa_key_restriction, TARGET_DSA_RESTRICTION)
print_check_min('RSA SSH keys minimum bits', rsa_key_restriction, TARGET_RSA_MIN_BITS)
print_check('ED25519 SSH keys', ed25519_key_status, TARGET_ED25519_ALLOWED)
print_check('ECDSA SSH keys', ecdsa_key_status, TARGET_ECDSA_ALLOWED)

print_check('Outbound requests (webhooks/integrations)', outbound_webhooks, TARGET_OUTBOUND_WEBHOOKS)
print_check('Outbound requests (system hooks)', outbound_system_hooks, TARGET_OUTBOUND_SYSTEM_HOOKS)

print_check('Instance runners', instance_runners_enabled, TARGET_INSTANCE_RUNNERS)
print_check('Gravatar', gravatar_enabled, TARGET_GRAVATAR)
print_check('Session duration (minutes)', session_expire_delay, TARGET_SESSION_EXPIRE_MIN)

# 문서 기준: Repository size limit 은 선택 적용 항목이므로 수동 판단
print_check('Repository size limit (MB)', repository_size_limit.nil? ? '미설정' : repository_size_limit, :manual)

print_check('Max artifacts size (MB)', max_artifacts_size, TARGET_MAX_ARTIFACTS_MB)
print_check('CI/CD dotenv variables limit', dotenv_variables_limit, TARGET_DOTENV_VARIABLES_LIMIT)
print_check('Max attachment size (MB)', max_attachment_size, TARGET_MAX_ATTACHMENT_MB)

print_check('Web IDE single origin fallback', web_ide_single_origin_fallback, TARGET_WEB_IDE_FALLBACK)

puts '------------------------------------------------------------'
puts '[2] 수동 확인 필요 항목'
puts '------------------------------------------------------------'
puts

print_check('root 비밀번호 변경', '수동 확인 필요', :manual)
print_check('root 이메일 수정', '수동 확인 필요', :manual)
print_check('개인 관리자 계정 생성', '수동 확인 필요', :manual)
print_check('Runner registration token', 'CI/CD > Runners 화면에서 Disabled 여부 수동 확인', :manual)
print_check('SMTP 설정', 'gitlab.rb / reconfigure / 발송 테스트 수동 확인', :manual)
print_check('gitlab-secrets.json 외부 백업', '수동 확인 필요', :manual)
print_check('gitlab.rb 외부 백업', '수동 확인 필요', :manual)

puts '------------------------------------------------------------'
puts '[3] 참고'
puts '------------------------------------------------------------'
puts '- 문서 기준으로 Password complexity 는 CE 미지원이므로 N/A 처리함'
puts '- Email confirmation setting 은 off / soft / hard 값으로 판별함'
puts '- Git access protocol 은 일부 버전에서 rails setting 값이 비어 있을 수 있음'
puts '- Unknown sign-in notification 은 notify_on_unknown_sign_in 속성으로 자동 판별함'
puts '- ED25519 / ECDSA 는 nil 또는 0 값을 일반적으로 Allow 로 해석함'
puts '- Repository size limit 은 선택 적용 항목이므로 자동 PASS/FAIL 대신 수동 판단 대상으로 처리함'
puts '- CI/CD dotenv variables limit 은 Plan.default.actual_limits.dotenv_variables 기준으로 판별함'
puts '- Instance runners 는 문서 기준 최신 공식 용어로만 표기함'
puts

puts '------------------------------------------------------------'
puts '[4] 요약'
puts '------------------------------------------------------------'
puts \"PASS   : #{\$pass_count}\"
puts \"FAIL   : #{\$fail_count}\"
puts \"MANUAL : #{\$manual_count}\"
puts \"N/A    : #{\$na_count}\"
puts
puts '============================================================'

exit(\$fail_count > 0 ? 1 : 0)
"

exit 0

# =============================================================================
# 권한:
# 	sudo chmod 700 ./gitlab-policy-check.sh
#
# Windows CRLF / UTF-8 BOM 제거가 필요한 경우:
# 	sudo sed -i '1s/^\xEF\xBB\xBF//' ./gitlab-policy-check.sh
# 	sudo sed -i 's/\r$//' ./gitlab-policy-check.sh
#
# 문법 확인:
# 	sudo bash -n ./gitlab-policy-check.sh
#
# 사용법:
# 	sudo bash ./gitlab-policy-check.sh sncompany.com
