Role i uprawnienia WordPress: add_role, add_cap i bezpieczeństwo

Role i uprawnienia WordPress: add_role, add_cap i bezpieczeństwo

Ostatnio zweryfikowano: 20 września 2026
9 min czytania
Przewodnik
Full-stack developer
Audytor bezpieczeństwa

WordPress ma wbudowaną listę kontroli dostępu, a prawie nikt z niej nie korzysta. Najczęstszy błąd uprawnień na stronach klientów to nie niezałatana wtyczka - to redaktor treści z rolą Administrator, bo ktoś raz potrzebował przestawić menu. Ten przewodnik pokazuje, co API ról i uprawnień robi na poziomie źródeł: gdzie leżą dane, dlaczego add_role() milcząco ignoruje drugie wywołanie, do czego naprawdę sprowadza się sprawdzenie nazwy roli i które capabilities są szersze, niż sugeruje nazwa.

Jedna zmiana robi większość roboty. Przestań nazywać role w warunkach i nazwij capability, której kod naprawdę potrzebuje. Reszta przewodnika to koszt tej decyzji w praktyce.

#1. Koncepcje: rola vs uprawnienie (capability)

Capability to pojedynczy ciąg uprawnienia: edit_posts, publish_pages, install_plugins. Rola to nazwany worek tych ciągów. WordPress przechowuje worek, a ewaluuje ciąg.

Domyślne worki są mniejsze, niż się zakłada. Według dokumentacji ról i uprawnień WordPress Editor ma 26 capabilities, a Author siedem: read, upload_files, edit_posts, edit_published_posts, publish_posts, delete_posts, delete_published_posts. Contributor ma trzy: read, edit_posts, delete_posts. Contributor nie może wgrać pliku - stąd ticket o bibliotece mediów po „dajcie mu Contributor”.

Editor niesie unfiltered_html na single-site (surowy <script> w treści). W multisite map_meta_cap() odmawia go wszystkim poza super adminami:

if ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML ) {
    $caps[] = 'do_not_allow';
} elseif ( is_multisite() && ! is_super_admin( $user_id ) ) {
    $caps[] = 'do_not_allow';
} else {
    $caps[] = 'unfiltered_html';
}

Czego Editor nie ma: edit_theme_options, list_users, edit_users, manage_options, niczego związanego z wtyczkami.

#Dlaczego sprawdzenie nazwy roli wydaje się działać

Sprawdzanie nazwy roli to nie błąd składni. Jest gorzej: zwraca poprawną odpowiedź w jedynym przypadku, który przetestowałeś.

// Źle
if ( current_user_can( 'administrator' ) ) { ... }

// Dobrze
if ( current_user_can( 'manage_options' ) ) { ... }

Pierwsza linia zwraca true dla administratora przez merge na końcu WP_User::get_role_caps():

$this->allcaps = array();
foreach ( (array) $this->roles as $role ) {
    $the_role      = $wp_roles->get_role( $role );
    $this->allcaps = array_merge( (array) $this->allcaps, (array) $the_role->capabilities );
}
$this->allcaps = array_merge( (array) $this->allcaps, (array) $this->caps );

$this->caps to surowa meta wp_capabilities, a jej kluczami są nazwy ról. Stąd administrator => true ląduje w allcaps obok prawdziwych uprawnień i przechodzi sprawdzenie przypadkiem. Dokumentacja current_user_can() mówi, że sprawdzanie ról zamiast capabilities jest częściowo wspierane, ale odradzane, bo daje niepewne wyniki.

Trzy scenariusze z żywych wdrożeń:

  1. Budujesz własną rolę z manage_options, żeby klient wszedł na stronę ustawień. current_user_can('administrator') jest false. Twoja własna strona admina zwraca 403 użytkownikowi, dla którego ją zrobiłeś.
  2. Capability nadane bezpośrednio na obiekcie użytkownika, bez roli, przechodzi każde sprawdzenie capability i pada przy każdym sprawdzeniu roli.
  3. W multisite WP_User::has_cap() dla super adminów kończy się wcześniej, zanim zajrzy do allcaps, i zwraca true dla wszystkiego poza do_not_allow. Sprawdzenie roli nic tam nie rozróżnia.

Sprawdzenia capability przeżywają wszystkie trzy, bo capability to to, czego kod naprawdę potrzebuje.

#2. Tworzenie własnej roli

