환자 미리보기 접근 규칙을 먼저 세운다 — 레거시에서 옮기지 않을 것들
미리보기 레거시 호환 계획의 A3 첫 조각. DB 조회는 조사 중이라 규칙과 감사부터 세운다.
<b>레거시에서 옮기지 않는 것이 요점이다.</b>
· 폼이 뜰 때마다 인가코드를 InputBox 로 묻는데 <b>통과값 3개가 소스에 평문</b>이다
(frmSheetDesigner.vb:913)
· 환자를 검색조건 없이 <b>운영 전체</b>에서 찾는다(:636)
· 누가 어느 환자를 열었는지 <b>남기지 않는다</b>(감사 0건)
셋을 그대로 옮기면 신규 앱이 최악을 물려받는다.
검색 범위는 레거시와 같게 두기로 했다(전체 검색 + 감사 로그).
그래서 <b>남기는 쪽</b>이 안전장치의 본체다 — 조회를 막지 않는 대신 흔적을 남긴다.
AppLog.Audit 은 FIXED 레벨이라 어느 로그 설정에서도 남는다.
권한 축은 ModifyPolicy 와 같은 것을 쓴다. 환자 기록을 보는 일은
지금 쓰이는 서식을 제자리에서 고치는 일과 무게가 다르지 않다.
미인증이면 막는다 — 주체가 없으면 감사에 남길 것이 없고, 남길 수 없는 조회는 하지 않는다.
<b>감사 한 줄에 환자 이름은 넣지 않는다.</b> 내원번호로 되짚을 수 있고,
로그는 오래 남고 접근 통제가 DB 보다 느슨하다. 검사로 못 박았다.
게이트: 테스트 303/303(신규 4), --edit-smoke 0실패,
--db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5072acb518
commit
e9d8f7356e
@@ -0,0 +1,49 @@
|
|||||||
|
namespace SheetMe.Core.Catalog;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 환자 데이터로 미리보기를 볼 수 있는 조건.
|
||||||
|
///
|
||||||
|
/// <b>레거시에서 옮기지 않는 것이 요점이다.</b> 레거시 서식생성기는
|
||||||
|
/// · 폼이 뜰 때마다 인가코드를 InputBox 로 묻고 <b>통과값 3개가 소스에 평문</b>으로 박혀 있고
|
||||||
|
/// (<c>frmSheetDesigner.vb:913</c>),
|
||||||
|
/// · 환자를 검색조건 없이 <b>운영 전체</b>에서 찾을 수 있고(<c>:636</c>),
|
||||||
|
/// · 누가 어느 환자를 열었는지 <b>남기지 않는다</b>(감사 0건).
|
||||||
|
/// 그 셋을 그대로 옮기면 신규 앱이 최악을 물려받는다.
|
||||||
|
///
|
||||||
|
/// 여기서 정하는 것은 <b>누가 볼 수 있는가</b>뿐이다. 검색 범위는 레거시와 같게 두기로 했고
|
||||||
|
/// (전체 검색 + 감사 로그), 그래서 <b>남기는 쪽</b>이 안전장치의 본체다 —
|
||||||
|
/// 조회 자체를 막지 않는 대신 흔적을 남긴다.
|
||||||
|
///
|
||||||
|
/// 조건은 <see cref="ModifyPolicy"/> 와 같은 축을 쓴다. 환자 기록을 보는 일은
|
||||||
|
/// 지금 쓰이는 서식을 제자리에서 고치는 일과 무게가 다르지 않다.
|
||||||
|
/// </summary>
|
||||||
|
public static class PatientPreviewPolicy
|
||||||
|
{
|
||||||
|
#region Methods
|
||||||
|
/// <summary>
|
||||||
|
/// 이 사용자가 환자 데이터로 미리보기를 볼 수 있는가.
|
||||||
|
///
|
||||||
|
/// 인증된 HIS 사용자여야 한다 — 미인증이면 감사 로그에 남길 주체가 없고,
|
||||||
|
/// 주체 없는 환자 조회는 그 자체로 남기면 안 되는 일이다.
|
||||||
|
/// </summary>
|
||||||
|
public static bool CanPreviewPatient(string hospitalCode, string departmentCode, string uidCod,
|
||||||
|
string hospitalStartDate)
|
||||||
|
=> uidCod.Length > 0
|
||||||
|
&& ModifyPolicy.CanModify(hospitalCode, departmentCode, uidCod, hospitalStartDate);
|
||||||
|
|
||||||
|
/// <summary>왜 막혔는지 — "권한이 없습니다"만으로는 누구에게 물어야 할지 모른다</summary>
|
||||||
|
public static string DenyReason(string hospitalCode, string uidCod, string hospitalStartDate)
|
||||||
|
=> uidCod.Length == 0
|
||||||
|
? "HIS 사용자가 확인되지 않았습니다. 기록지정보 화면에서 실행하거나 사용자 코드를 전달해 주세요."
|
||||||
|
: ModifyPolicy.DenyReason(hospitalCode, uidCod, hospitalStartDate);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 감사 로그 한 줄 — 누가·언제·어느 내원을 열었는가.
|
||||||
|
///
|
||||||
|
/// 문구를 여기서 만드는 이유: 호출부마다 다르게 적으면 나중에 로그를 찾을 수 없다.
|
||||||
|
/// 환자 이름은 넣지 않는다 — 내원번호로 되짚을 수 있고, 로그 파일에 이름을 흘릴 이유가 없다.
|
||||||
|
/// </summary>
|
||||||
|
public static string AuditLine(string uidCod, string comNum)
|
||||||
|
=> $"[환자 미리보기] 사용자={uidCod} 내원={comNum}";
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using SheetMe.Core.Catalog;
|
||||||
|
|
||||||
|
namespace SheetMe.Core.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 환자 미리보기 접근 규칙 고정.
|
||||||
|
///
|
||||||
|
/// 이 표가 느슨해지면 서식을 만드는 사람 누구나 <b>운영 환자 기록</b>을 열 수 있다.
|
||||||
|
/// 레거시가 그 상태였다 — 인가코드가 소스에 평문이고, 검색 범위 무제한이고, 감사 0건이다.
|
||||||
|
/// 검색 범위는 레거시와 같게 두기로 했으므로 <b>감사와 권한</b>이 유일한 장치다.
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
public sealed class PatientPreviewPolicyTests
|
||||||
|
{
|
||||||
|
#region Methods
|
||||||
|
[TestMethod]
|
||||||
|
public void 미인증이면_볼_수_없다()
|
||||||
|
{
|
||||||
|
// 주체가 없으면 감사 로그에 남길 것이 없다 — 남길 수 없는 조회는 하지 않는다.
|
||||||
|
Assert.IsFalse(PatientPreviewPolicy.CanPreviewPatient("ABC", "EDPS", string.Empty, string.Empty));
|
||||||
|
StringAssert.Contains(
|
||||||
|
PatientPreviewPolicy.DenyReason("ABC", string.Empty, string.Empty), "HIS 사용자");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void 수정_권한과_같은_축을_쓴다()
|
||||||
|
{
|
||||||
|
// 환자 기록을 보는 일은 지금 쓰이는 서식을 제자리에서 고치는 일과 무게가 다르지 않다.
|
||||||
|
Assert.IsTrue(PatientPreviewPolicy.CanPreviewPatient("ABC", "EDPS", "011825", string.Empty));
|
||||||
|
Assert.IsFalse(PatientPreviewPolicy.CanPreviewPatient("ABC", "NURS", "011825", string.Empty));
|
||||||
|
Assert.IsTrue(PatientPreviewPolicy.CanPreviewPatient("HANKOOK", "SALO", "011825", string.Empty));
|
||||||
|
Assert.IsFalse(PatientPreviewPolicy.CanPreviewPatient("XYZ", "EDPS", "011825", "20250501"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void 감사_한_줄에_주체와_내원이_들어간다()
|
||||||
|
{
|
||||||
|
var line = PatientPreviewPolicy.AuditLine("011825", "C12345");
|
||||||
|
StringAssert.Contains(line, "011825");
|
||||||
|
StringAssert.Contains(line, "C12345");
|
||||||
|
StringAssert.Contains(line, "환자 미리보기");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void 감사_한_줄에_환자_이름은_넣지_않는다()
|
||||||
|
{
|
||||||
|
// 내원번호로 되짚을 수 있다. 로그 파일에 이름을 흘릴 이유가 없다 —
|
||||||
|
// 로그는 오래 남고 접근 통제가 DB 보다 느슨하다.
|
||||||
|
var line = PatientPreviewPolicy.AuditLine("011825", "C12345");
|
||||||
|
Assert.IsFalse(line.Contains("이름", StringComparison.Ordinal));
|
||||||
|
Assert.AreEqual(2, line.Split('=').Length - 1, $"항목이 둘(사용자·내원)이어야 한다: {line}");
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user