bcc-code / bcc-wp

[BCC IT] Custom Wordpress themes and plugins which can be used in any BCC application (currently includes the BCC Login plugin)
Apache License 2.0
0 stars 7 forks source link

Add security filtering for chruch or PMO ID for components #166

Open ice1e0 opened 4 months ago

ice1e0 commented 4 months ago

I have a plugin which adds the option to add a permission filter for church or PMO ID:

This plugin gives the possiblility to hide sites or UX builder objects based on the church or PMO ID given in the id token:

UX Builder field:

image

Site field:

image

I guess that such functionaliy would be interesting for many people using the BCC Wordpress plugin so I would like to get this merged into the main plugin - but I am not an expert in neither Wordpress nor PHP. Can someone help me to create a PR for this?

This is the plugin for this functionality:

<?php
/*
Plugin Name: Only Selected Church Or PMO-ID
Description: Allows authors to restrict access to parts or full pages and posts or to widgets to users of your local church or specific users by PMO-ID. Requires the plugin "BCC Signon"
Version: 1.1.0
Author: Simon Schick
Co-Authored: Bastian Wissler, Leonhard Schick
Author URI: http://simonsimcity.net/

CHANGELOG
---------

Version 1.1.0
- use church and user id from id token instead of user metadata
- use case insensitive matching for church name

Version 1.0.0
- initial version
*/

/* =========================================================================
 * Non-admin filters
 * these perform the actual changes seen by visiters
 * =========================================================================*/

/**
 * add filters used on non-admin screens
 * filter out pages from menus, replace content with message
 */
add_filter('wp_get_nav_menu_items', 'sco_filter_nav_items');
add_filter('wp_list_pages_excludes', 'sco_pages_excludes');
add_filter('get_pages', 'sco_filter_pages');
add_filter('the_posts', 'sco_filter_pages');
add_action('template_redirect', 'sco_redirect');

/**
 * exclude locked pages from the list used in the default main menu
 * @global type $wpdb wordpress database access
 * @param type $excludes the list of excludes
 * @return type the modified list of excludes
 */
function sco_pages_excludes($excludes){
  global $current_user, $wpdb;
  $sql = "SELECT post_id, meta_value from $wpdb->postmeta WHERE meta_key = 'sco_selection'";

  $rows = $wpdb->get_results($sql, ARRAY_A );
  $ids = array();
  foreach($rows as $row) {
    if(!sco_allowed($row['post_id'], true, $row['meta_value'])){
      $ids[] = $row['post_id'];
    }
  }

  $excludes = array_merge($excludes, $ids);
  return $excludes;
}

/**
 * filter the navigation menu items. returns all if the user is logged in (ID > 0)
 * or those that do not have the sco_selection meta set if the user is not logged in
 *
 * @global type $current_user the object representing th ecurren user
 * @param type $items the menu items
 * @return type the filtered menu items
 */
function sco_filter_nav_items($items) {
  global $current_user;
  foreach($items as $item) {
    $allowed  = sco_allowed($item->object_id, true);
    if($allowed)$filtered[] = $item;
  }

  return $filtered;
}

/**
 * replace the content of posts and pages with the message that the page is locked
 * will not reduce the list to empty, always returns at least one post
 * @global type $current_user
 * @param type $posts a list of posts or pages
 * @return type  the filtered list
 */
function sco_filter_pages($pages) {
  $filtered = array();
  foreach($pages as $page) {

    $allowed = sco_allowed($page->ID);
    $id = $page->ID;
    if($allowed) {
      $filtered[] = $page;
    } else {
      if($page->post_type == 'page') {
        $filtered[] = sco_set_locked_text($page);
      }
    }
  }
  if(!$filtered && $pages){
    $filtered[] = sco_set_locked_text($pages[0]);
  }
  return $filtered;
}

/*
 * check for direct access to page or post
 * and produce 404 if requested
 */
