Skip to content

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

@ice1e0

Description

@ice1e0

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions