Açık kaynak bir Laravel CRM uygulamasını modül bazında yeniden kullanılabilir bir SaaS çekirdeğine çıkarma serisinin bir sonraki kısmına hoş geldiniz. Şu ana kadar: temel katman, Fortify üzerinde kimlik doğrulama, çok kiracılık ve roller & izinler. Şimdi v0.5.0 aşamasındayız: aktivite kaydı.
<p>Bu modülün ne olduğu konusunda açık olmak istiyorum, çünkü abartma yapmak oldukça kolay.</p>
<h2>
<a name="i-did-not-invent-an-audit-log" href="#i-did-not-invent-an-audit-log">
</a>
Ben bir denetim kaydı icat etmedim
</h2>
<p>Kayıt işlemi <code>spatie/laravel-activitylog</code> üzerinde gerçekleşiyor. Harika bir paket ve kendi denetim kaydımı yazmak gereksiz olurdu. Bu yüzden bunu yapmadım.</p>
<p>Benim çıkardığım şey, spatie'nin kutudan çıkışta sunduğu şeylerin dışında kalanları, etrafına sarmalayarak ekleyip özelleştirdim:</p>
<ul>
<li>Her kayıt, yalnızca "model X değişti" demek yerine <strong>cihaz, IP, rota, HTTP yöntemi ve coğrafi konumu</strong> içerir.</li>
<li>Hepsini okuyabilen tek bir <strong>süper yönetici görüntüleyici</strong>.</li>
<li>Kendi olaylarını kayıt eden bir yapılandırma kaydı (kimlik doğrulama, kiracılık) ve ev sahibi kendi alan olaylarını aynı şekilde ekler.</li>
</ul>
<p>Değer, entegrasyonda yatıyor, depolama işlemlerinde değil. Bunu her yazımda belirtiyorum çünkü kamuya açık inşanın amacı, bitmemiş bir arabanın sadece motorunu gönderirken onu bitmiş gibi göstermemektir.</p>
<h2>
<a name="the-one-decision-worth-explaining-this-is-not-a-tenant-feature" href="#the-one-decision-worth-explaining-this-is-not-a-tenant-feature">
</a>
Açıklanmayı gerektiren bir karar: bu bir kiracı özelliği değil
</h2>
<p>CRM’imde, aktivite kaydı bir platform operatör aracıdır. O kaydı okuyan kişi <em>ben</em>, operatörüm, şirket sahibi değil. Bu nedenle kayıt küresel bir yapıdadır. <code>company_id</code> yok. Süper yönetici, her kiracı arasında her şeyi görebilir; şirkete ait bir sahip bunu asla göremez.</p>
<p>Bu küçük bir şey gibi görünebilir ama tüm modülü şekillendirdi. Yanlış yapılacak bir kiracı ayarı yok, her şirkete özgü bir filtrasyon yok, bir şirketin diğerinin kaydını okuma riski yok. Erişim kuralı tek bir soruya indirgeniyor: süper yönetici misin? Ve süper yönetici, kimlik doğrulama aşamasından bir kimlik işareti olarak tek bir sınıf aracılığıyla çözülüyor; kazara verilmeyen bir rol değil.</p>
<p>Ayrıca, bu modül sessizce daha büyük bir şeyin ilk dilimini sunuyor: bir platform operatörü konsolu. Aktivite kaydı bu konsolun ilk ekranı. Yönetici arayüzünü kasıtlı olarak küçük tuttum (bir başlık, bir "süper yönetici" işareti, bir alan) böylece bu haftayı konsolun tamamını inşa etmeye çalışmış gibi göstermiyorum.</p>
<h2>
<a name="the-context-wrapper" href="#the-context-wrapper">
</a>
Bağlam sarmalayıcı
</h2>
<p>Temel, her kayıtta istek bağlamını bir yerden kaydediyor ve cihazı, kimlik doğrulama aşamasındaki parmak izi sözleşmesine göre çözüyor (ikinci bir kullanıcı aracısı ayrıştırıcı değil) ve her şeyi saklamadan önce gizli bilgileri düzeltip düzenliyor.<br/></p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><span class="k">public</span> <span class="k">function</span> <span class="n">forRequest</span><span class="p">(</span><span class="kt">?Request</span> <span class="nv">$request</span> <span class="o">=</span> <span class="kc">null</span><span class="p">):</span> <span class="kt">array</span>{
$request ??= request();
$device = $this->deviceResolver->resolve($request);
<span class="k">return</span> <span class="p">[</span>
<span class="s1">'user_ip'</span> <span class="o">=></span> <span class="nv">$request</span><span class="o">-></span><span class="nf">ip</span><span class="p">(),</span>
<span class="s1">'user_device_type'</span> <span class="o">=></span> <span class="nv">$device</span><span class="o">-></span><span class="n">type</span><span class="p">,</span>
<span class="s1">'user_os'</span> <span class="o">=></span> <span class="nv">$device</span><span class="o">-></span><span class="n">os</span><span class="p">,</span>
<span class="s1">'user_browser'</span> <span class="o">=></span> <span class="nv">$device</span><span class="o">-></span><span class="n">browser</span><span class="p">,</span>
<span class="s1">'route_name'</span> <span class="o">=></span> <span class="nv">$request</span><span class="o">-></span><span class="nf">route</span><span class="p">()</span><span class="o">?-></span><span class="nf">getName</span><span class="p">(),</span>
<span class="s1">'request_method'</span> <span class="o">=></span> <span class="nv">$request</span><span class="o">-></span><span class="nf">getMethod</span><span class="p">(),</span>
<span class="s1">'full_url'</span> <span class="o">=></span> <span class="nv">$this</span><span class="o">-></span><span class="nf">redactUrl</span><span class="p">(</span><span class="nv">$request</span><span class="o">-></span><span class="nf">fullUrl</span><span class="p">()),</span>
<span class="c1">// ...</span>
<span class="p">];</span>}
<p>O <code>redactUrl</code> önemli. Bağışlayan, ham URL'yi saklıyordu; bu, bir şifre sıfırlama bağlantısı veya 2FA kodunun bir sorgu dizisinde geçmesi durumunda, denetim tablosuna düz metin olarak yazılmasına neden oluyordu. Paket, saklanan herhangi bir yapılandırılmış PII anahtarının değerini kaydetmeden önce maskelemektedir ve ayrıca (bir gözden geçirme notu, aşağıdaki incelemelerle ilgili daha fazla bilgi) geri kalan URL'yi de bozmadan yapıyor.</p>
<h2>
<a name="then-the-review-found-it-logging-the-wrong-thing" href="#then-the-review-found-it-logging-the-wrong-thing">
</a>
Ardından inceleme, yanlış şeyi kaydettiğini buldu
</h2>
<p>Bu serideki her modül, birleştirilmeden önce bir kod incelemesinden geçmektedir ve şu ana kadar her modül inceleme sırasında gerçek bir hata tespit etmiştir. Bu modülde hata oldukça gizliydi, çünkü kod açıkça doğru görünüyordu.</p>
<p>Bağışlayan, zaten bildiğim bir gerçeği hads etti: hem <code>causer_id</code> hem de <code>subject_id</code> için aynı kullanıcı kimliğini yazıyordu. "Bunu kim yaptı" ve "ne yapıldığı" aynı satıra kaydediliyordu. Bu, bir denetim kaydı için mantıksız, bu yüzden çıkardım: neden olan (causer) eylemi gerçekleştiren, konu (subject) ise eylemin nesnesi olarak ayrı ayrı çözüldü.</p>
<p>Resolver'ım, etkinlikten belirli bir özelliği kontrol ederek konuyu alıyordu:<br/></p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><span class="k">foreach</span> <span class="p">([</span><span class="s1">'subject'</span><span class="p">,</span> <span class="s1">'company'</span><span class="p">,</span> <span class="s1">'model'</span><span class="p">,</span> <span class="s1">'invitation'</span><span class="p">,</span> <span class="s1">'employee'</span><span class="p">]</span> <span class="k">as</span> <span class="nv">$property</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">isset</span><span class="p">(</span><span class="nv">$event</span><span class="o">-></span><span class="p">{</span><span class="nv">$property</span><span class="p">})</span> <span class="o">&&</span> <span class="nv">$event</span><span class="o">-></span><span class="p">{</span><span class="nv">$property</span><span class="p">}</span> <span class="k">instanceof</span> <span class="nc">Model</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nv">$event</span><span class="o">-></span><span class="p">{</span><span class="nv">$property</span><span class="p">};</span>
<span class="p">}</span>}


