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. Her init action’ında çağırmak hem performansı öldürür hem de veritabanını gereksiz yere yorar.
  • __return_true kullanmak: 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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir