환자 미리보기에서 부서 문을 뺀다 — 레거시에 없던 제약을 내가 붙였다
개발자 계정으로 환자 선택을 누르면 "전산실 소속만 가능합니다"가 떴다. PatientPreviewPolicy 가 ModifyPolicy.CanModify 를 그대로 호출하고 있었기 때문이다. ## 근거를 다시 읽었다 레거시가 부서로 가리는 툴은 TK_MODIFY 하나뿐이다(frmSheetDesigner.vb:1030-1060). TK_ExportXML / TK_ImportXML 은 MSYS 계정 문이고, TK_PREVIEW 에는 가시성 조건이 하나도 걸려 있지 않다 — 서식생성기를 열 수 있으면 쓸 수 있다. 즉 이 제약은 레거시에서 옮긴 것이 아니라 내가 "무게가 비슷하다"고 판단해 붙인 것이다. 사용자가 정한 것도 아니었다 — 환자 접근에 대한 결정은 "전체 검색 + 감사 로그"였고 부서 제한은 거기에 없다. ## 두 일의 무게가 실제로 다르다 수정은 지금 쓰이는 서식을 제자리에서 바꿔 병원 전체에 즉시 영향을 준다. 미리보기는 이미 그 사람이 볼 수 있는 기록을 읽기만 한다. 그리고 이 문은 막아서 더 위험해지는 쪽이었다. 서식을 만드는 사람이 자기 서식을 검증할 수 없으면 값이 틀린 서식이 그대로 운영에 올라간다. 태그가 정확한 값을 뽑는지 확인하는 것이 이 기능을 만든 이유였는데 그 확인을 만든 사람에게서 빼앗고 있었다. ## 바꾼 것 CanPreviewPatient 는 인증만 요구한다 — 미인증이면 감사 로그에 남길 주체가 없고 주체 없는 환자 조회는 남기면 안 되는 일이다. 그것이 유일한 조건이다. 병원·부서 인자는 받아 두고 쓰지 않는다. 병원별 규칙이 생기면 호출부를 고치지 않고 여기서 받을 수 있어야 한다. 안전장치는 막는 것이 아니라 감사 로그다. 그래서 테스트도 무게를 옮겼다 — 클래스 주석의 "감사와 권한이 유일한 장치"를 "감사 로그가 유일한 장치"로 고쳤고, 감사 한 줄의 모양(주체가 있는가·이름이 새지 않는가)이 가장 중요한 판정이 된다. ## 대조군 '수정_권한은_그대로_좁게_남는다' 를 새로 넣었다. 미리보기를 연 것이 수정까지 연 것이 되면 안 되고, 이 대조가 없으면 위 완화가 조용히 ModifyPolicy 까지 번져도 알 수 없다. 게이트: dotnet test 313/313 (미리보기 판정 1건 → 2건으로 갈라짐) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
88dab2edcf
commit
f50a7cff4b
@@ -14,28 +14,36 @@ namespace SheetMe.Core.Catalog;
|
|||||||
/// (전체 검색 + 감사 로그), 그래서 <b>남기는 쪽</b>이 안전장치의 본체다 —
|
/// (전체 검색 + 감사 로그), 그래서 <b>남기는 쪽</b>이 안전장치의 본체다 —
|
||||||
/// 조회 자체를 막지 않는 대신 흔적을 남긴다.
|
/// 조회 자체를 막지 않는 대신 흔적을 남긴다.
|
||||||
///
|
///
|
||||||
/// 조건은 <see cref="ModifyPolicy"/> 와 같은 축을 쓴다. 환자 기록을 보는 일은
|
/// <b>부서 문(전산실)을 걸지 않는다.</b> 처음에는 <see cref="ModifyPolicy"/> 를 그대로 갖다 썼는데
|
||||||
/// 지금 쓰이는 서식을 제자리에서 고치는 일과 무게가 다르지 않다.
|
/// 그건 레거시에 없는 제약이었다. 레거시가 부서로 가리는 툴은 <c>TK_MODIFY</c> 하나뿐이고
|
||||||
|
/// (<c>frmSheetDesigner.vb:1030-1060</c>, MSYS 전용인 <c>TK_ExportXML/ImportXML</c> 은 계정 문),
|
||||||
|
/// <c>TK_PREVIEW</c> 에는 어떤 가시성 조건도 걸리지 않는다 — 서식생성기를 열 수 있으면 쓸 수 있다.
|
||||||
|
///
|
||||||
|
/// 두 일의 무게가 다르기도 하다. 수정은 <b>지금 쓰이는 서식을 제자리에서 바꿔</b> 병원 전체에
|
||||||
|
/// 즉시 영향을 준다. 미리보기는 이미 그 사람이 볼 수 있는 기록을 <b>읽기만</b> 한다.
|
||||||
|
/// 여기에 부서 문을 걸면 서식을 만드는 사람이 자기 서식을 검증할 수 없게 되고,
|
||||||
|
/// 검증을 못 하면 값이 틀린 서식이 그대로 운영에 올라간다 — 막아서 더 위험해지는 쪽이다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class PatientPreviewPolicy
|
public static class PatientPreviewPolicy
|
||||||
{
|
{
|
||||||
#region Methods
|
#region Methods
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 이 사용자가 환자 데이터로 미리보기를 볼 수 있는가.
|
/// 이 사용자가 환자 데이터로 미리보기를 볼 수 있는가 — <b>인증만</b> 요구한다.
|
||||||
///
|
///
|
||||||
/// 인증된 HIS 사용자여야 한다 — 미인증이면 감사 로그에 남길 주체가 없고,
|
/// 미인증이면 감사 로그에 남길 주체가 없고, 주체 없는 환자 조회는 남기면 안 되는 일이다.
|
||||||
/// 주체 없는 환자 조회는 그 자체로 남기면 안 되는 일이다.
|
/// 그것이 유일한 조건이다. 병원·부서 인자는 <b>일부러 받아 두고 쓰지 않는다</b> —
|
||||||
|
/// 병원별 규칙이 생기면 호출부를 고치지 않고 여기서 받을 수 있어야 하고,
|
||||||
|
/// 인자가 없으면 그때 호출부 전부를 다시 손대야 한다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool CanPreviewPatient(string hospitalCode, string departmentCode, string uidCod,
|
public static bool CanPreviewPatient(string hospitalCode, string departmentCode, string uidCod,
|
||||||
string hospitalStartDate)
|
string hospitalStartDate)
|
||||||
=> uidCod.Length > 0
|
=> uidCod.Length > 0;
|
||||||
&& ModifyPolicy.CanModify(hospitalCode, departmentCode, uidCod, hospitalStartDate);
|
|
||||||
|
|
||||||
/// <summary>왜 막혔는지 — "권한이 없습니다"만으로는 누구에게 물어야 할지 모른다</summary>
|
/// <summary>왜 막혔는지 — "권한이 없습니다"만으로는 누구에게 물어야 할지 모른다</summary>
|
||||||
public static string DenyReason(string hospitalCode, string uidCod, string hospitalStartDate)
|
public static string DenyReason(string hospitalCode, string uidCod, string hospitalStartDate)
|
||||||
=> uidCod.Length == 0
|
=> uidCod.Length == 0
|
||||||
? "HIS 사용자가 확인되지 않았습니다. 기록지정보 화면에서 실행하거나 사용자 코드를 전달해 주세요."
|
? "HIS 사용자가 확인되지 않았습니다. 기록지정보 화면에서 실행하거나 사용자 코드를 전달해 주세요."
|
||||||
: ModifyPolicy.DenyReason(hospitalCode, uidCod, hospitalStartDate);
|
: string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 감사 로그 한 줄 — 누가·언제·어느 내원을 열었는가.
|
/// 감사 로그 한 줄 — 누가·언제·어느 내원을 열었는가.
|
||||||
|
|||||||
@@ -132,11 +132,12 @@ internal sealed class FormDesignDataBusiness
|
|||||||
UserSession.Current.HspCod, UserSession.Current.UidCod, HospitalStartDate);
|
UserSession.Current.HspCod, UserSession.Current.UidCod, HospitalStartDate);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 이 사용자가 <b>실제 환자</b>로 미리보기를 할 수 있는가.
|
/// 이 사용자가 <b>실제 환자</b>로 미리보기를 할 수 있는가 — 인증만 확인한다.
|
||||||
///
|
///
|
||||||
/// 수정과 같은 문을 쓴다 — 서식생성기에서 환자 정보를 꺼내 보는 것은 진료 행위가 아니라
|
/// 수정과 <b>다른</b> 문이다. 레거시가 부서로 가리는 툴은 TK_MODIFY 하나뿐이고
|
||||||
/// 서식이 맞게 뽑히는지 확인하는 일이고, 그 권한은 레거시가 전산실에만 열어 둔 것과 같은 범위다.
|
/// TK_PREVIEW 에는 조건이 없다(frmSheetDesigner.vb:1030-1060).
|
||||||
/// 여기를 넓히면 서식생성기가 <b>환자 조회 도구</b>가 된다.
|
/// 여기에 부서 문을 걸면 서식을 만든 사람이 자기 서식을 검증할 수 없다.
|
||||||
|
/// 안전장치는 막는 것이 아니라 <b>감사 로그</b>다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool CanPreviewPatient() => PatientPreviewPolicy.CanPreviewPatient(
|
public static bool CanPreviewPatient() => PatientPreviewPolicy.CanPreviewPatient(
|
||||||
UserSession.Current.HspCod, UserSession.Current.DepCod, UserSession.Current.UidCod,
|
UserSession.Current.HspCod, UserSession.Current.DepCod, UserSession.Current.UidCod,
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ namespace SheetMe.Core.Tests;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 환자 미리보기 접근 규칙 고정.
|
/// 환자 미리보기 접근 규칙 고정.
|
||||||
///
|
///
|
||||||
/// 이 표가 느슨해지면 서식을 만드는 사람 누구나 <b>운영 환자 기록</b>을 열 수 있다.
|
/// 레거시는 인가코드가 소스에 평문이고, 검색 범위 무제한이고, 감사 0건이었다.
|
||||||
/// 레거시가 그 상태였다 — 인가코드가 소스에 평문이고, 검색 범위 무제한이고, 감사 0건이다.
|
/// 검색 범위와 접근 범위는 레거시와 같게 두기로 했으므로 <b>감사 로그가 유일한 장치</b>다 —
|
||||||
/// 검색 범위는 레거시와 같게 두기로 했으므로 <b>감사와 권한</b>이 유일한 장치다.
|
/// 그래서 감사 한 줄의 모양(주체가 있는가, 이름이 새지 않는가)이 여기서 가장 중요한 판정이다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class PatientPreviewPolicyTests
|
public sealed class PatientPreviewPolicyTests
|
||||||
@@ -23,13 +23,30 @@ public sealed class PatientPreviewPolicyTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void 수정_권한과_같은_축을_쓴다()
|
public void 부서로_막지_않는다()
|
||||||
{
|
{
|
||||||
// 환자 기록을 보는 일은 지금 쓰이는 서식을 제자리에서 고치는 일과 무게가 다르지 않다.
|
// 처음에는 수정 권한(ModifyPolicy)을 그대로 갖다 썼다. 그건 레거시에 없는 제약이었다 —
|
||||||
Assert.IsTrue(PatientPreviewPolicy.CanPreviewPatient("ABC", "EDPS", "011825", string.Empty));
|
// 레거시가 부서로 가리는 툴은 TK_MODIFY 하나뿐이고 TK_PREVIEW 에는 문이 없다
|
||||||
Assert.IsFalse(PatientPreviewPolicy.CanPreviewPatient("ABC", "NURS", "011825", string.Empty));
|
// (frmSheetDesigner.vb:1030-1060). 서식을 만드는 사람이 자기 서식을 검증할 수 없으면
|
||||||
Assert.IsTrue(PatientPreviewPolicy.CanPreviewPatient("HANKOOK", "SALO", "011825", string.Empty));
|
// 값이 틀린 서식이 그대로 운영에 올라간다.
|
||||||
Assert.IsFalse(PatientPreviewPolicy.CanPreviewPatient("XYZ", "EDPS", "011825", "20250501"));
|
foreach (var dep in new[] { "EDPS", "NURS", "SALO", "MEDI", "" })
|
||||||
|
{
|
||||||
|
Assert.IsTrue(PatientPreviewPolicy.CanPreviewPatient("ABC", dep, "011825", string.Empty),
|
||||||
|
$"부서 '{dep}' 로 막혔다");
|
||||||
|
}
|
||||||
|
// 수정에만 걸리던 갈래도 미리보기에는 걸리지 않는다
|
||||||
|
Assert.IsTrue(PatientPreviewPolicy.CanPreviewPatient("XYZ", "NURS", "011825", "20250501"));
|
||||||
|
Assert.IsTrue(PatientPreviewPolicy.CanPreviewPatient("HANKOOK", "NURS", "011825", string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void 수정_권한은_그대로_좁게_남는다()
|
||||||
|
{
|
||||||
|
// 미리보기를 연 것이 수정까지 연 것이 되면 안 된다 — 수정은 지금 쓰이는 서식을
|
||||||
|
// 제자리에서 바꿔 병원 전체에 즉시 영향을 준다. 이 대조가 없으면 위 완화가
|
||||||
|
// 조용히 ModifyPolicy 까지 번져도 알 수 없다.
|
||||||
|
Assert.IsFalse(ModifyPolicy.CanModify("ABC", "NURS", "011825", string.Empty));
|
||||||
|
Assert.IsTrue(ModifyPolicy.CanModify("ABC", "EDPS", "011825", string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|||||||
Reference in New Issue
Block a user