function sco_redirect() {
  global $post;
  $pid = $post->ID;
  $allowed = sco_allowed($post->ID);
  error_log("allowed=$allowed");
  if($allowed)return;

  error_log("forcing 404");
  status_header( 404 );
  nocache_headers();
  include( get_query_template( '404' ) );
  die();
}

/**
 * set the text for pages that should not be displayed, also turns off comments
 * @global type $current_user
 * @param type $page
 * @return type
 */
function sco_set_locked_text($page) {
  global $current_user;
  if(0 < $current_user->ID) {
  } else {
    $page->post_content = get_option('sco_selection_text', sco_default_login_text());
  }
  $page->comment_status = 'close';
  return $page;
}

/**
 * determine if content is to be displayed
 * always display if in admin pages
 * otherwise display based on lco locked value
 * @global type $current_user
 * @param type $post_id of the content
 * @param type $menu true if this is for a menu
 * @return type true if not restricted
 */
function sco_allowed($post_id, $menu=false, $value=null) {
  if(is_admin())return true;

  $logged_in = is_user_logged_in();
  if(!$value)$value = get_post_meta($post_id, 'sco_selection', true);
  if(!$value)return true;

  return sco_is_user_allowed($value);
}

function sco_get_oidc_token() {
  #setcookie('wordpress_nocache', 'true');
  $token = '';
  $token_id = $_COOKIE['oidc_token_id'];
  if ( ! empty( $token_id ) ) {    
      $token = get_transient( 'oidc_id_token_' . $token_id );
  }
  return $token;
}

function sco_get_church_for_user() {
  $jwt_token_payload = BCC_Login_Token_Utility::get_token_claims( sco_get_oidc_token() );
  if ($jwt_token_payload) {
    return $jwt_token_payload["https://login.bcc.no/claims/churchName"];
  } else {
    return '';
  }
}

function sco_get_pmoid_for_user() {
  $jwt_token_payload = BCC_Login_Token_Utility::get_token_claims( sco_get_oidc_token() );
  if ($jwt_token_payload) {
    return $jwt_token_payload["https://login.bcc.no/claims/personId"] . "";
  } else {
    return '';
  }
}

function sco_is_user_allowed($selection) {
  $cleaned_selection = array_map('strtolower', explode(',',preg_replace('/\s+/', '', $selection)));
  return $selection === '' || in_array(strtolower(sco_get_church_for_user()), $cleaned_selection) || in_array(sco_get_pmoid_for_user(), $cleaned_selection);
}

/* =========================================================================
 * Page and post admin, used by authors, editors and administrators
 * filter the list of pages and posts
 * allow the user to set the locked status of a page or post
 * =========================================================================*/

/**
 * set up filers for the posts and pages lists
 * add action to add the locked column
 */
add_filter('manage_posts_columns', 'sco_posts_columns');
add_filter('manage_pages_columns', 'sco_posts_columns');
add_action('manage_posts_custom_column',  'sco_show_columns');
add_action('manage_pages_custom_column',  'sco_show_columns');

/**
 * add the locked column and set its headre
 * @param array $columns list of columns
 * @return string list of columns with locked column added
 */
function sco_posts_columns($columns) {
  $columns['sco_selection'] = 'Selected Churches or PMO-IDs Only';
  return $columns;
}

/**
 * display the content of the locked column
 * @global type $post the post for this row and column
 * @param type $name the name of the column
 */
function sco_show_columns($name) {
  sco_show_column_value($name, 'sco_selection');
}

/**
 * display the value for a column
 * @param type $name the column being processed
 * @param type $key the post meta key that should match the column
 */
function sco_show_column_value($name, $key) {
  global $post;
  if($name == $key) {
    $value =  get_post_meta($post->ID, $key, true);
    echo $value;
  }
}

/**
 * set up meta box support on the page and post edit pages
 */
