Files

791 lines
23 KiB
PHP

<?php
function auth_session_start_if_needed(): void
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
function auth_cookie_options(int $expires): array
{
return [
'expires' => $expires,
'path' => '/',
'samesite' => 'Lax',
];
}
function auth_json_cookie_encode($value): string
{
return base64_encode(json_encode($value, JSON_UNESCAPED_UNICODE));
}
function auth_json_cookie_decode(string $value, $default)
{
if ($value === '') {
return $default;
}
$decoded = base64_decode($value, true);
if ($decoded === false) {
return $default;
}
$json = json_decode($decoded, true);
return $json === null ? $default : $json;
}
function auth_current_host(): string
{
return $_SERVER['HTTP_HOST'] ?? 'eg-bim.com';
}
function auth_current_scheme(): string
{
$https = $_SERVER['HTTPS'] ?? '';
$forwardedProto = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '';
if ($https === 'on' || $https === '1' || strtolower((string) $forwardedProto) === 'https') {
return 'https';
}
return 'http';
}
function auth_absolute_url(string $path): string
{
return auth_current_scheme() . '://' . auth_current_host() . $path;
}
function auth_site_base_url(string $site): string
{
return $site === 'eng' ? '/eng' : '/egbim';
}
function auth_site_qna_url(string $site): string
{
return auth_site_base_url($site) . '/bbs/descope_qa_list.php';
}
function auth_baron_config(): array
{
$scopes = trim((string) (getenv('BARON_SSO_SCOPE') ?: 'openid profile email'));
return [
'client_id' => trim((string) getenv('BARON_SSO_CLIENT_ID')),
'client_secret' => trim((string) getenv('BARON_SSO_CLIENT_SECRET')),
'authorize_endpoint' => trim((string) getenv('BARON_SSO_AUTHORIZE_ENDPOINT')),
'token_endpoint' => trim((string) getenv('BARON_SSO_TOKEN_ENDPOINT')),
'userinfo_endpoint' => trim((string) getenv('BARON_SSO_USERINFO_ENDPOINT')),
'issuer' => trim((string) getenv('BARON_SSO_ISSUER')),
'redirect_uri' => trim((string) getenv('BARON_SSO_REDIRECT_URI')),
'scope' => $scopes,
'client_auth_method' => trim((string) (getenv('BARON_SSO_CLIENT_AUTH_METHOD') ?: 'basic')),
];
}
function auth_baron_redirect_uri(): string
{
$config = auth_baron_config();
if ($config['redirect_uri'] !== '') {
return $config['redirect_uri'];
}
return auth_absolute_url('/auth/callback');
}
function auth_baron_is_configured(): bool
{
$config = auth_baron_config();
return $config['client_id'] !== ''
&& $config['client_secret'] !== ''
&& $config['authorize_endpoint'] !== ''
&& $config['token_endpoint'] !== '';
}
function auth_normalize_user(array $user): array
{
$loginIds = $user['loginIds'] ?? [];
if (!is_array($loginIds)) {
$loginIds = [];
}
$loginId = trim((string) ($loginIds[0] ?? $user['loginId'] ?? $user['email'] ?? ''));
if ($loginId !== '' && empty($loginIds)) {
$loginIds = [$loginId];
}
$customAttributes = $user['customAttributes'] ?? [];
$roleNames = $user['roleNames'] ?? [];
return [
'userId' => trim((string) ($user['userId'] ?? $loginId)),
'loginIds' => $loginIds,
'name' => trim((string) ($user['name'] ?? '')),
'email' => trim((string) ($user['email'] ?? $loginId)),
'phone' => trim((string) ($user['phone'] ?? '')),
'customAttributes' => is_array($customAttributes) ? $customAttributes : [],
'roleNames' => is_array($roleNames) ? $roleNames : [],
];
}
function auth_descope_user_from_cookies(): ?array
{
$loginId = trim((string) ($_COOKIE['descope_login_id'] ?? ''));
if ($loginId === '') {
return null;
}
return auth_normalize_user([
'userId' => trim((string) ($_COOKIE['descope_user_id'] ?? $loginId)),
'loginIds' => [$loginId],
'name' => trim((string) ($_COOKIE['descope_user_name'] ?? '')),
'email' => trim((string) ($_COOKIE['descope_user_email'] ?? $loginId)),
'phone' => trim((string) ($_COOKIE['descope_user_phone'] ?? '')),
'customAttributes' => auth_json_cookie_decode((string) ($_COOKIE['descope_custom_attributes'] ?? ''), []),
'roleNames' => auth_json_cookie_decode((string) ($_COOKIE['descope_role_names'] ?? ''), []),
]);
}
function auth_baron_user_from_cookies(): ?array
{
$encodedUser = (string) ($_COOKIE['baron_user'] ?? '');
if ($encodedUser === '') {
return null;
}
$user = auth_json_cookie_decode($encodedUser, []);
if (!is_array($user) || empty($user)) {
return null;
}
return auth_normalize_user($user);
}
function auth_restore_user_session(): void
{
auth_session_start_if_needed();
if (!empty($_SESSION['user']['userId'])) {
$_SESSION['user'] = auth_normalize_user($_SESSION['user']);
return;
}
$user = auth_baron_user_from_cookies();
if ($user === null) {
$user = auth_descope_user_from_cookies();
}
if ($user !== null) {
$_SESSION['user'] = $user;
}
}
function auth_apply_user_session(array $user, string $provider, array $tokens = []): array
{
auth_session_start_if_needed();
session_regenerate_id(true);
$normalizedUser = auth_normalize_user($user);
$_SESSION['user'] = $normalizedUser;
$_SESSION['auth_provider'] = $provider;
foreach ($tokens as $key => $value) {
$_SESSION[$key] = $value;
}
return $normalizedUser;
}
function auth_set_descope_session_cookies(array $user): void
{
$normalizedUser = auth_normalize_user($user);
$expire = time() + 86400;
$cookieOptions = auth_cookie_options($expire);
setcookie('descope_login_id', (string) ($normalizedUser['loginIds'][0] ?? ''), $cookieOptions);
setcookie('descope_user_id', (string) $normalizedUser['userId'], $cookieOptions);
setcookie('descope_user_name', (string) $normalizedUser['name'], $cookieOptions);
setcookie('descope_user_email', (string) $normalizedUser['email'], $cookieOptions);
setcookie('descope_user_phone', (string) $normalizedUser['phone'], $cookieOptions);
setcookie('descope_custom_attributes', auth_json_cookie_encode($normalizedUser['customAttributes']), $cookieOptions);
setcookie('descope_role_names', auth_json_cookie_encode($normalizedUser['roleNames']), $cookieOptions);
}
function auth_set_baron_session_cookies(array $user, array $claims = []): void
{
$normalizedUser = auth_normalize_user($user);
$expire = time() + 86400;
$cookieOptions = auth_cookie_options($expire);
setcookie('baron_user', auth_json_cookie_encode($normalizedUser), $cookieOptions);
setcookie('baron_claims', auth_json_cookie_encode($claims), $cookieOptions);
setcookie('baron_provider', 'baron', $cookieOptions);
}
function auth_clear_all_cookies(): void
{
$expired = auth_cookie_options(time() - 42000);
foreach ([
'PHPSESSID',
'G5sessphp',
'descope_login_id',
'descope_user_id',
'descope_user_name',
'descope_user_email',
'descope_user_phone',
'descope_custom_attributes',
'descope_role_names',
'baron_user',
'baron_claims',
'baron_provider',
] as $cookieName) {
setcookie($cookieName, '', $expired);
}
}
function auth_admin_emails(): array
{
return [
'b23008@baroncs.co.kr',
'kjy0426@hanmaceng.co.kr',
'b24014@hanmaceng.co.kr',
'b23065@hanmaceng.co.kr',
'cyhan@samaneng.com',
'shyeom1@samaneng.com',
'cjy627@hanmaceng.co.kr',
'b23072@hanmaceng.co.kr',
'cozyjin@hanmaceng.co.kr',
'm24031@hanmaceng.co.kr',
'b24051@hanmaceng.co.kr',
'rmsgud1202@hanmaceng.co.kr',
'm21318@hanmaceng.co.kr',
'b21367@hanmaceng.co.kr',
'b25023@hanmaceng.co.kr',
'sdi9429@naver.com',
'junsuy@hanmail.net',
'ilphilo92@gmail.com',
];
}
function auth_normalize_identity(string $value): string
{
return strtolower(trim($value));
}
function auth_is_admin_login(string $loginId): bool
{
$normalizedLoginId = auth_normalize_identity($loginId);
if ($normalizedLoginId === '') {
return false;
}
foreach (auth_admin_emails() as $adminLoginId) {
if ($normalizedLoginId === auth_normalize_identity($adminLoginId)) {
return true;
}
}
return false;
}
function auth_is_admin_user(array $user, ?string $loginId = null): bool
{
$candidates = [];
if ($loginId !== null) {
$candidates[] = $loginId;
}
$loginIds = $user['loginIds'] ?? [];
if (is_array($loginIds)) {
foreach ($loginIds as $candidate) {
if (is_string($candidate)) {
$candidates[] = $candidate;
}
}
}
$candidates[] = (string) ($user['email'] ?? '');
$customAttributes = $user['customAttributes'] ?? [];
if (is_array($customAttributes)) {
$candidates[] = (string) ($customAttributes['employeeId'] ?? '');
$candidates[] = (string) ($customAttributes['familyUniqueKey'] ?? '');
}
foreach ($candidates as $candidate) {
if (auth_is_admin_login((string) $candidate)) {
return true;
}
}
$roleNames = $user['roleNames'] ?? [];
if (is_array($roleNames)) {
foreach ($roleNames as $roleName) {
$normalizedRole = auth_normalize_identity((string) $roleName);
if ($normalizedRole === 'super' || $normalizedRole === 'admin' || $normalizedRole === 'administrator') {
return true;
}
}
}
return false;
}
function auth_sync_gnuboard_session(): array
{
auth_restore_user_session();
$loginIds = $_SESSION['user']['loginIds'] ?? [];
$loginId = (is_array($loginIds) && !empty($loginIds)) ? trim((string) $loginIds[0]) : '';
$userName = trim((string) ($_SESSION['user']['name'] ?? ''));
$userEmail = trim((string) ($_SESSION['user']['email'] ?? ''));
$isAdmin = !empty($_SESSION['user']) && is_array($_SESSION['user'])
? auth_is_admin_user($_SESSION['user'], $loginId)
: false;
if ($loginId !== '') {
$_SESSION['ss_mb_id'] = $loginId;
$_SESSION['ss_mb_level'] = $isAdmin ? 10 : 2;
return [
'loginId' => $loginId,
'isAdmin' => $isAdmin,
'is_member' => true,
'is_admin' => $isAdmin ? 'super' : '',
'member' => [
'mb_id' => $loginId,
'mb_name' => $userName !== '' ? $userName : $loginId,
'mb_email' => $userEmail,
'mb_level' => $_SESSION['ss_mb_level'],
'mb_nick' => $userName !== '' ? $userName : $loginId,
],
];
}
unset($_SESSION['ss_mb_id'], $_SESSION['ss_mb_level']);
return [
'loginId' => '',
'isAdmin' => false,
'is_member' => false,
'is_admin' => '',
'member' => [],
];
}
function auth_url_is_safe(string $url, string $site): bool
{
if ($url === '') {
return false;
}
if (strpos($url, '://') !== false) {
return false;
}
return strpos($url, auth_site_base_url($site) . '/') === 0;
}
function auth_baron_state_payload(string $site, string $returnUrl): array
{
return [
'site' => $site,
'return_url' => auth_url_is_safe($returnUrl, $site) ? $returnUrl : auth_site_qna_url($site),
'nonce' => bin2hex(random_bytes(16)),
'created_at' => time(),
];
}
function auth_store_baron_state(array $payload): string
{
auth_session_start_if_needed();
$state = bin2hex(random_bytes(16));
$_SESSION['baron_oauth_state'] = [
'value' => $state,
'payload' => $payload,
];
return $state;
}
function auth_consume_baron_state(string $state): ?array
{
auth_session_start_if_needed();
$stored = $_SESSION['baron_oauth_state'] ?? null;
unset($_SESSION['baron_oauth_state']);
if (!is_array($stored) || ($stored['value'] ?? '') !== $state) {
return null;
}
$payload = $stored['payload'] ?? null;
return is_array($payload) ? $payload : null;
}
function auth_http_post_form(string $url, array $data, array $headers = [], ?string $username = null, ?string $password = null): array
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
$mergedHeaders = array_merge([
'Accept: application/json',
'Content-Type: application/x-www-form-urlencoded',
], $headers);
curl_setopt($ch, CURLOPT_HTTPHEADER, $mergedHeaders);
if ($username !== null) {
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $username . ':' . ($password ?? ''));
}
$response = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return [
'body' => $response === false ? '' : $response,
'http_code' => $httpCode,
'error' => $error,
];
}
function auth_http_get_json(string $url, string $accessToken): array
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json',
'Authorization: Bearer ' . $accessToken,
]);
$response = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return [
'body' => $response === false ? '' : $response,
'http_code' => $httpCode,
'error' => $error,
];
}
function auth_jwt_payload(string $jwt): array
{
$parts = explode('.', $jwt);
if (count($parts) < 2) {
return [];
}
$payload = strtr($parts[1], '-_', '+/');
$padding = strlen($payload) % 4;
if ($padding > 0) {
$payload .= str_repeat('=', 4 - $padding);
}
$decoded = base64_decode($payload, true);
if ($decoded === false) {
return [];
}
$json = json_decode($decoded, true);
return is_array($json) ? $json : [];
}
function auth_first_non_empty_string(...$values): string
{
foreach ($values as $value) {
if (is_string($value)) {
$trimmed = trim($value);
if ($trimmed !== '') {
return $trimmed;
}
}
}
return '';
}
function auth_claim_list($value): array
{
if (is_array($value)) {
return $value;
}
$stringValue = trim((string) $value);
if ($stringValue === '') {
return [];
}
return [$stringValue];
}
function auth_extract_phone_from_claims(array $claims): string
{
$profile = isset($claims['profile']) && is_array($claims['profile']) ? $claims['profile'] : [];
$phones = auth_claim_list($claims['phones'] ?? ($profile['phones'] ?? []));
foreach ($phones as $phoneEntry) {
if (is_array($phoneEntry)) {
$candidate = auth_first_non_empty_string(
$phoneEntry['value'] ?? '',
$phoneEntry['phone_number'] ?? '',
$phoneEntry['number'] ?? ''
);
if ($candidate !== '') {
return $candidate;
}
continue;
}
$candidate = trim((string) $phoneEntry);
if ($candidate !== '') {
return $candidate;
}
}
return auth_first_non_empty_string(
$claims['phone_number'] ?? '',
$claims['phone'] ?? '',
$profile['phone_number'] ?? '',
$profile['phone'] ?? ''
);
}
function auth_extract_company_fields(array $claims): array
{
$company = auth_first_non_empty_string(
$claims['company'] ?? '',
$claims['company_name'] ?? '',
$claims['organization'] ?? '',
$claims['organization_name'] ?? '',
$claims['tenant_name'] ?? ''
);
$familyCompany = auth_first_non_empty_string(
$claims['familyCompany'] ?? '',
$claims['family_company'] ?? '',
$claims['group_name'] ?? '',
$claims['affiliate'] ?? ''
);
$team = auth_first_non_empty_string(
$claims['team'] ?? '',
$claims['department'] ?? '',
$claims['dept'] ?? '',
$claims['division'] ?? '',
$claims['org_unit'] ?? ''
);
$tenants = auth_claim_list($claims['tenants'] ?? []);
$primaryTenant = null;
$tenantId = trim((string) ($claims['tenant_id'] ?? ''));
foreach ($tenants as $tenantKey => $tenant) {
if (!is_array($tenant)) {
continue;
}
$candidateId = trim((string) ($tenant['id'] ?? (is_string($tenantKey) ? $tenantKey : '')));
if (($tenant['isPrimary'] ?? false) === true || ($tenant['representative'] ?? false) === true || ($tenantId !== '' && $candidateId === $tenantId)) {
$primaryTenant = $tenant;
break;
}
}
if ($primaryTenant === null) {
foreach ($tenants as $tenant) {
if (is_array($tenant)) {
$primaryTenant = $tenant;
break;
}
}
}
if (is_array($primaryTenant)) {
$companyAncestor = '';
$familyCompanyAncestor = '';
$departmentAncestor = '';
$ancestors = $primaryTenant['ancestors'] ?? [];
if (is_array($ancestors)) {
foreach ($ancestors as $ancestor) {
if (!is_array($ancestor)) {
continue;
}
$ancestorType = strtoupper(trim((string) ($ancestor['type'] ?? '')));
$ancestorName = trim((string) ($ancestor['name'] ?? ''));
if ($ancestorName === '') {
continue;
}
if ($companyAncestor === '' && $ancestorType === 'COMPANY') {
$companyAncestor = $ancestorName;
}
if ($familyCompanyAncestor === '' && $ancestorType === 'COMPANY_GROUP') {
$familyCompanyAncestor = $ancestorName;
}
if ($ancestorType === 'ORGANIZATION') {
$departmentAncestor = $ancestorName;
}
}
}
$company = auth_first_non_empty_string(
$company,
$companyAncestor,
trim((string) ($primaryTenant['name'] ?? ''))
);
$familyCompany = auth_first_non_empty_string(
$familyCompany,
$companyAncestor,
$familyCompanyAncestor,
$company
);
$team = auth_first_non_empty_string(
$team,
$departmentAncestor,
trim((string) ($primaryTenant['name'] ?? ''))
);
}
foreach ($tenants as $tenant) {
if (!is_array($tenant)) {
if ($company === '') {
$company = trim((string) $tenant);
}
continue;
}
if ($company === '') {
$company = auth_first_non_empty_string(
$tenant['name'] ?? '',
$tenant['display_name'] ?? '',
$tenant['company'] ?? '',
$tenant['company_name'] ?? '',
$tenant['organization'] ?? ''
);
}
if ($familyCompany === '') {
$familyCompany = auth_first_non_empty_string(
$tenant['familyCompany'] ?? '',
$tenant['family_company'] ?? '',
$tenant['group_name'] ?? '',
$tenant['affiliate'] ?? ''
);
}
if ($team === '') {
$team = auth_first_non_empty_string(
$tenant['team'] ?? '',
$tenant['department'] ?? '',
$tenant['dept'] ?? '',
$tenant['division'] ?? '',
$tenant['org_unit'] ?? ''
);
}
}
return [
'company' => $company,
'familyCompany' => $familyCompany,
'team' => $team,
];
}
function auth_baron_user_from_claims(array $claims): array
{
$profile = isset($claims['profile']) && is_array($claims['profile']) ? $claims['profile'] : [];
$email = trim((string) ($claims['email'] ?? $profile['email'] ?? $claims['upn'] ?? $claims['preferred_username'] ?? $claims['sub'] ?? ''));
$name = trim((string) ($claims['name'] ?? $profile['name'] ?? $claims['display_name'] ?? $claims['preferred_username'] ?? $email));
$phone = auth_extract_phone_from_claims($claims);
$companyFields = auth_extract_company_fields($claims);
$company = $companyFields['company'];
$familyCompany = $companyFields['familyCompany'];
$team = $companyFields['team'];
$employeeId = trim((string) ($claims['employee_id'] ?? $profile['employee_id'] ?? $claims['employeeId'] ?? $claims['familyUniqueKey'] ?? ''));
$status = trim((string) ($claims['status'] ?? $profile['status'] ?? ''));
$tenants = auth_claim_list($claims['tenants'] ?? []);
$secondaryEmails = auth_claim_list($claims['secondary_emails'] ?? $claims['secondaryEmails'] ?? ($profile['secondary_emails'] ?? []));
$roles = auth_claim_list($claims['roles'] ?? $claims['roleNames'] ?? []);
$position = auth_first_non_empty_string(
$claims['position'] ?? '',
$profile['position'] ?? ''
);
foreach ($tenants as $tenant) {
if (!is_array($tenant)) {
continue;
}
if ($position === '') {
$position = auth_first_non_empty_string(
$tenant['grade'] ?? '',
$tenant['position'] ?? ''
);
}
}
return auth_normalize_user([
'userId' => trim((string) ($claims['sub'] ?? $email)),
'loginIds' => $email !== '' ? [$email] : [],
'name' => $name,
'email' => $email,
'phone' => $phone,
'customAttributes' => [
'company' => $company,
'familyCompany' => $familyCompany,
'team' => $team,
'position' => $position,
'employeeId' => $employeeId,
'familyUniqueKey' => $employeeId,
'status' => $status,
'tenants' => $tenants,
'secondaryEmails' => $secondaryEmails,
],
'roleNames' => $roles,
]);
}
function auth_script_redirect(string $message, string $redirectUrl): void
{
$safeMessage = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
$safeUrl = htmlspecialchars($redirectUrl, ENT_QUOTES, 'UTF-8');
echo "<script>alert('{$safeMessage}'); window.location.href = '{$safeUrl}';</script>";
exit;
}
function auth_is_local_debug(): bool
{
return (getenv('APP_ENV') ?: '') === 'local';
}
function auth_compact_debug_value(string $value, int $maxLength = 300): string
{
$value = preg_replace('/\s+/', ' ', trim($value)) ?? '';
if (strlen($value) <= $maxLength) {
return $value;
}
return substr($value, 0, $maxLength) . '...';
}