Skip to content

Note Permissions dan Security

Ringkasan

Video ini membahas cara mengamankan fitur Notes. Kita menambahkan custom capability type pada Note CPT, mengatur izin per-role menggunakan Members plugin, memaksa semua note berstatus private melalui wp_insert_post_data filter, dan memasang sanitasi server-side untuk mencegah injeksi kode berbahaya.


1. Capability Type untuk Note CPT

Update Registrasi CPT

Di mu-plugins/university-post-types.php:

php
register_post_type('note', array(
  'capability_type' => 'note',        // Buat permission unik untuk note
  'map_meta_cap' => true,             // WordPress auto-mapping ke capabilities
  'show_in_rest' => true,
  'supports' => array('title', 'editor'),
  'public' => false,
  'show_ui' => true,
  'labels' => array(
    'name' => 'Notes',
    'add_new_item' => 'Add New Note',
    'edit_item' => 'Edit Note',
    'all_items' => 'All Notes',
    'singular_name' => 'Note'
  ),
  'menu_icon' => 'dashicons-welcome-write-blog'
));

Capabilities yang Dibuat Otomatis:

CapabilityFungsi
edit_noteMengedit satu note milik sendiri
edit_notesMengedit note-note milik sendiri
edit_others_notesMengedit note orang lain
publish_notesMempublikasikan note
delete_noteMenghapus satu note
delete_notesMenghapus note-note milik sendiri
read_private_notesMembaca note private orang lain

Atur Permission via Members Plugin:

Untuk role Subscriber, berikan HANYA:

  • edit_notes — bisa edit note sendiri
  • publish_notes — bisa buat note baru
  • delete_notes — bisa hapus note sendiri

JANGAN berikan:

  • edit_others_notes — tidak boleh edit note orang lain
  • delete_others_notes — tidak boleh hapus note orang lain
  • read_private_notes — tidak boleh baca note orang lain

Penting: Jangan lupa berikan semua note capabilities ke Administrator juga, karena menambahkan capability_type baru akan menghapus akses default admin.


2. Status Private: Paksa di Server-Side

Masalah:

Meskipun kita kirim status: 'private' dari JavaScript, attacker bisa memanipulasi request dan mengirim status: 'publish' — note mereka akan terlihat oleh semua orang.

Solusi: wp_insert_post_data Filter

php
// Di functions.php
add_filter('wp_insert_post_data', 'makeNotePrivate', 10, 2);

function makeNotePrivate($data, $postArray) {
  // Hanya berlaku untuk post type 'note'
  if ($data['post_type'] == 'note') {
    // Jangan paksa private saat di-trash (delete)
    if ($data['post_status'] != 'trash') {
      $data['post_status'] = 'private';
    }

    // Sanitize title dan content di server
    $data['post_title'] = sanitize_text_field($data['post_title']);
    $data['post_content'] = sanitize_textarea_field($data['post_content']);
  }

  return $data;
}

Penjelasan Parameter:

ParameterIsiContoh
$dataData yang akan disimpan ke database$data['post_status'], $data['post_title'], $data['post_content']
$postArrayData post lengkap termasuk ID$postArray['ID'] (0 jika post baru)

Flow:

Client → REST API → wp_insert_post_data filter → Database

          PAKSA post_status = 'private'
          + sanitize title & content

Cek post_status != 'trash':

Saat user menghapus note, WordPress set status ke 'trash'. Jika kita selalu paksa 'private', note tidak bisa dihapus! Maka hanya paksa 'private' jika status bukan 'trash'.


3. Sanitasi & Escape: Defense in Depth

Server-Side Sanitization (saat MENYIMPAN):

php
// Hapus tag HTML dan escape karakter berbahaya
sanitize_text_field($data['post_title']);      // Untuk single-line text
sanitize_textarea_field($data['post_content']); // Untuk multi-line text

Fungsi sanitize_text_field():

  • Menghapus semua tag HTML (<script>, <b>, dll.)
  • Menghapus line breaks
  • Strip octets (%00)
  • Cocok untuk judul, nama, single-line input

Fungsi sanitize_textarea_field():

  • Sama seperti sanitize_text_field() tapi mempertahankan line breaks
  • Cocok untuk konten/body text

Client-Side Escaping (saat MENAMPILKAN):

FungsiKonteksContoh
esc_html()Dalam tag HTML<h2><?php echo esc_html($title); ?></h2>
esc_attr()Dalam atribut HTML<input value="<?php echo esc_attr($title); ?>">
esc_textarea()Dalam <textarea><textarea><?php echo esc_textarea($content); ?></textarea>
esc_url()Dalam URL<a href="<?php echo esc_url($link); ?>">

Contoh Penerapan di Template:

php
<li data-id="<?php the_ID(); ?>">
  <input readonly class="note-title-field" value="<?php echo esc_attr(get_the_title()); ?>">
  <textarea readonly class="note-body-field"><?php echo esc_textarea(wp_strip_all_tags(get_the_content())); ?></textarea>
</li>

4. Capability unfiltered_html

  • Secara default, hanya Administrator dan Editor memiliki capability unfiltered_html
  • Subscriber tidak memiliki capability ini
  • Artinya: bahkan tanpa sanitize_text_field(), WordPress sudah memblokir tag HTML dari Subscriber
  • TAPI, kita tetap menambahkan sanitasi sebagai defense in depth — lapisan keamanan ekstra

5. Hapus Prefix "Private:" dari Judul

Masalah:

Karena semua note berstatus private, WordPress otomatis menambahkan "Private: " di depan judul. Ini terlihat jelek di front-end.

Solusi:

php
// Di functions.php
add_filter('the_title', 'removePrivatePrefix');

function removePrivatePrefix($text) {
  $text = str_replace('Private: ', '', $text);
  return $text;
}

6. Kurangi Subscriber Capabilities

Best Practice:

Berikan izin seminimal mungkin kepada setiap role.

Untuk Subscriber, pastikan hanya memiliki:

  • read — akses dasar
  • edit_notes — edit note sendiri
  • publish_notes — buat note baru
  • delete_notes — hapus note sendiri

Lepaskan yang tidak diperlukan:

  • read_private_notes — Subscriber hanya boleh baca note milik sendiri, bukan semua private notes

WordPress REST API sudah otomatis memfilter: user hanya bisa melihat, mengedit, dan menghapus private posts milik sendiri selama capability-nya benar.


Diagram Security Layers

Layer 1: CLIENT
  └── Kirim status: 'private' dari JavaScript (mudah dibypass)

Layer 2: SERVER - wp_insert_post_data
  └── PAKSA post_status = 'private'  (tidak bisa dibypass client)
  └── sanitize_text_field() + sanitize_textarea_field()

Layer 3: SERVER - WordPress Core
  └── Cek unfiltered_html capability
  └── Cek edit_notes / publish_notes capability

Layer 4: OUTPUT - Template
  └── esc_html() / esc_attr() / esc_textarea()
  └── wp_strip_all_tags()