Zanim napiszesz własną, sprawdź, czy platforma już ją dostarcza. WooCommerce przy instalacji dodaje shop_manager i customer. Według dokumentacji ról WooCommerce Shop Manager i tak zarządza produktami, zamówieniami, kuponami i kontami klientów. Równoległy „manager sklepu” dubluje rolę aktualizowaną w cudzym cyklu wydań.

Gdy naprawdę potrzebujesz własnej:

add_role(
    'editorial_lead',
    'Szef redakcji',
    [
        'read'                 => true,
        'upload_files'         => true,
        'edit_posts'           => true,
        'edit_others_posts'    => true,
        'edit_published_posts' => true,
        'publish_posts'        => true,
        'delete_posts'         => true,
        'moderate_comments'    => true,
    ]
);

Dwie rzeczy z tej sygnatury nie wynikają z dokumentacji API.

Zapisuje raz i potem Cię ignoruje. Handbook wtyczek mówi, że po pierwszym wywołaniu rola i capabilities są w bazie, a „kolejne wywołania nic nie robią: także przy zmianie listy uprawnień”. Edytujesz tablicę, wdrażasz - nic się nie zmienia. Każda zmiana capabilities wymaga migracji:

const EDITORIAL_LEAD_VERSION = 3;

function wppoland_sync_editorial_lead() {
    if ( (int) get_option( 'wppoland_editorial_lead_version' ) === EDITORIAL_LEAD_VERSION ) {
        return;
    }

    remove_role( 'editorial_lead' );
    add_role( 'editorial_lead', 'Szef redakcji', [ /* ... */ ] );

    update_option( 'wppoland_editorial_lead_version', EDITORIAL_LEAD_VERSION );
}
register_activation_hook( __FILE__, 'wppoland_sync_editorial_lead' );
add_action( 'admin_init', 'wppoland_sync_editorial_lead' );

Kopia na admin_init ma znaczenie w multisite i przy deployach, które nie odpalają activation hooka. Bramka wersji trzyma to poza ścieżką krytyczną; handbook ostrzega, że bezwarunkowe usuwanie i ponowne dodawanie „mocno degraduje wydajność”.

remove_role() nie rusza użytkowników. Usuwa klucz w options; meta wp_capabilities nadal trzyma nazwę roli, a WP_Roles::is_role() już zwraca false - ciąg nic nie daje. Przypisz użytkowników przed usunięciem albo zaraz po.

Gdzie leży wiersz. WP_Roles::for_site() buduje klucz $wpdb->get_blog_prefix( $this->site_id ) . 'user_roles' (wp_user_roles na single-site, wp_3_user_roles na subsite 3). Role tylko w kodzie: global $wp_user_roles w wp-config.php - wtedy opcja nie jest aktualizowana ani używana.

#3. Dodawanie uprawnień do istniejących ról

get_role() plus add_cap() to mniejszy diff, gdy domyślna rola jest prawie dobra:

$role = get_role( 'editor' );
if ( $role ) {
    $role->add_cap( 'edit_theme_options' );
}

Ta sama bramka wersji z sekcji 2 obowiązuje. WP_Role::add_cap( $cap, $grant = true ) ma dwa parametry; drugi to grant/deny, nie „czy zapisać”. Zapis idzie przez WP_Roles::add_cap() i update_option() przy każdym wywołaniu, gdy use_db jest true - add_cap() na init bez bramki to zapis options przy każdym requeście.

Większy problem to zakres. edit_theme_options przy klasycznym motywie otwiera mniej więcej Widgety, Menu i Customize. Przy motywie blokowym dokumentacja core nazywa je głównym capability Site Editora i ostrzega, że nie ogranicza się do niego. Editor dostaje szablony, style i nawigację - więcej niż switch_themes.

Pełny Site Editor: edit_theme_options plus edit_posts, edit_pages, edit_others_posts, read, upload_files. Bez edit_others_posts panel się ładuje, a zapis pada - czytaj Network pod 401/403; kod REST nazywa brakujące uprawnienie.

#4. Odzyskiwanie dostępu: resetowanie ról

Skrypt resetu z parametrem GET, który krążył też we wcześniejszych edycjach tego artykułu, to zdalne podniesienie uprawnień:

// Nie wdrażaj tego.
if ( ! isset( $_GET['reset_roles_secret_key'] ) ) return;
require_once( ABSPATH . 'wp-admin/includes/schema.php' );
populate_roles();

