Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add settings for user role selection #76

Merged
merged 25 commits into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@

Safe SVG is the best way to Allow SVG Uploads in WordPress!

It gives you the ability to allow SVG uploads whilst making sure that they're sanitized to stop SVG/XML vulnerabilities affecting your site. It also gives you the ability to preview your uploaded SVGs in the media library in all views.
It gives you the ability to allow SVG uploads (and limit that by user role) whilst making sure that they're sanitized to stop SVG/XML vulnerabilities affecting your site. It also gives you the ability to preview your uploaded SVGs in the media library in all views.

### Current Features
* **Sanitised SVGs** - Don't open up security holes in your WordPress site by allowing uploads of unsanitised files.
* **View SVGs in the Media Library** - Gone are the days of guessing which SVG is the correct one, we'll enable SVG previews in the WordPress media library.

### Features on the Roadmap
* **SVGO Optimisation** - You'll have the option to run your SVGs through our SVGO server on upload to save you space.
* **Choose Who Can Upload** - Restrict SVG uploads to certain users on your WordPress site or allow anyone to upload.

Initially a proof of concept for [#24251](https://core.trac.wordpress.org/ticket/24251).

Expand Down
154 changes: 154 additions & 0 deletions includes/safe-svg-settings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php
/**
* Safe SVG plugin settings.
*
* @package safe-svg
*/

/**
* SVG settings class.
*/
class safe_svg_settings {

/**
* Set up the class
*/
public function __construct() {
add_action( 'admin_init', [ $this, 'settings_init' ] );
add_filter( 'pre_update_option_safe_svg_upload_roles', [ $this, 'update_capability' ], 10, 2 );
}

/**
* Custom option and settings
*/
public function settings_init() {
register_setting( 'media', 'safe_svg_upload_roles', [ $this, 'sanitize_safe_svg_roles' ] );

add_settings_section(
'safe_svg_settings',
__( 'Safe SVG Settings', 'safe-svg' ),
[ $this, 'safe_svg_settings_callback' ],
'media'
);

add_settings_field(
'safe_svg_roles',
__( 'User Roles', 'safe-svg' ),
[ $this, 'safe_svg_roles_cb' ],
'media',
'safe_svg_settings'
);
}

/**
* Sanitizes roles before saving.
*
* @param array $roles The roles that we are attempting to save
*
* @return array The sanitized roles array.
*/
public function sanitize_safe_svg_roles( $roles ) {
if ( ! is_array( $roles ) ) {
$roles = [];
}

$valid_roles = $this->get_upload_capable_roles();
$valid_slugs = array_keys( $valid_roles );
$roles = array_intersect( $valid_slugs, $roles );

// Store a non empty/falsy value for easier handling.
if ( empty( $roles ) ) {
$roles = 'none';
}

return $roles;
}

/**
* Get roles with with upload capabilities.
*
* @return array An array of roles with the upload_files capability.
*/
public function get_upload_capable_roles() {
$all_roles = get_editable_roles();
$upload_roles = array_filter(
$all_roles,
function( $_role ) {
return $_role['capabilities']['upload_files'] ?? false;
}
);

return apply_filters( 'safe_svg_upload_roles', $upload_roles, $all_roles, $this );
dkotter marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Settings section callback function.
*
* @param array $args The settings array, defining title, id, callback.
*/
public function safe_svg_settings_callback( $args ) {
?>
<p id="<?php echo esc_attr( $args['id'] ); ?>">
<?php esc_html_e( 'Select which user roles can upload SVG files.', 'safe-svg' ); ?>
</p>
<?php
}

/**
* User role field callback function.
*/
public function safe_svg_roles_cb() {
$upload_roles = (array) get_option( 'safe_svg_upload_roles', [] );
$role_options = $this->get_upload_capable_roles();

if ( empty( $upload_roles ) ) {
$upload_roles = array_keys( $role_options );
}

foreach ( $role_options as $role => $info ) :
?>
<div>
<label>
<input type="checkbox" name="safe_svg_upload_roles[]" value="<?php echo esc_attr( $role ); ?>" <?php checked( in_array( $role, $upload_roles, true ), true ); ?> /> <?php echo esc_html( $info['name'] ); ?>
dkotter marked this conversation as resolved.
Show resolved Hide resolved
</label>
</div>
<?php
endforeach;
}

/**
* Update user role capability based on the settings.
*
* @param array $new_roles New user roles.
* @param array $old_roles Old user roles.
*
* @return array
*/
public function update_capability( $new_roles, $old_roles ) {
$add_roles = array_filter( array_diff( (array) $new_roles, (array) $old_roles ) );
$remove_roles = array_filter( array_diff( (array) $old_roles, (array) $new_roles ) );

if ( ! empty( $add_roles ) ) {
foreach ( $add_roles as $role ) {
$role = get_role( $role );

if ( $role instanceof \WP_Role ) {
$role->add_cap( 'safe_svg_upload_svg' );
}
}
}

if ( ! empty( $remove_roles ) ) {
foreach ( $remove_roles as $role ) {
$role = get_role( $role );

if ( $role instanceof \WP_Role ) {
$role->remove_cap( 'safe_svg_upload_svg' );
}
}
}

return $new_roles;
}

}
3 changes: 1 addition & 2 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ Enable SVG uploads and sanitize them to stop XML/SVG vulnerabilities in your Wor

Safe SVG is the best way to Allow SVG Uploads in WordPress!

It gives you the ability to allow SVG uploads whilst making sure that they're sanitized to stop SVG/XML vulnerabilities affecting your site. It also gives you the ability to preview your uploaded SVGs in the media library in all views.
It gives you the ability to allow SVG uploads (and limit that by user role) whilst making sure that they're sanitized to stop SVG/XML vulnerabilities affecting your site. It also gives you the ability to preview your uploaded SVGs in the media library in all views.

#### Current Features
* **Sanitised SVGs** - Don't open up security holes in your WordPress site by allowing uploads of unsanitised files.
* **View SVGs in the Media Library** - Gone are the days of guessing which SVG is the correct one, we'll enable SVG previews in the WordPress media library.

#### Features on the Roadmap
* **SVGO Optimisation** - You'll have the option to run your SVGs through our SVGO server on upload to save you space.
* **Choose Who Can Upload** - Restrict SVG uploads to certain users on your WordPress site or allow anyone to upload.

Initially a proof of concept for [#24251](https://core.trac.wordpress.org/ticket/24251).

Expand Down
35 changes: 33 additions & 2 deletions safe-svg.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function() {

require 'includes/safe-svg-tags.php';
require 'includes/safe-svg-attributes.php';
require_once 'includes/safe-svg-settings.php';

if ( ! class_exists( 'safe_svg' ) ) {

Expand Down Expand Up @@ -84,6 +85,25 @@ public function __construct() {
add_filter( 'wp_generate_attachment_metadata', array( $this, 'skip_svg_regeneration' ), 10, 2 );
add_filter( 'wp_get_attachment_metadata', array( $this, 'metadata_error_fix' ), 10, 2 );
add_filter( 'wp_calculate_image_srcset_meta', array( $this, 'disable_srcset' ), 10, 4 );

new safe_svg_settings();
}

/**
* Custom function to check if user can upload svg
* Use core caps if setting hasn't every been updated
*
* @return Bool
*/
public function current_user_can_upload_svg() {
$upload_roles = get_option( 'safe_svg_upload_roles', [] );

// Fallback to upload_files check for backwards compatibility.
if ( empty( $upload_roles ) ) {
return current_user_can( 'upload_files' );
}

return current_user_can( 'safe_svg_upload_svg' );
}

/**
Expand All @@ -94,8 +114,10 @@ public function __construct() {
* @return mixed
*/
public function allow_svg( $mimes ) {
$mimes['svg'] = 'image/svg+xml';
$mimes['svgz'] = 'image/svg+xml';
if ( $this->current_user_can_upload_svg() ) {
$mimes['svg'] = 'image/svg+xml';
$mimes['svgz'] = 'image/svg+xml';
}

return $mimes;
}
Expand Down Expand Up @@ -148,6 +170,15 @@ public function check_for_svg( $file ) {
$type = ! empty( $wp_filetype['type'] ) ? $wp_filetype['type'] : '';

if ( 'image/svg+xml' === $type ) {
if ( ! $this->current_user_can_upload_svg() ) {
$file['error'] = __(
'Sorry, you are not allowed to upload SVG files.',
'safe-svg'
);

return $file;
}

if ( ! $this->sanitize( $file['tmp_name'] ) ) {
$file['error'] = __(
"Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded",
Expand Down
6 changes: 6 additions & 0 deletions tests/cypress/e2e/admin.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@ describe('Admin can login and make sure plugin is activated', () => {
cy.activatePlugin('safe-svg');
cy.deactivatePlugin('safe-svg-cypress-test-plugin');
});

it('Can enable user role', () => {
cy.visit('/wp-admin/options-media.php');
cy.get('[name="safe_svg_upload_roles[]"]').first().check();
cy.get('#submit').click()
});
});
34 changes: 34 additions & 0 deletions tests/unit/test-safe-svg.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,37 @@ public function test_constructor() {
* @return void
*/
public function test_allow_svg() {
\WP_Mock::userFunction(
'get_option',
array(
'args' => [ 'safe_svg_upload_roles', [] ],
'return' => [ 'editor' ],
)
);

\WP_Mock::userFunction(
'current_user_can',
array(
'args' => 'safe_svg_upload_svg',
'return' => true,
)
);

$allowed_svg = $this->instance->allow_svg( array() );
$this->assertNotEmpty( $allowed_svg );
$this->assertContains( 'image/svg+xml', $allowed_svg );
}

/**
* Test dont_allow_svg function.
*
* @return void
*/
public function test_dont_allow_svg() {
$allowed_svg = $this->instance->allow_svg( array() );
$this->assertEmpty( $allowed_svg );
}

/**
* Test fix_mime_type_svg function.
*
Expand Down Expand Up @@ -115,6 +141,14 @@ public function test_check_for_svg() {
)
);

\WP_Mock::userFunction(
'current_user_can',
array(
'args' => 'upload_files',
'return' => true,
)
);

// Test sanitize on valid SVG.
$temp = tempnam( sys_get_temp_dir(), 'TMP_' );
$files_dir = __DIR__ . '/files';
Expand Down