add_action('add_meta_boxes', 'sco_add_metas');
add_action('save_post', 'sco_save_meta');

/**
 * add actions to create meta boxes, this must be delayed until the add_meta-box
 * these allow the user to set the locked status
 */
function sco_add_metas() {
  add_meta_box('sco_show_meta', 'Selected Churches or PMO-IDs Only', 'sco_show_meta', 'post', 'side');
  add_meta_box('sco_show_meta', 'Selected Churches or PMO-IDs Only', 'sco_show_meta', 'page', 'side');
}

/**
 * display the locked meta box
 * @param type $post
 */
function sco_show_meta($post) {
  $id = $post->ID;

  $fieldId = esc_attr( 'sco_selection' );
  $fieldTitle = __( 'Limit to members of church or PMO-ID:', 'text_domain' );
  $fieldDescription = __( 'List the churches or PMO-IDs you want to limit this item to. List is comma-separated.', 'text_domain' );
  $fieldName = esc_attr( 'sco_selection' );
  $fieldValue = esc_attr( get_post_meta($id, 'sco_selection', true) );

  $html = <<<QEND
  <p>
    <label for="$fieldId">$fieldTitle
      <input class="widefat" value="$fieldValue" id="$fieldId" name="$fieldName" type="text" />
      <small>$fieldDescription</small>
    </label>
  </p>
QEND;
  echo $html;
}

/**
 * process the meta box result data
 * @param type $post_id
 */
function sco_save_meta($post_id) {
  $field = 'sco_selection';

  if ($_POST['sco_selection']) {
    update_post_meta($post_id, $field, $_POST['sco_selection']);
  } else {
    delete_post_meta($post_id, $field);
  }
}

/* =========================================================================
 * Build settings used by administrators either as a separate page or as a
 * section in the general settings page.
 *
 * This code uses the new settings api
 * thanks to http://ottopress.com/2009/wordpress-settings-api-tutorial/ for the helpful tutorial
 * =========================================================================*/

/**
 * set up actions to link into the admin settings
 */
$sco_options_location = 'sco_options'; // on a separate page

add_action('admin_init', 'sco_admin_init');
if($sco_options_location == 'sco_options') {
  add_action('admin_menu', 'sco_add_option_page');
}

/**
 * run when admin initializes
 * register our settings as part of the sco_options_group
 * add the section sco_options:sco_options_main
 * add the fields to that section
 */

function sco_admin_init() {
  global $sco_options_location;

  add_settings_section  ('sco_options_main', '', 'sco_main_section_text', $sco_options_location);
  register_setting('sco_options_group', 'sco_selection_text');
  add_settings_field('sco_selection_text', 'Text to display in place of locked content', 'sco_selection_text', $sco_options_location, 'sco_options_main');
}

/**
 * action to add the custom options page, for users with manage_options capabilities
 */
function sco_add_option_page() {
  add_options_page('Local Church or PMO-ID Only Settings', 'Local Church or PMO-ID Only',
          'manage_options', 'sco_options', 'sco_options_page');
}

/**
 * display the custom options page
 */
function sco_options_page() {
  ?>
  <div class="wrap">
    <h2>Local Church or PMO-ID Only Settings</h2>
    <p>Church: <?php echo sco_get_church_for_user() ?></p>
    <p>ID: <?php echo sco_get_pmoid_for_user() ?></p>
    <form action="options.php" method="post">
      <?php settings_fields('sco_options_group');?>
      <?php do_settings_sections('sco_options')?>
      <p class="submit"> <input name="submit" class="button-primary" type="submit" value="Save changes"/></p>
    </form>
  </div>
  <?php
}

/**
 * display the section title, empty if separate page
 */
function sco_main_section_text() {
  global $sco_options_location;
  if($sco_options_location != 'sco_options'){
    echo "<strong>Local Church or PMO-ID Only</strong>";
  }
}

/**
 * display the form field for the locked page content text
 */
