Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: wordpress

  • Restrict Site Access Filters

    Restrict Site Access Filters

    I have a demo site I use to for development. One of the things I want is to be able to lock the site to logged in users only and that I can do via Restricted Site Access by 10up.

    One of the things the plugin also allows is to open up access to an IP, so someone who doesn't have an account can check the site before you go live. The problem with this feature is caching.

    Caching Restricted Pages

    It doesn't really matter what kind of caching system you use, the point is all the same. People who aren't logged in should get a cached version of the content. People who are logged in, or whom you've determined need a unique experience, don't get cached content. That's the barebones of caching.

    The problem I ran into with restricted site access is that if I whitelisted an IP range, and someone from that range visited the site, they generated a page which my cache system … cached. That meant the next person got to see the cached content.

    Worf from Star Trek face-palming

    Now this may not actually be a problem in all cache systems, but I happened to be using Varnish, which is fairly straightforward about how it works. And, sadly, the plugin I'm using doesn't have a way around this. Yet.

    Filters and Hooks

    Like any enterprising plugin hoyden, I popped open the code and determined I needed to address the issue here:

    // check if the masked versions match
    if ( ( inet_pton( $ip ) & $mask ) == ( $remote_ip & $mask ) ) {
    	return;
    }

    This section of code is checking "If the IP matches the IP we have on our list, stop processing the block. It's okay to show them the content." What I needed was to add something just above the return to tell it "And if it's Varnish, don't cache!"

    At first my idea was to just toss a session_start() in there, which does work. For me. Adam Silverstein was leery of that having unintended consequences for others, and wouldn't it be better to make it hookable? After all, then any caching plugin could hook in! He was right, so I changed my pull request to this:

    do_action( 'restrict_site_access_ip_match', $remote_ip, $ip, $mask ); // allow users to hook ip match

    The next version of the release will have that code.

    In The Field

    Now, assuming you've slipped that code into your plugin, how do you actually use it?

    Since I need to have this only on my 'dev' site, and I'm incredibly lazy efficient, I decided to put this code into the MU plugins I use for the site:

    if ( DB_HOST == 'mysql.mydevsite.dream.press' ) {
    	add_action( 'restrict_site_access_ip_match', 'mydevsite_restrict_site_access_ip_match' );
    }
    
    function mydevsite_restrict_site_access_ip_match() {
    	session_start();
    }

    This is not the only way to do it. I also happen to have a define of define( 'MYSITE_DEV', true ); in my wp-config.php file, so I could have checked if that was true:

    if ( defined( 'MYSITE_DEV' ) && MYSITE_DEV ) { ... }

    Now, you'll notice I'm using sessions, even after Adam and I determined this could be bad for some people. It can. And in my case, in this specific situation, it's not dangerous. It's a quick and dirty way to tell Varnish not to cache (because PHP sessions indicate a unique experience is needed).

    The downside is that not caching means there's more load on my server for the non-logged in user who is legit supposed to be visiting the site. Since this is a development site, I'm okay with that. I would never run this in production on a live site. 

  • Still Not Using Plugins for Security

    Still Not Using Plugins for Security

    Seven years, and my answer to 'do I need a security plugin' is the same.

    Nope.

    What is a Security Plugin?

    A security plugin is not a plugin like 'brute force protect' or 'limit login attempts.'

    A security plugin is like Better WP Security or WordFence or a hundred other plugins that promise to scan your site and let you know what's changed.

    This is not to say that first set of plugins aren't there to make you 'safer,' it's that those are single use, targeted plugins that address a single issue. Limiting login attempts prevents someone from trying the same attack over and over and over until they get in.

    By contrast, all-in-one security plugins try to do everything. They scan your code, your data, and your site. They look for all the possible attack vectors and they try to plugin them.

    What Makes That Secure?

    That's the question I ask people. If a plugin adds in 2-factor authentication, I ask them what it does for them? Password expirations, captchas, file compares etc. Those are all good things, individually, but are they applicable for all people? What, specifically, about those things makes you more secure or not?

    Now. Before you get all shirty with me, I am well aware of what all of those things are good for. With the exception of captchas (which are not accessibility friendly, please stop using them), all of those things make a lot of sense. You expire passwords and, one hopes, require strong passwords to make it harder to break in. But if you have a 2FA setup, do you need to require rotating passwords?

    It’s All About Thinking

    Security plugins stop people from thinking about what's going on.

    I've seen it time and again, people install a plugin that 'makes them safe,' follow the bare minimum of requirements, and then install whatever they want without thinking about it, leave registrations open, and oops, get hacked.

    This is not to say that security plugins don't prevent some of that from happening, but they're often an 'after the fact' solution. That is, usually a security plugin doesn't know to block X until X has been exploited. That's kind of the nature of the beast, though, and why WordPress and many other CMS developers don't release full details on security fixes until they've been out there for a while. They want to give people a chance to upgrade before saying "Hey, y'all who didn't are super vulnerable."

    It’s Also About Speed

    Security plugins also have a tendency to make your site slower. This usually comes up when people have turned on everything that comes with a security plugin. Which goes right back to my point about thinking. The user doesn't think, because they're not yet educated, about the impact of the code on their site.

    To put it simply, the more things you ask WordPress to do before it can load a page, the slower it will be to load a page.

    Pretty cut and dried, right?

    What’s My Answer?

    I don't call this the 'right' answer or even the best one. Not everyone has access to my resources after all, so it's not fair to say "Hire Mika to think for you!" But to me, the best answer is to use the resources you have intelligently.

    Firewalls, from a server side, are all but a requirement to me. If your web host doesn't have one, and most at least have ModSecurity, get a new web host. If you disabled it on your site because a random plugin doesn't work with it, delete the plugin and turn it back on. If you can't move to a new host, look into firewalls like Incapsula or Sucuri. Put something between users and data.

    Site Scanning is a great tool, but don't run it on WordPress. A great example of smart security scanning is VaultPress. It's a remote service that has a copy of all your files and it scans the copy, not your site, for issues. There are other services you can use that scan your site without affecting traffic. Again, web hosts often have tools for this.

    Be Aware of what's going on. Don't just let security be a black box. Make sure you know what kinds of attacks are common on your site. If you're hit by a DDoS, for example, where they're just hammering your site to take it down, a 2FA plugin will not help. If they're trying to log in all the time, a scanner is probably not what you need.

    Lock It Down. If you don't need it, don't use it. If you don't need it on, turn it off. Update regularly. Don't install everything under the sun. 

    Don’t Buy Into FUD

    This is a tricky balance. On the one hand, I want to say 'don't panic if your favourite security plugin of choice tells you everything doomed!' Remember, they're trying to sell you things. But on the other … don't think everything's fine and dandy.

    It's not a simple solution. You have to simultaneously be aware of problems and not overwhelmed by them. You have to learn how to care about which ones are important to you and which are not.

    In a word, you have to think.

    And there is no plugin on the planet that can think for you.

  • The Purpose of a Case Study

    The Purpose of a Case Study

    Back in early December I gave a talk at WordCamp US about a website I built with Tracy. The talk was titled “Lesbians, Damn Lesbians, and Statistics.” You can watch it here:

    Of all the talks I’ve given, I feel it was the least WordPressy of them all. That is, while I did talk about WordPress and why we made choices that we did, and how they relate to the data design, it wasn’t very code heavy. It was, instead, a case study in how and why and what.

    What

    A case study talk is complicated because you have to address what the topic is beyond WordPress. When you start with WordPress, and you’re at a WordPress convention, you don’t have to lay the groundwork. You can jump in and talk about custom post types and taxonomies. At a WordCamp, everyone knows about WordPress, or at least enough to skip over the basics.

    On the other hand, your case study starts by explaining what the reason was that you built the site in the first place. What’s the purpose of the site and what’s the relation to WordPress. You start with the narrative of “This is my story.” And you have to do it fast because next is the why!

    Why

    Once you have explained what the site is about, you have to explain why WordPress. When you’re doing a normal talk about WordPress things, you skip right over this. Everyone knows why WordPress. Because WordPress! But many times we ask ourselves “Is WordPress the best tool for this job?” We ask “Is it the right tool?” So in a case study, you have to build up your case and explain why.

    This is hard, because you already jumped through those hoops to explain to yourself (and any business partners you’re working with) the rationale. Distilling all of that into a third of your talk, which means maybe ten minutes, is not easy. You summarize, you skip over things, and you still have to hit the main points or people won’t be able to make the connections for the next section.

    How

    Finally you have to explain how you did this. If people don’t understand what you did and why, the how becomes meaningless. This is because the brunt of your talk takes place here. This is the real WordPressy stuff, where you talk about how the what and why came together to be this thing. If people can understand the enormity of the data, they can conceptualize your logic.

    If you’ve built everything up before, people will understand “Oh, she couldn’t make death a taxonomy because the overlap would cause problems and become unwieldy.” They’ll follow you when you explain about faceting searches and moving data.

    Because

    The purpose of all this is to draw people in with a cohesive story that puts the code and the concept together. People remember songs because of the rhythm and pattern. They will remember your case study because of the story. We remember stories.

    The purpose of your case study is to tell a good story that people remember and that connects them to your topic and your code.

  • Zap a Daily Tweet

    Zap a Daily Tweet

    Last week I told you how I made a random post of a day. Well, now I want to Tweet that post once a day.

    Now there are a lot (a lot) of possibilities to handle something like that in WordPress, and a lot of plugins that purport to Tweet old posts. The problem with all of them was that they used WordPress.

    There's nothing wrong with WordPress

    Obviously. But at the same time, asking WP to do 'things' that aren't it's business, like Tweeting random posts, is not a great idea. WordPress is the right tool for some jobs, but not all jobs, after all.

    What is WordPress' job is generating a random post and setting a tracker (transient) to store for a day. And it's also WordPress' job to output that data how I want in a JSON format.

    The rest, we turn to a service. Zapier.

    A Service?

    Like many WordPressers, I like to roll my own whenever humanly possible. In this case, I could have added an OAuth library and scripted a cron job, but that puts a maintenance burden on me and could slow my site down. Since I have the JSON call, all I need is 'something' to do the following:

    1. Every day, at a specific time, do things
    2. Visit a specific URL and parse the JSON data
    3. Craft a Tweet based on the data in 2

    I dithered and kvetched for days (Monday and Tuesday) before complaining to Otto on Tuesday night. He pointed out he'd written those scripts. On Wednesday, he and I bandied about ideas, and he said I should use IFTTT. Even using IFTTT's Maker code, though, the real tool needed is one that lets me code logically.

    Zapier

    The concept of IFTTT is just "If This, Then That." If one thing is true, then do another. It's very simple logic. Too simple. Because what I needed was "If this, then do that, and tell another that." There wasn't an easy way I could find to do it with IFTTT so I went to the more complicated.

    Example of what the flow looks like - Trigger is every day, action is GET, final action is tweet

    Three steps. Looks like my little three item'd list, doesn't it?

    The first step is obvious. Set a specific time to run the zap. It's a schedule. The second step is just a web hook saying 'Get the data from URL.' And the third step is aware!

    Showing the example of the tweet, with placeholders for the name and URL

    Pretty nice. If you click on the 'add field' box in the message content (upper right), it knows how to grab the variables from the previous steps and insert them. Which is damn cool.

  • Random Post of the Day

    Random Post of the Day

    I wanted to make a random post of the day. In this case, I wanted it to be a random post of one of two custom post types, and I wanted to output it as JSON for a variety of reasons, including future plans. Like tweeting that post.

    I’ll get to that later.

    Overview

    To do this, I have the following moving parts:

    1. The RESTful routes
    2. The random post
    3. The expiration (i.e. each post lasts a day)

    I’m going to skip over how to make a REST API route. I talked about that earlier in 2017 when I explained how I made the Bury Your Queers plugin.

    What’s important here is actually the random post and spitting out the right content.

    Getting a Random Post

    This is cool. WordPress can do this out of the box:

    $args = array( 
    	'post_type'      => 'post_type_characters',
    	'orderby'        => 'rand', 
    	'posts_per_page' =>'1',
    );
    $post = new WP_Query( $args );
    
    while ( $post->have_posts() ) {
    	$post->the_post(); 
    	$id = get_the_ID();
    }
    wp_reset_postdata();
    
    $of_the_day_array = array(
    	'name'   => get_the_title( $id ),
    	'url'    => get_permalink( $id ),
    );
    

    And at that point all you need to do is have the API return the array, and your final output is like this:

    {"name":"Van","url":"http:\/\/lezwatchtv.com\/character\/van\/"}
    

    This is a simplified version of my code, since in actuality I’m juggling a couple post types (shows or characters), and outputting more data (like if the character is dead or alive). It’s sufficient to prove this point.

    Expirations

    Okay. Now here’s the fun part. If you go to your JSON page now, it’ll show you a new character on every page reload, which is absolutely not what we want. We want this to only update once a day, so we can do this via Transients like this:

    if ( false === ( $id = get_transient( 'lwtv_otd_character' ) ) ) {
    	// Grab a random post
    	$args = array( 
    		'post_type'      => 'post_type_characters',
    		'orderby'        => 'rand', 
    		'posts_per_page' =>'1',
    	);
    	$post = new WP_Query( $args );
    	// Do the needful
    	while ( $post->have_posts() ) {
    		$post->the_post();
    		$id = get_the_ID();
    	}
    	wp_reset_postdata();
    	set_transient( 'lwtv_otd_character', $id, DAY_IN_SECONDS );
    }
    

    But.

    Transients kinda suck.

    Expirations 2.0

    Alright. Let’s do this differently. The server this is on has object caching, and it gets flushed every now and then. While it doesn’t matter if the post is re-randomizes in this case, it’s still not a great practice. So let’s use options!

    // Grab the options
    $default = array (
    	'character' => array( 
    		'time'  => strtotime( 'midnight tomorrow' ),
    		'post'  => 'none',
    	),
    	'show'      =>  array( 
    		'time'  => strtotime( 'midnight tomorrow' ),
    		'post'  => 'none',
    	),
    );
    $options = get_option( 'lwtv_otd', $default );
    
    // If there's no ID or the timestamp has past, we need a new ID
    if ( $options[ $type ][ 'post' ] == 'none' || time() >= $options[ $type ][ 'time' ] ) {
    	// Grab a random post
    	$args = array( 
    		'post_type'      => 'post_type_characters',
    		'orderby'        => 'rand', 
    		'posts_per_page' =>'1',
    	);
    	$post = new WP_Query( $args );
    	// Do the needful
    	while ( $post->have_posts() ) {
    		$post->the_post();
    		$id = get_the_ID();
    	}
    	wp_reset_postdata();
    
    	// Update the options
    	$options[ $type ][ 'post' ] = $id;
    	$options[ $type ][ 'time' ] = strtotime( 'midnight tomorrow' );
    	update_option( 'lwtv_otd', $options );
    }
    

    And now you see my $type variable and why it matters. There’s more magic involved in the real world, but it’s not relevant.