isset() sprawdza obecność parametru, nie poprawność. Każdy niezalogowany gość z ?reset_roles_secret_key na dowolnym URL przywraca domyślne capabilities - w tym to, co celowo ściąłeś z Editor lub Author.

Użyj WP-CLI. wp role reset korzysta z tej samej funkcji core i jest uwierzytelniony dostępem do shella:

wp role reset --all
wp role reset editor

Robi też więcej niż samo populate_roles(), i różnica ma znaczenie przy debugowaniu. populate_roles() woła populate_roles_160() przez populate_roles_300(), a te funkcje tylko add_role() i add_cap(). Nie ma w nich remove_cap(). Wywołane bezpośrednio przywracają usunięte uprawnienia i zostawiają każde dodane przez wtyczkę. WP-CLI robi na tym diff względem czystej instalacji, stąd output w stylu:

Restored 1 capability to and removed 0 capabilities from 'administrator' role.

Własne role żaden z tych ścieżek nie rusza. Jeśli problemem jest popsuta własna rola, reset jej nie naprawi.

Zanim zresetujesz, podejrzyj stan - to jeden wiersz options:

wp option get wp_user_roles --format=json | jq '.editor.capabilities'
wp cap list editor
wp user list --field=user_login --role=administrator

Ostatnie polecenie warto odpalić na każdej stronie, którą dziedziczysz. Liczba administratorów, która Cię zaskakuje, jest już diagnozą.

#5. Najlepsze praktyki bezpieczeństwa 2026

#Przytnij listę Administratorów, nie tylko dodawaj role

Najmniejsze uprawnienie to nie nowa rola - to krótsza lista administratorów. Odpal wp user list --role=administrator na stronach, które utrzymujesz. Konta agencji, odejście freelancera i login wsparcia wtyczki sprzed dwóch lat to typowi „lokatorzy”.

#Enumeracja to nie hardening, ale nic nie kosztuje

Instalator pyta o login od WordPress 3.0, więc zostaje enumeracja: REST użytkowników bez auth zwraca m.in. slug (user_nicename). roles i capabilities są w kontekście edit. To lista loginów, nie breach - rate limiting formularza waży więcej.

#Zamknij ścieżki plików stałymi, nie capabilities

Usunięcie edit_themes z Administratora to zmiana capability, którą ktoś może cofnąć. Stała w wp-config.php jest sprawdzana w map_meta_cap() i nie da się jej obejść grantem:

define( 'DISALLOW_FILE_EDIT', true );

DISALLOW_FILE_EDIT mapuje edit_files, edit_plugins i edit_themes na do_not_allow. DISALLOW_FILE_MODS idzie dalej: blokuje instalację i aktualizację wtyczek oraz motywów z panelu i wyłącza edytor plików jako efekt uboczny. Na stronie wdrażanej z Gita DISALLOW_FILE_MODS jest uczciwym ustawieniem, bo aktualizacje i tak idą pipeline’em.

DISALLOW_UNFILTERED_HTML działa tak samo i warto ją ustawić na stronie redakcyjnej, gdzie Editorzy nie muszą wklejać skryptów.

#Mapuj meta capabilities na własnych typach treści