function sco_selection_text() {
  $value=trim(get_option('sco_selection_text', ''));
  if(!$value) $value = sco_default_login_text ();
  echo '<textarea id="sco_selection_text" name="sco_selection_text" rows="5" cols="40" >'.$value.'</textarea>';
}

function sco_default_login_text() {
  return '<h3>This content is for local church or selected members only.</h3>';
}

/**
 * Access control for all widgets
 */
if ( ! function_exists( 'olc_display_selection_field_in_widget_form' ) ) {
  add_action( 'in_widget_form', 'olc_display_selection_field_in_widget_form', 10, 3 );

  /**
   * Append custom selection field end of the widgets control form
   */
  function olc_display_selection_field_in_widget_form( $widget, $return, $instance ) {
    $sco_selection = isset( $instance['sco_selection'] ) ? $instance['sco_selection'] : '';

    ob_start(); 
    ?>

    <p>
      <label for="<?php echo esc_attr( $widget->get_field_id( 'sco_selection' ) ) ?>"><?php _e( 'Limit to members of church or PMO-IDs:', 'text_domain' ) ?>
        <input class="widefat" value="<?php echo esc_attr( $sco_selection ) ?>" id="<?php echo esc_attr( $widget->get_field_id( 'sco_selection' ) ) ?>" name="<?php echo esc_attr( $widget->get_field_name( 'sco_selection' ) ) ?>" type="text" />
        <small><?php _e( 'List the churches or PMO-IDs you want to limit this widget to. List is comma-separated.', 'text_domain' ) ?></small>
      </label>
    </p>

    <?php 
    echo ob_get_clean();
  }
}

if ( ! function_exists( 'olc_update_selection_field_in_widget_form' ) ) {
  add_action( 'widget_update_callback', 'olc_update_selection_field_in_widget_form', 10, 2 );

  function olc_update_selection_field_in_widget_form( $instance, $new_instance ) {
    $instance['sco_selection'] = !empty( $new_instance['sco_selection'] ) ? $new_instance['sco_selection'] : '';
    return $instance;
  }
}

if ( ! function_exists( 'olc_apply_access_control_field_on_widget' ) ) {
  add_filter( 'widget_display_callback', 'olc_apply_access_control_field_on_widget', 10, 1 );
  function olc_apply_access_control_field_on_widget( $array ) {
    $selection = isset( $array['sco_selection'] ) ? $array['sco_selection'] : '';
    if (sco_is_user_allowed($selection)) {
      return $array;
    } else {
      return false;
    }
  };
}

/**
 * Access control for all shortcodes
 */
if ( ! function_exists( 'olc_add_selection_to_ux_builder_shortcode_data' ) ) {
  add_filter( 'ux_builder_shortcode_data', 'olc_add_selection_to_ux_builder_shortcode_data', 10, 1 );
  function olc_add_selection_to_ux_builder_shortcode_data( $data ) {
    $data['options']['sco_selection'] = array(
      'type' => 'textfield',
      'heading' => 'Limit to members of church or PMO-IDs:',
      'placeholder' => 'Enter church names or PMO-IDs (comma separated)...',
      'default' => '',
    );
    return $data;
  };
}

if ( ! function_exists( 'olc_pre_do_shortcode_tag' ) ) {
  add_filter( 'pre_do_shortcode_tag', 'olc_pre_do_shortcode_tag', 10, 3 );
  function olc_pre_do_shortcode_tag( $data, $tag, $attr ) {
    if ($data !== false) {
      return $data;
    }

    // Hide the element (return empty string) if ...
    return (
      is_array($attr) &&
      !empty($attr['sco_selection']) &&
      !sco_is_user_allowed($attr['sco_selection'])
    ) ? '' : false;
  };
}

/* =========================================================================
 * end of program, php close tag intentionally omitted
 * ========================================================================= */

It is possible to add it as a dedicated plugin.