Ders 01 / 17
Girdi Doğrulama Kalıpları
Doğrulamanın kapsadığı nokta sayısının ölçülmesi: bölgesel ölçüm ağının beş giriş noktasındaki on beş alan noktasının her birine kendi bildirimini bozan bir sonda gönderilir, izin listesi temelli doğrulamanın, ret listesinin ve uç başına elle yazılmış denetimin kaç noktayı kapattığı sayılır ve envantere yeni bir uç ile yeni bir alan türü eklendiğinde kapsamanın nasıl değiştiği ölçülür.
İçindekiler
Güvenli Geliştirme Yaşam Döngüsü kursu tasarımda hangi tehdidin doğduğunu ve hangi karşı önlemin planlandığını saydı. O karşı önlemlerin ortak bir özelliği vardı: her biri bir karardı, hiçbiri bir yer değildi. Karar koda dönüştüğünde tek bir yerde durmaz; kod tabanındaki her giriş noktasında ayrı ayrı ya durur ya durmaz.
Bu ders o yerleri sayar. Ölçülen şey doğrulamanın nasıl yazıldığı değildir — Sunucu Güvenliği ve Üretime Alma kursu doğrulama işlevinin kendisini ölçtü. Burada ölçülen şey kapsamadır: kaç nokta var, savunma kaçını örtüyor, kaçı dışarıda kalıyor. Kursun ölçü birimi son sayıdır.
Nokta Envanteri
Bölgesel ölçüm ağı kurgudur: abone portalı, sayaç okuma alım servisi, saha uygulaması ve rapor üretici. Envanterin birimi uç değil nokta: bir ucun bir alanı. Bir uçta üç alan varsa o uç üç nokta taşır, çünkü savunma alan başına ya vardır ya yoktur.
GC1 (varsayım): envanter eksiksizdir. Bütün sayılar bu envantere görelidir ve envanterde olmayan bir giriş noktası hakkında hiçbir şey söylemez.
// noktalar.mjs — bolgesel olcum aginin giris noktalari (kurgu model) // Nokta = bir ucun bir alani. Bildirim her noktanin kisitini tek yerde tutar. export const BILDIRIM = { "POST /abone": { aboneNo: ["kod", /^A-\d{6}$/], ad: ["metin", 60], tarife: ["kume", ["mesken", "ticari", "tarim"]] }, "GET /fatura": { aboneNo: ["kod", /^A-\d{6}$/], donem: ["kod", /^\d{4}-\d{2}$/], sayfa: ["sayi", [1, 200]] }, "POST /okuma": { sayacNo: ["kod", /^S-\d{7}$/], deger: ["sayi", [0, 999999]], kaynak: ["kume", ["saha", "uzaktan"]] }, "POST /is-emri": { aboneNo: ["kod", /^A-\d{6}$/], tur: ["kume", ["kesme", "acma", "olcum"]], aciklama: ["metin", 200] }, "GET /rapor": { donem: ["kod", /^\d{4}-\d{2}$/], bolge: ["kume", ["kuzey", "guney"]], bicim: ["kume", ["ozet", "ayrintili"]] }, }; export const noktalar = (b = BILDIRIM) => Object.entries(b).flatMap(([uc, a]) => Object.keys(a).map((ad) => [uc, ad])); const GECERLI = { kod: (k) => (k.test("A-100432") ? "A-100432" : k.test("S-2200145") ? "S-2200145" : "2026-03"), metin: () => "olcum notu", kume: (k) => k[0], sayi: ([a]) => a, liste: (k) => [k[0]] }; export const gecerliKayit = (uc, b = BILDIRIM) => Object.fromEntries(Object.entries(b[uc]).map(([ad, [t, k]]) => [ad, GECERLI[t](k)])); // Sonda: yalniz tek noktanin bildirimini bozar, ayni kayittaki diger noktalar gecerli kalir. const IHLAL = { kod: () => "A-1", metin: (k) => "x".repeat(k + 1), kume: () => "bilinmeyen", sayi: ([, u]) => u + 1, liste: () => ["bilinmeyen"] }; export const sonda = (uc, alan, b = BILDIRIM) => ({ ...gecerliKayit(uc, b), [alan]: IHLAL[b[uc][alan][0]](b[uc][alan][1]) });
Sonda kavramı ölçünün tamamını taşır. Bir noktanın kapsanıp kapsanmadığını sormanın tek yolu, o noktanın kendi bildirimini bozan bir değer göndermek ve kaydın reddedilip reddedilmediğine bakmaktır. Sondadaki değerler zararsızdır: kısa bir tanımlayıcı, bir karakter fazla metin, kümede olmayan bir sözcük, üst sınırın bir üstündeki sayı.
GC2 (varsayım): bir nokta, sondası reddedildiğinde kapsanmış sayılır. Sonda yalnız tek noktayı bozduğu için ret kararı o noktaya yazılabilir.
Üç Savunma Biçimi
Aynı envanterin üzerine üç biçim konur. İzin listesi temelli doğrulama kararını bildirimden türetir: bildirimi olmayan uç da, bildirimde geçmeyen alan da reddedilir. Ret listesi temelli doğrulama adı konmuş biçimleri reddeder ve ucun bildirimini hiç okumaz. Üçüncüsü dağınık savunmadır: uç başına elle yazılmış denetim.
// dogrulayici.mjs — ayni nokta kumesine bakan uc savunma bicimi import { BILDIRIM } from "./noktalar.mjs"; // Tur cizelgesi: izin listesinin tanidigi kisit turleri. Yeni tur buraya eklenir. export const TUR = { kod: (v, s) => typeof v === "string" && s.test(v), metin: (v, s) => typeof v === "string" && v.length <= s, kume: (v, s) => s.includes(v), sayi: (v, s) => Number.isInteger(v) && v >= s[0] && v <= s[1], }; // 1) Izin listesi: karar bildirimden turer. Bildirimsiz uc, bildirimsiz alan ve // cizelgede olmayan tur -- ucu de rettir. export function izinListesi(uc, kayit, b = BILDIRIM, tur = TUR) { const alanlar = b[uc]; if (!alanlar) return "ret"; for (const [ad, v] of Object.entries(kayit)) { const k = alanlar[ad]; if (!k || !tur[k[0]] || !tur[k[0]](v, k[1])) return "ret"; } return Object.keys(alanlar).every((a) => a in kayit) ? "kabul" : "ret"; } // 2) Ret listesi: adi konmus bicimler reddedilir; ucun bildirimi hic okunmaz. const YASAK_ALAN = ["yetki", "rol", "tutar"], EN_COK = 200; export function retListesi(uc, kayit) { for (const [ad, v] of Object.entries(kayit)) { if (YASAK_ALAN.includes(ad)) return "ret"; const m = typeof v === "string" ? v : String(v ?? ""); if (m.length === 0 || m.length > EN_COK) return "ret"; } return "kabul"; } // 3) Dagink savunma: uc basina elle yazilmis denetim; uce yazilmis, ikisi bos kalmis. export function elleYazilmis(uc, kayit) { if (uc === "POST /abone") return /^A-\d{6}$/.test(kayit.aboneNo ?? "") ? "kabul" : "ret"; if (uc === "GET /fatura") return /^A-\d{6}$/.test(kayit.aboneNo ?? "") && Number.isInteger(kayit.sayfa) && kayit.sayfa >= 1 ? "kabul" : "ret"; if (uc === "POST /okuma") return /^S-\d{7}$/.test(kayit.sayacNo ?? "") ? "kabul" : "ret"; return "kabul"; }
Üç biçim de aynı on beş sondayla yoklanır. Dördüncü sütun bedeli tutar: geçerli bir kayıtla çağrıldığında hangi ucun durduğu.
// kapsama.mjs — her nokta, kendi bildirimini bozan bir sonda ile yoklanir import { BILDIRIM, noktalar, sonda, gecerliKayit } from "./noktalar.mjs"; import { izinListesi, retListesi, elleYazilmis } from "./dogrulayici.mjs"; const N = noktalar(), UC = Object.keys(BILDIRIM); console.log(`${UC.length} giris noktasi, ${N.length} alan noktasi, ${N.length} sonda\n`); console.log(`${"savunma bicimi".padEnd(24)}${"kapsanan".padStart(9)}${"disarida".padStart(9)}${"duran mesru uc".padStart(16)}`); for (const [ad, f] of [["izin listesi", izinListesi], ["ret listesi", retListesi], ["elle yazilmis denetim", elleYazilmis]]) { const disarida = N.filter(([uc, a]) => f(uc, sonda(uc, a)) === "kabul"); const duran = UC.filter((uc) => f(uc, gecerliKayit(uc)) !== "kabul").length; console.log(ad.padEnd(24) + String(N.length - disarida.length).padStart(9) + String(disarida.length).padStart(9) + String(duran).padStart(16)); console.log(" uc basina disarida: " + UC.map((uc) => `${uc.split(" ")[1]} ${disarida.filter(([u]) => u === uc).length}`).join(" ")); }
5 giris noktasi, 15 alan noktasi, 15 sonda savunma bicimi kapsanan disarida duran mesru uc izin listesi 15 0 0 uc basina disarida: /abone 0 /fatura 0 /okuma 0 /is-emri 0 /rapor 0 ret listesi 1 14 0 uc basina disarida: /abone 3 /fatura 3 /okuma 3 /is-emri 2 /rapor 3 elle yazilmis denetim 3 12 0 uc basina disarida: /abone 2 /fatura 2 /okuma 2 /is-emri 3 /rapor 3
Ret listesi on beş noktanın birini kapatıyor. Kapattığı nokta da tesadüfidir: iş emri açıklamasının üst sınırı iki yüz karakterdir ve listenin genel uzunluk kuralı da iki yüzdür, o yüzden sonda takılıyor. Kalan on dört noktada liste hiçbir şey bilmiyor — tanımlayıcı biçimini, küme üyeliğini ve sayı aralığını tanımıyor, çünkü bunlar noktaya özgüdür ve listede yazılı değildir. Ret listesinin bedeli sıfır çıktı; darlığı tam da kapsamasının düşük olma nedenidir.
Elle yazılmış denetim üç noktayı kapatıyor ve üçü de aynı sınıftan: abone ve sayaç tanımlayıcısı. Denetimi yazan kişi her uçta ilk alanı görmüş, ikinci ve üçüncü alanı görmemiş. İki uçta ise hiçbir denetim yok; oradaki altı nokta baştan dışarıda.
İzin listesinin sıfırı, doğrulamanın daha dikkatli yazılmış olmasından gelmiyor. Bildirim zaten noktanın kısıtını taşıyordu; doğrulama o bildirimi okuyor. Kapsama, savunmanın bildirimle aynı yerden türemesinin sonucudur.
Envanterin Dışı
On beşte on beş, GC1’e bağlı bir sayıdır: envanter eksiksiz sayılmıştı. Bu varsayım kaldırıldığında kapsama başka bir yerden sızar. Kodda kayıtlı uçların listesi yönlendirme tablosundadır; bildirimlerin listesi ayrıdır ve ikisi eşit olmak zorunda değildir.
GC3 (varsayım): yönlendirme tablosu koddan çıkarılmıştır ve kayıtlı bütün uçları içerir. Bildirim kümesi ise elle yazılmıştır.
// envanter-farki.mjs — kodda kayitli uclar ile bildirimi olan uclarin farki import { BILDIRIM } from "./noktalar.mjs"; import { izinListesi } from "./dogrulayici.mjs"; // Yonlendirme tablosu (model): kodda gercekten kayitli uclar ve isleyicinin okudugu alanlar. const ROTA = { "POST /abone": ["aboneNo", "ad", "tarife"], "GET /fatura": ["aboneNo", "donem", "sayfa"], "POST /okuma": ["sayacNo", "deger", "kaynak"], "POST /is-emri": ["aboneNo", "tur", "aciklama"], "GET /rapor": ["donem", "bolge", "bicim"], "POST /okuma/toplu": ["dosyaNo", "satir", "kaynak"], // bildirimi yok "GET /abone/gecmis": ["aboneNo", "baslangic", "bitis"], // bildirimi yok }; const bildirimsiz = Object.keys(ROTA).filter((uc) => !BILDIRIM[uc]); const nokta = (l) => l.reduce((t, uc) => t + ROTA[uc].length, 0); const SONDA = "x".repeat(5000); // hicbir alan bildiriminin kabul etmeyecegi uzunluk const kayit = (uc, bozuk) => Object.fromEntries(ROTA[uc].map((a) => [a, a === bozuk ? SONDA : "deger"])); console.log(`yonlendirme tablosu ${Object.keys(ROTA).length} uc / ${nokta(Object.keys(ROTA))} nokta, ` + `bildirim ${Object.keys(BILDIRIM).length} uc / ${nokta(Object.keys(BILDIRIM))} nokta`); console.log(`bildirimsiz uc: ${bildirimsiz.join(", ")} -> ${nokta(bildirimsiz)} nokta\n`); // Bildirimsiz uc icin iki ilke. const gecir = (uc, k) => (BILDIRIM[uc] ? izinListesi(uc, k) : "kabul"); console.log(`${"bildirimsiz uc ilkesi".padEnd(24)}${"disarida nokta".padStart(15)}${"duran mesru uc".padStart(16)}`); for (const [ad, f] of [["varsayilan ret", izinListesi], ["varsayilan gecis", gecir]]) { const dis = bildirimsiz.flatMap((uc) => ROTA[uc].map((a) => [uc, a])) .filter(([uc, a]) => f(uc, kayit(uc, a)) === "kabul").length; const duran = bildirimsiz.filter((uc) => f(uc, kayit(uc, null)) !== "kabul").length; console.log(ad.padEnd(24) + String(dis).padStart(15) + String(duran).padStart(16)); }
yonlendirme tablosu 7 uc / 21 nokta, bildirim 5 uc / 15 nokta bildirimsiz uc: POST /okuma/toplu, GET /abone/gecmis -> 6 nokta bildirimsiz uc ilkesi disarida nokta duran mesru uc varsayilan ret 0 2 varsayilan gecis 6 0
Yirmi bir noktanın altısının bildirimi yok. Merkezî kalıp bu altı noktayı kendi başına kapatamaz; kapatabildiği tek şey, bildirimi olmayan bir uca ne yapılacağıdır. Varsayılan ret altı noktayı da kapatır ve karşılığında iki ucu durdurur — biri toplu okuma yükleme ucu, biri abone geçmişi. Varsayılan geçiş hiçbir meşru işi durdurmaz ve altı noktayı elde bırakır.
İki satırın farkı sessizlikte. Üstteki ilkede eksik bildirim ilk çağrıda görülür; alttakinde hiçbir belirti yoktur. Kapsamanın kendisi ölçülebilir olduğu için de bu fark yazılabilir: iki listenin karşılaştırılması tek bir çıkarma işlemidir ve her değişiklikte tekrarlanabilir.
Yeni Nokta Eklendiğinde
Kapsamanın asıl sınavı envanter büyüdüğünde başlar. Sayaç değişim bildirimi için yeni bir uç açılıyor: önce yalnız bilinen türlerle üç alan, sonra dördüncü alan olarak yeni bir tür — birden çok değer taşıyan bir liste.
GC4 (varsayım): yeni ucun bildirimi elle yazılır ve doğru yazıldığı kabul edilir. Ölçülen şey bildirimin doğruluğu değil, bildirimin ardından savunmanın kendiliğinden davranışıdır.
// yeni-nokta.mjs — envantere yeni uc ve yeni alan turu eklenince kapsama ne oluyor import { BILDIRIM, noktalar, sonda, gecerliKayit } from "./noktalar.mjs"; import { izinListesi, retListesi, elleYazilmis, TUR } from "./dogrulayici.mjs"; const YENI = "POST /sayac/degisim"; const A = { ...BILDIRIM, [YENI]: { sayacNo: ["kod", /^S-\d{7}$/], yeniNo: ["kod", /^S-\d{7}$/], neden: ["kume", ["ariza", "sure", "talep"]] } }; // yalniz bilinen turler const B = { ...A, [YENI]: { ...A[YENI], ekipman: ["liste", ["muhur", "kapak"]] } }; // yeni tur // Turu taniyan tek merkezi degisiklik: tur cizelgesine bir satir. const TUR2 = { ...TUR, liste: (v, s) => Array.isArray(v) && v.every((x) => s.includes(x)) }; const izinV2 = (uc, kayit, b) => izinListesi(uc, kayit, b, TUR2); const olc = (b, f) => { const N = noktalar(b); const disarida = N.filter(([uc, a]) => f(uc, sonda(uc, a, b), b) === "kabul").length; const duran = Object.keys(b).filter((uc) => f(uc, gecerliKayit(uc, b), b) !== "kabul").length; return [N.length, disarida, duran]; }; console.log(`${"asama".padEnd(34)}${"nokta".padStart(6)}${"izin".padStart(6)}${"ret".padStart(5)}${"elle".padStart(6)}${"duran mesru uc".padStart(16)}`); for (const [ad, b, f] of [["mevcut envanter", BILDIRIM, izinListesi], ["yeni uc, bilinen turler", A, izinListesi], ["yeni alan turu eklendi", B, izinListesi], ["turu taniyan tek degisiklik", B, izinV2]]) { const [n, iz, du] = olc(b, f); console.log(ad.padEnd(34) + String(n).padStart(6) + String(iz).padStart(6) + String(olc(b, retListesi)[1]).padStart(5) + String(olc(b, elleYazilmis)[1]).padStart(6) + String(du).padStart(16)); } console.log(`\nyeni uc icin yazilan dogrulama kodu: izin listesi 0 satir (3 satir bildirim), ` + `elle yazilmis ${Object.keys(B[YENI]).length} denetim`);
asama nokta izin ret elle duran mesru uc mevcut envanter 15 0 14 12 0 yeni uc, bilinen turler 18 0 17 15 0 yeni alan turu eklendi 19 0 18 16 1 turu taniyan tek degisiklik 19 0 18 16 0 yeni uc icin yazilan dogrulama kodu: izin listesi 0 satir (3 satir bildirim), elle yazilmis 4 denetim
Orta iki sütun üç aşamada da artıyor: envanter on beşten on dokuza çıkarken ret listesinin dışarıda bıraktığı nokta on dörtten on sekize, elle yazılmış denetiminki on ikiden on altıya çıkıyor. Her yeni nokta, bu iki biçimde doğrudan bir açık adayıdır — çünkü savunma noktayı ancak biri onu elle yazarsa görür.
İzin listesi sütunu üç aşamada da sıfır. Yeni uç eklendiğinde tek satır doğrulama kodu yazılmadı; yalnız bildirim yazıldı ve doğrulama o bildirimi okudu. Kapsama sabit kaldı.
Üçüncü satır bu dersin tek dolu bedel hücresidir. Yeni tür eklendiğinde durum farklıdır: tür çizelgesinde karşılığı olmayan bildirim reddedilir, dolayısıyla o ucun geçerli kaydı da reddedilir. Kapsama on dokuzda kalır, ama bir uç durur. Bu, güvenli olan yöndeki hatadır ve gürültülüdür — meşru istek reddedildiği için hemen görülür. Dördüncü satır düzeltmeyi gösteriyor: tür çizelgesine bir satır, dokunulan başka yer yok.
Yeni nokta ile yeni tür arasındaki fark kalıcıdır. Nokta eklemek sık ve dağınık bir iştir; her uç, her alan, her sürüm. Tür eklemek seyrektir ve merkezî tek bir yerde olur. Kapsamayı sabit tutan kalıp, sık olan işi hiçbir koda bağlamayan kalıptır.
Özet
- Envanterin birimi uç değil noktadır: beş giriş noktası on beş alan noktası taşıyor. Her nokta, kendi bildirimini bozan bir sondayla yoklandı; sondaların hiçbiri zararlı değil, hepsi sınır değeri.
- Aynı on beş sondada izin listesi 15 noktayı kapattı, ret listesi 1, uç başına elle yazılmış denetim 3. Dışarıda kalan nokta sayısı sırasıyla 0, 14 ve 12.
- Ret listesinin kapattığı tek nokta tesadüfidir: genel uzunluk kuralı bir alanın üst sınırıyla çakıştı. Elle yazılmış denetimin kapattığı üç nokta da tek sınıftandır ve iki uçta hiçbir denetim yoktur.
- Yönlendirme tablosunda 21 nokta var, bildirimde 15; altı nokta envanterin dışında. Merkezî kalıp bunları ancak bildirimsiz uca uyguladığı ilkeyle kapatır: varsayılan ret 6 noktayı kapatıp 2 ucu durdurur, varsayılan geçiş 6 noktayı elde bırakır ve hiçbir belirti vermez.
- Envanter on beşten on dokuza çıktığında izin listesinin dışarıda bıraktığı nokta 0’da kaldı, ret listesininki 18’e, elle yazılmışınki 16’ya çıktı. Yeni uç için yazılan doğrulama kodu izin listesinde 0 satırdır.
- Yeni bir alan türü eklemek farklıdır: çizelgede karşılığı olmayan tür reddedilir, bir uç durur ve bu görülür. Düzeltme tür çizelgesinde tek satırdır. Nokta eklemek sıktır ve koda dokunmaz, tür eklemek seyrektir ve tek merkezde olur.
Sonraki Adım
Doğrulamayı geçen değer sözleşmeye uygundur; noktanın bildirimi ne diyorsa onu taşır. Bu değerin sistemden çıkarken ne olacağı ayrı bir sorudur. Abone adı bir sayfa gövdesine, bir öznitelik tırnağının içine, bir betik değişkenine ve bir adres bileşenine yazılıyor; dördü de aynı değeri alıyor, dördü de farklı bir dilin kuralına tabi. Sonraki ders çıkış noktalarını sayar ve tek soruyu ölçer: bağlam yanlış seçildiğinde kaç durum bozuluyor ve tek bir kodlayıcı kaç çıkış noktasını kendiliğinden kapatabiliyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.