register_post_type() domyślnie ustawia map_meta_cap na null, nie false. WP_Post_Type::set_props() potem zamienia null na true, gdy capabilities jest puste i capability_type to post lub page (kompatybilność wsteczna z 3.0, ticket #14122), a w pozostałych przypadkach spada do false. Własny capability_type jak poniżej ląduje więc na false, o ile tego nie powiesz. Ustaw oba argumenty:

register_post_type( 'book', [
    'capability_type' => [ 'book', 'books' ],
    'map_meta_cap'    => true,
] );

Forma tablicowa nie jest ozdobą. Przy zwykłym stringu WordPress dokleja s do liczby mnogiej - stąd storys dla story. Referencja register_post_type() wskazuje tablicę jako sposób podania alternatywnej liczby mnogiej.

capability_type => 'book' daje meta edit_book / read_book / delete_book i prymitywy w liczbie mnogiej. Kolejne sześć (m.in. delete_others_books, edit_published_books) powstaje tylko przy map_meta_cap => true. Bez flagi użytkownik z edit_books edytuje książki wszystkich. create_posts domyślnie mapuje się na edit_books - rola „edytuj, nie twórz” wymaga jawnego wpisu w capabilities.

#Sprawdzaj capability w punkcie decyzji

Sprawdzenie należy obok operacji, nie przy menu. add_menu_page() z capability chowa link. Nie chroni handlera. Każdy admin-post, callback AJAX i trasa REST potrzebuje własnego current_user_can(), a REST w permission_callback, nie w handlerze - inaczej WordPress loguje _doing_it_wrong().

Dla operacji na konkretnym obiekcie przekaż obiekt: current_user_can( 'edit_post', $post_id ) idzie przez map_meta_cap() i rozstrzyga własność. current_user_can( 'edit_posts' ) pyta o typ treści, nie o ten wpis.

W multisite super admin omija allcaps wcześniej niż admin witryny - to samo rozróżnienie z sekcji 1.

#Podsumowanie

Role to magazyn. Capabilities to decyzja. Cały przewodnik wynika z tego podziału:

  • Sprawdzaj capabilities, nigdy nazw ról - current_user_can('administrator') przechodzi przez merge kluczy meta użytkownika i zwraca false dla każdej własnej roli, którą zbudujesz.
  • Traktuj add_role() i add_cap() jak migracje wiersza w bazie, z bramką wersji, a nie jak konfigurację, którą edytujesz i wdrażasz.
  • Czytaj realny zakres capability przed grantem. edit_theme_options przy motywie blokowym to Site Editor, nie tylko ekran menu.
  • Odzyskuj przez wp role reset --all, nigdy przez nieuwierzytelniony parametr w URL, i pamiętaj, że własne role zostawia w spokoju.
  • Ustaw map_meta_cap => true na każdym własnym typie treści z więcej niż jednym autorem.

Audyt listy administratorów i mapy uprawnień: audyt bezpieczeństwa WordPress albo kontakt.

Następny krok

Przekuj artykuł w realne wdrożenie

Pod tym wpisem dokładam linki, które domykają intencję użytkownika i prowadzą dalej w strukturze serwisu.

Powiązany klaster

Sprawdź inne usługi WordPress i bazę wiedzy

Wzmocnij swój biznes dzięki profesjonalnemu wsparciu technicznemu w kluczowych obszarach ekosystemu WordPress.

FAQ do artykułu

Często zadawane pytania

Najważniejsze odpowiedzi, które pomagają wdrożyć temat w praktyce.

SEO-readyGEO-readyAEO-ready5 Q&A
Jaka jest różnica między rolą a capability w WordPressie?#
Capability to pojedynczy ciąg uprawnienia, na przykład edit_posts albo manage_options. Rola to nazwany zestaw takich ciągów zapisany w options. WordPress sprawdza capability w momencie decyzji, więc rola ma znaczenie tylko jako kontener, który to uprawnienie dostarcza.
Czy w kodzie lepiej sprawdzać rolę czy capability?#
Capability. current_user_can('administrator') działa przypadkiem, bo WP_User::get_role_caps() scala meta użytkownika, gdzie nazwy ról są kluczami, do allcaps. Dla własnej roli z manage_options zwraca false i blokuje użytkownika, dla którego tę rolę zbudowałeś.
Jak zresetować popsute role w WordPressie?#
Uruchom wp role reset --all przez WP-CLI. Wywołuje populate_roles() i robi diff, więc przywraca usunięte domyślne uprawnienia i usuwa dodane. Samo populate_roles() tylko dokłada brakujące, bo funkcje populate_roles_* nigdy nie wołają remove_cap().
Dlaczego add_role() ignoruje nowe uprawnienie?#
add_role() zapisuje do bazy raz. Handbook wtyczek mówi wprost, że kolejne wywołania nic nie robią, także przy zmianie listy capabilities. Trzymaj numer wersji w opcji, porównuj przy aktywacji i przy zmianie wywołaj remove_role(), a potem add_role().
Które uprawnienie steruje Site Editorem w motywie blokowym?#
Głównie edit_theme_options. WordPress sprawdza je na endpointach REST szablonów i części szablonów. Pełne użycie Site Editora zwykle wymaga też edit_posts, edit_pages, edit_others_posts, read i upload_files.

Potrzebujesz FAQ dopasowanego do branży i rynku? Przygotujemy wersję pod Twoje cele biznesowe.

Porozmawiajmy

Polecane artykuły