WordPress Eklentisinde Kullanıcı Rolü ve Yetkilendirme Yönetimi
WordPress eklenti geliştirirken en çok ihmal edilen konulardan biri güvenlik tarafı, özellikle de kullanıcı rolü ve yetkilendirme yönetimi. “Eklenti çalışıyor, neden uğraşayım?” diye düşünmek oldukça yaygın ama bu yaklaşım sizi ciddi güvenlik açıklarına davet ediyor. Yanlış yapılandırılmış bir yetki kontrolü, abonelerin admin paneline erişmesine ya da yetkisiz kullanıcıların kritik verileri silmesine yol açabilir. Bu yazıda WordPress’in rol ve yetkilendirme sistemini derinlemesine inceleyeceğiz, gerçek dünya senaryolarıyla nasıl doğru implement edeceğinizi adım adım göstereceğiz.
WordPress Rol ve Yetenek Sistemi Nasıl Çalışır?
WordPress, kullanıcı yönetimini Roles and Capabilities (Roller ve Yetenekler) adı verilen bir sistem üzerine inşa etmiş. Her kullanıcıya bir rol atanır, her rol da belirli yetenekler (capabilities) taşır. Yetenekler, kullanıcının ne yapıp ne yapamayacağını belirleyen boolean değerlerdir.
Varsayılan roller şunlardır:
- Administrator: Tüm yeteneklere sahip, siteyi tam kontrol eder
- Editor: İçerik yönetimi konusunda geniş yetkiye sahip, diğer kullanıcıların yazılarını da düzenleyebilir
- Author: Kendi yazılarını yayınlayabilir ve yönetebilir
- Contributor: Yazı yazabilir ama yayınlayamaz, moderasyon gerektirir
- Subscriber: Sadece profil bilgilerini düzenleyebilir, içerik oluşturamaz
Bu yapı wp_user_roles option’ında saklanır ve WP_Roles sınıfı tarafından yönetilir. Her kullanıcı nesnesinin allcaps property’si, o kullanıcının sahip olduğu tüm yetenekleri içerir.
Özel Rol Oluşturma ve Yönetme
Gerçek dünya senaryosu düşünelim: Bir üyelik eklentisi geliştiriyorsunuz ve “Premium Üye” ile “Moderatör” gibi özel rollere ihtiyacınız var. Varsayılan roller yetmez, özel roller oluşturmanız gerekir.
<?php
// Eklenti aktive edildiğinde rolleri oluştur
function my_plugin_create_custom_roles() {
// Premium Üye rolü
add_role(
'premium_member',
__( 'Premium Üye', 'my-plugin' ),
array(
'read' => true,
'view_premium_content' => true,
'download_files' => true,
'submit_reviews' => true,
'edit_posts' => false,
'delete_posts' => false,
)
);
// Moderatör rolü
add_role(
'content_moderator',
__( 'İçerik Moderatörü', 'my-plugin' ),
array(
'read' => true,
'edit_posts' => true,
'edit_others_posts' => true,
'delete_posts' => true,
'moderate_comments' => true,
'view_premium_content' => true,
'manage_reports' => true,
)
);
}
register_activation_hook( __FILE__, 'my_plugin_create_custom_roles' );
// Eklenti devre dışı bırakıldığında rolleri temizle
function my_plugin_remove_custom_roles() {
remove_role( 'premium_member' );
remove_role( 'content_moderator' );
}
register_deactivation_hook( __FILE__, 'my_plugin_remove_custom_roles' );
Dikkat etmeniz gereken kritik bir nokta var: add_role() fonksiyonu, rol zaten varsa null döner ve üzerine yazmaz. Bu nedenle güncelleme senaryolarında önce rolün var olup olmadığını kontrol etmek gerekir.
Mevcut Rollere Yetenek Ekleme ve Çıkarma
Bazen var olan rollerin yeteneklerini genişletmeniz ya da kısıtlamanız gerekir. Örneğin WooCommerce kullanıyorsunuz ve Editor rolüne sipariş yönetimi yetkisi vermek istiyorsunuz.
<?php
function my_plugin_modify_editor_capabilities() {
$editor_role = get_role( 'editor' );
if ( ! $editor_role ) {
return;
}
// WooCommerce sipariş yetenekleri ekle
$woo_caps = array(
'view_woocommerce_reports',
'manage_woocommerce',
'edit_shop_orders',
'edit_others_shop_orders',
'publish_shop_orders',
'read_private_shop_orders',
'delete_shop_orders',
'delete_other_shop_orders',
);
foreach ( $woo_caps as $cap ) {
$editor_role->add_cap( $cap );
}
}
// Bu fonksiyonu sadece bir kere çalıştırmak için
// activation hook içinde veya update kontrolüyle çağırın
add_action( 'init', function() {
$version = get_option( 'my_plugin_caps_version', '0' );
if ( version_compare( $version, '1.2', '<' ) ) {
my_plugin_modify_editor_capabilities();
update_option( 'my_plugin_caps_version', '1.2' );
}
});
Önemli bir uyarı: Rol yetenekleri veritabanında wp_options tablosunda saklanır. Bu değişiklikler kalıcıdır. Her init çağrısında add_cap() yapmak gereksiz veritabanı sorgusu oluşturur, o yüzden versiyon kontrolü gibi bir mekanizma kullanın.
current_user_can() ile Yetki Kontrolü
Yetkilendirmenin kalbi current_user_can() fonksiyonudur. Bu fonksiyonu her kritik işlem öncesinde kullanmak zorundasınız. Asla “bu zaten admin panelinde, dışarıdan erişilemez” diye düşünmeyin.
<?php
// YANLIŞ yaklaşım - Hiçbir zaman böyle yapmayın
function my_plugin_delete_item() {
$item_id = intval( $_POST['item_id'] );
// Direkt silme işlemi - TEHLİKELİ!
my_plugin_db_delete( $item_id );
wp_die( 'Silindi' );
}
// DOĞRU yaklaşım
function my_plugin_delete_item_secure() {
// Nonce kontrolü her şeyden önce gelir
if ( ! isset( $_POST['_wpnonce'] ) ||
! wp_verify_nonce( $_POST['_wpnonce'], 'my_plugin_delete_item' ) ) {
wp_die( __( 'Güvenlik doğrulaması başarısız.', 'my-plugin' ), 403 );
}
// Yetki kontrolü
if ( ! current_user_can( 'delete_premium_items' ) ) {
wp_die( __( 'Bu işlem için yetkiniz bulunmuyor.', 'my-plugin' ), 403 );
}
// Giriş doğrulama
$item_id = isset( $_POST['item_id'] ) ? absint( $_POST['item_id'] ) : 0;
if ( ! $item_id ) {
wp_die( __( 'Geçersiz öğe ID.', 'my-plugin' ), 400 );
}
// Ek kontrol: Kullanıcı sadece kendi öğelerini silebilir mi?
if ( ! current_user_can( 'delete_others_premium_items' ) ) {
$item_owner = my_plugin_get_item_owner( $item_id );
if ( $item_owner !== get_current_user_id() ) {
wp_die( __( 'Bu öğeyi silme yetkiniz yok.', 'my-plugin' ), 403 );
}
}
my_plugin_db_delete( $item_id );
wp_send_json_success( array( 'message' => 'Öğe silindi.' ) );
}
add_action( 'wp_ajax_my_plugin_delete_item', 'my_plugin_delete_item_secure' );
Admin Menü ve Sayfa Erişim Kontrolü
Admin panelinde menü öğeleri oluştururken capability parametresini doğru kullanmak şart. Hem görünürlüğü hem de direkt URL erişimini kontrol etmeniz gerekiyor.
<?php
function my_plugin_admin_menu() {
// Ana menü - sadece eklentiyi yönetme yetkisi olanlar görür
add_menu_page(
__( 'Plugin Yönetimi', 'my-plugin' ),
__( 'Plugin', 'my-plugin' ),
'manage_options', // Bu yeteneğe sahip kullanıcılar görebilir
'my-plugin-main',
'my_plugin_main_page',
'dashicons-admin-plugins',
30
);
// Raporlar alt menüsü - daha geniş erişim
add_submenu_page(
'my-plugin-main',
__( 'Raporlar', 'my-plugin' ),
__( 'Raporlar', 'my-plugin' ),
'view_my_plugin_reports', // Özel yetenek
'my-plugin-reports',
'my_plugin_reports_page'
);
// Kullanıcı raporları - aboneler de görebilsin
add_submenu_page(
'my-plugin-main',
__( 'Kendi Raporlarım', 'my-plugin' ),
__( 'Raporlarım', 'my-plugin' ),
'read', // Tüm kayıtlı kullanıcılar
'my-plugin-user-reports',
'my_plugin_user_reports_page'
);
}
add_action( 'admin_menu', 'my_plugin_admin_menu' );
// Sayfa render fonksiyonunda tekrar kontrol et
function my_plugin_reports_page() {
if ( ! current_user_can( 'view_my_plugin_reports' ) ) {
wp_die( __( 'Yetki hatası.', 'my-plugin' ) );
}
// Sayfa içeriği...
echo '<div class="wrap">';
echo '<h1>' . esc_html__( 'Raporlar', 'my-plugin' ) . '</h1>';
// Rapor içeriği
echo '</div>';
}
REST API Endpoint’lerinde Yetkilendirme
Modern WordPress eklentileri genellikle REST API kullanır ve bu noktada yetkilendirme çok daha kritik hale gelir. permission_callback parametresi asla boş bırakılmamalı veya __return_true gibi değerler verilmemeli.
<?php
function my_plugin_register_rest_routes() {
// Genel okuma endpoint'i - giriş yapmış kullanıcılar
register_rest_route( 'my-plugin/v1', '/items', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'my_plugin_get_items',
'permission_callback' => function() {
return is_user_logged_in();
},
'args' => array(
'per_page' => array(
'default' => 10,
'sanitize_callback' => 'absint',
'validate_callback' => function( $param ) {
return $param > 0 && $param <= 100;
},
),
),
));
// Yazma endpoint'i - özel yetenek gerekli
register_rest_route( 'my-plugin/v1', '/items', array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => 'my_plugin_create_item',
'permission_callback' => function() {
return current_user_can( 'create_premium_items' );
},
));
// Admin endpoint'i - tam yönetim
register_rest_route( 'my-plugin/v1', '/items/(?P<id>[d]+)', array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => 'my_plugin_delete_item_rest',
'permission_callback' => function( $request ) {
$item_id = $request->get_param( 'id' );
// Admin her şeyi silebilir
if ( current_user_can( 'manage_options' ) ) {
return true;
}
// Sahip sadece kendi öğesini silebilir
if ( current_user_can( 'delete_premium_items' ) ) {
$owner = my_plugin_get_item_owner( $item_id );
return $owner === get_current_user_id();
}
return false;
},
'args' => array(
'id' => array(
'validate_callback' => function( $param ) {
return is_numeric( $param );
},
),
),
));
}
add_action( 'rest_api_init', 'my_plugin_register_rest_routes' );
map_meta_cap ile Dinamik Yetenek Kontrolü
Bazen yeteneklerin nesne tabanlı kontrol gerektirdiği durumlar olur. Örneğin “sadece kendi içeriğini düzenleyebilir” gibi bir senaryo. İşte burada map_meta_cap filtresi devreye girer.
<?php
// Premium içerik için meta capability mapping
function my_plugin_map_meta_cap( $caps, $cap, $user_id, $args ) {
// Bizimle ilgili olmayan yetki kontrollerini atla
if ( ! in_array( $cap, array( 'edit_premium_item', 'delete_premium_item', 'read_premium_item' ), true ) ) {
return $caps;
}
$item_id = isset( $args[0] ) ? absint( $args[0] ) : 0;
if ( ! $item_id ) {
$caps[] = 'do_not_allow';
return $caps;
}
$item = my_plugin_get_item( $item_id );
if ( ! $item ) {
$caps[] = 'do_not_allow';
return $caps;
}
switch ( $cap ) {
case 'edit_premium_item':
if ( $user_id === (int) $item->author_id ) {
// Kendi öğesini düzenlemek için temel yetenek yeterli
$caps = array( 'edit_premium_items' );
} else {
// Başkasının öğesini düzenlemek için üst yetenek gerekli
$caps = array( 'edit_others_premium_items' );
}
break;
case 'delete_premium_item':
if ( $user_id === (int) $item->author_id ) {
$caps = array( 'delete_premium_items' );
} else {
$caps = array( 'delete_others_premium_items' );
}
break;
case 'read_premium_item':
// Yayınlanmamış öğeler sadece sahibi tarafından okunabilir
if ( 'published' !== $item->status && $user_id !== (int) $item->author_id ) {
$caps = array( 'read_private_premium_items' );
} else {
$caps = array( 'read' );
}
break;
}
return $caps;
}
add_filter( 'map_meta_cap', 'my_plugin_map_meta_cap', 10, 4 );
Bu yapıyı kullandıktan sonra kontrolleri çok temiz bir şekilde yapabilirsiniz:
<?php
// Artık şöyle kontrol edebilirsiniz
if ( current_user_can( 'edit_premium_item', $item_id ) ) {
// Düzenleme formu göster
}
if ( current_user_can( 'delete_premium_item', $item_id ) ) {
// Silme butonu göster
}
Shortcode ve Frontend Yetkilendirmesi
Frontend tarafında da yetkilendirme kritik önem taşır. Özellikle shortcode’larla içerik gösterirken dikkatli olmalısınız.
<?php
function my_plugin_premium_content_shortcode( $atts ) {
$atts = shortcode_atts( array(
'id' => 0,
'fallback' => 'login', // 'login', 'message', 'hide'
), $atts, 'premium_content' );
$item_id = absint( $atts['id'] );
if ( ! $item_id ) {
return '';
}
// Giriş yapılmamışsa
if ( ! is_user_logged_in() ) {
if ( 'login' === $atts['fallback'] ) {
return sprintf(
'<div class="premium-login-prompt"><p>%s</p><a href="%s">%s</a></div>',
esc_html__( 'Bu içeriği görmek için giriş yapmalısınız.', 'my-plugin' ),
esc_url( wp_login_url( get_permalink() ) ),
esc_html__( 'Giriş Yap', 'my-plugin' )
);
}
if ( 'message' === $atts['fallback'] ) {
return '<div class="premium-restricted">' .
esc_html__( 'Bu içerik üyelere özeldir.', 'my-plugin' ) .
'</div>';
}
return ''; // 'hide' durumunda hiçbir şey gösterme
}
// Kullanıcının bu içeriğe erişim hakkı var mı?
if ( ! current_user_can( 'read_premium_item', $item_id ) ) {
return '<div class="premium-upgrade-prompt">' .
esc_html__( 'Bu içerik premium üyelere özeldir.', 'my-plugin' ) .
'</div>';
}
// İçeriği güvenli şekilde göster
$content = my_plugin_get_item_content( $item_id );
return '<div class="premium-content">' . wp_kses_post( $content ) . '</div>';
}
add_shortcode( 'premium_content', 'my_plugin_premium_content_shortcode' );
Sık Yapılan Hatalar ve Kaçınılması Gerekenler
Yetkilendirme konusunda en sık karşılaşılan hatalar şunlardır:
- Sadece UI gizlemek yetmez: Admin menüsünden bir seçeneği kaldırmak, o sayfaya direkt URL ile erişimi engellemez. Her sayfa render fonksiyonunda mutlaka
current_user_can()kontrolü yapın.
- Nonce olmadan form işlemi yapmak: Yetki kontrolü tek başına yeterli değil. CSRF saldırılarına karşı nonce kullanmak şart.
- Rol adına göre kontrol yapmak:
$user->roles[0] === 'administrator'gibi kontroller yerine her zaman yetenek bazlı kontrol kullanın. Roller değişebilir, yetenekler daha granüler kontrol sağlar.
- Capability’leri her request’te eklemek:
add_cap()veritabanına yazar. Herinitaction’ında çağırmak hem performansı öldürür hem de veritabanını gereksiz yere yorar.
__return_truekullanmak: REST API’de'permission_callback' => '__return_true'koymak tüm endpoint’i herkese açar. Test ortamında bile bu alışkanlığı edinmeyin.
- User meta’ya güvenmek:
get_user_meta()ile çektiğiniz değerlere doğrudan güvenerek yetki kararı vermeyin. Bu değerler manipüle edilebilir, her zaman WordPress’in kendi yetki sistemiyle doğrulayın.
Sonuç
WordPress’te kullanıcı rolü ve yetkilendirme yönetimi, eklenti geliştirmenin göz ardı edilemeyecek bir parçası. Doğru implement edildiğinde hem güvenli hem de esnek bir sistem elde edersiniz. Özetlemek gerekirse: her kritik işlem öncesinde current_user_can() kullanın, nonce kontrolünü asla atlamamayın, rol adına değil yeteneğe göre kontrol yapın ve özel roller oluştururken aktivasyon/deaktivasyon hook’larını doğru kullanın.
REST API tarafında permission_callback her zaman doldurulmalı, shortcode’larda hem giriş hem de yetenek kontrolü yapılmalı. map_meta_cap filtresi ile nesne bazlı dinamik yetki kontrolü kurduğunuzda ise gerçekten kurumsal kalitede bir yetkilendirme sisteminiz olur. Bu prensipleri bir kere özümsediğinizde, güvenli eklenti yazımı artık ekstra bir yük gibi hissettirmez, doğal bir alışkanlığa dönüşür.
