Tag: Ruby

  • One of my projects, Unbreach, has a database of more than 600 breaches. These come from haveibeenpwned and they are composed of some metadata, a one-paragraph description, and an image. I wanted to improve these with more content, links to articles, tweets, videos, and some content of my own.

    I decided that a good way to do it would be to move them from the app, which resides at app.unbrea.ch, to the marketing website, which is at unbrea.ch, essentially creating them as blog posts. That way after the blog post is automatically created (when haveibeenpwned ads the breach), I can go in and manually edit it in all the WordPress glory. I thought this was going to take me a few hours, not days.

    Hopefully, with this blog post, it’ll only take you hours. I’ll be using Ruby but it should be trivial to translate it to Python, JavaScript, or any other programming language. Writing the code wasn’t the hard part, understanding the WordPress.com world was.

    WordPress has two different APIs that should be able to accomplish this task, one is the XML-RPC API and the other is the REST API. The XML-RCP API depends on a file called xmlrpc.php and it’s strongly recommended you leave this deactivated because it had a lot of security issues. It’s also old, cumbersome, and possibly on the way out. I didn’t want to use it and I don’t think you should either.

    From what I can gather the REST API is what the admin tool uses, so using it sounds like a safe bet. If you are going to be creating blog posts from an unattended background process, as I do, you’ll find your first obstacle when you read about authentication because it just assumes there’s a browser sending cookies.

    Fear not! There are plug-ins that implement other authentication methods and one of those is the Application Passwords plug-in. Which is now discontinued because it’s been merged into WordPress itself in version 5.6. This sounds promising until you realize the feature seems to be missing in WordPress.com.

    If you search how to create an Application Password on WordPress.com you’ll land in the wrong place. WordPress.com users have an Application Password that’s hidden behind the Two-Step Authentication in Security. This is what it looks like:

    If you are here you are in the wrong place

    What’s going on here? Well, WordPress.com has its own API, which is a REST API, and if you talk to support and WordPress.com they’ll point you to that. I wasn’t a fan of that solution because although I want to use WordPress.com, I don’t want to be tied to it. I want to be able to move to WP Engine or something like that whenever I want.

    That API, similar to the REST API, assumes there’s a human interacting through a third-party application, so it’s not great for unattended processes. Authentication works using OAuth2 which for a background job that just needs an API key I find very annoying. It’s doable but annoying. Well… it’s doable until you enable 2FA and then it’s not doable anymore, and that’s why that specific Application Password exists.

    WordPress.com support also told me that the WordPress REST API is enabled only if you are on a business plan or above.

    So… where’s the Application Password for the REST API then? I don’t know if there’s a link to it anywhere, but you get to it by going to https://example.com/wp-admin/profile.php where example.com is the URL of your blog. That is, add /wp-admin/profile.php to it. On WordPress.com’s defense, it was their support that finally pointed me to it. When you go there you’ll see an old-style profile page:

    The correct place to set up an application password to use the WordPress REST API

    The previous Application Password was tied to the user, this one is tied to the user and the site, so if you have more than one site you’ll need to create one per site.

    And that was the hard part. Once I got that application password things just worked. It’s a straightforward and mostly well-documented API. I’ll share my messy code here anyway (sorry, didn’t have time to clean it up).

    In Ruby I’m using a library called Faraday to talk to APIs. The first thing is creating the Farady object that has the metadata that will be used in all the requests:

    auth_token = "#{Rails.application.credentials.wordpress&.username}:#{Rails.application.credentials.wordpress&.app_pass}"
    auth_token = Base64.strict_encode64(auth_token)
    conn = Faraday.new(url: ENV["WORDPRESS_URL"],
      headers: { "Authorization" => "Basic #{auth_token}" }) do |conn|
     conn.request :json
     conn.response :json
    end

    According to Faraday’s documentation, this should have worked as a better way of setting up the authentication details:

    conn.request :authorization,
                 :basic,
                 Rails.application.credentials.wordpress&.username,
                 Rails.application.credentials.wordpress&.app_pass

    but for me it didn’t. It was completely ignored.

    The first thing I need is the id of the category in which these posts will end up. This is very important because they appear on a separate page about breaches and not on the blog and that’s achieved with categories:

    response = conn.get("/wp-json/wp/v2/categories", {search: "Breach", _fields: %w[id name]})
    if response.status != 200
      raise "Unexpected response #{response.status}: #{response.body}"
    end
    category = response.body.find { |category| category["name"] == "Breach" }

    Now, if the category doesn’t exist, I want to create it:

    if category.nil?
      response = conn.post("/wp-json/wp/v2/categories") do |req|
        req.body = {name: "Breach"}
      end
      if response.status != 201
        raise "Unexpected response #{response.status}: #{response.body}"
      end
      category = response.body
    end

    Then I needed to do the same with tags. In my case, the tags were in a field called data_classes and the code for getting the id of the tag and creating it if it doesn’t exist is very similar:

    tags = data_classes.map do |data_class|
      response = conn.get("/wp-json/wp/v2/tags", {search: data_class, _fields: %w[id name]})
      if response.status != 200
        raise "Unexpected response #{response.status}: #{response.body}"
      end
      tag = response.body.find { |tag| tag["name"] == data_class }
      if tag.nil?
        response = conn.post("/wp-json/wp/v2/tags") do |req|
          req.body = {name: data_class}
        end
        if response.status != 201
          raise "Unexpected response #{response.status}: #{response.body}"
        end
        tag = response.body
      end
      tag
    end

    And finally, we can create the post. I create the content as an HTML snippet which causes WordPress to interpret it as classic content, not as blocks. But that’s fine because it renders well and the first time I edit one of those posts converting them to blocks is two clicks and works perfectly for this simple content.

    content = <<~CONTENT
      <p>#{description}</p>
      <p><!--more--></p>
      <p>Accounts breached: #{pwn_count}</p>
      <p>Breached on: #{breach_date&.strftime("%B %d, %Y")}
      <p>Exposed data: #{data_classes.to_sentence}</p>
      <p>Domain: #{domain}</p>
      <p>Added on: #{added_date.strftime("%B %d, %Y")}</p>
    CONTENT
    response = conn.post("/wp-json/wp/v2/posts", {
      title: title,
      content: content,
      excerpt: description,
      status: "publish",
      categories: [category["id"]],
      tags: tags.map { |tag| tag["id"] },
      date_gmt: (breach_date.to_time(:utc) + 12.hours).iso8601.to_s,
      template: "breach-template",
      ping_status: "closed"
    })
    if response.status != 201
      raise "Unexpected response #{response.status}: #{response.body}"
    end
    post = response.body

    At this point, I wasn’t done. I wanted these posts to have the image associated with the breach (the logo of the company breached). The first step was downloading it which was a trivial one-liner:

    logo_request = Faraday.new(url: logo_path).get("")

    In that code, logo_path is actually a full URL of the file.

    To create media items in WordPress, I needed to encode the post as multi-part, so I ended up creating a separate Faraday object for that:

    multipart_conn = Faraday.new(url: ENV["WORDPRESS_URL"],
      headers: {"Authorization" => "Basic #{auth_token}"}) do |conn|
      conn.request :multipart
      conn.response :json
    end

    It should have been possible to use a single Faraday object for all requests, but when you specify multipart, you need to take care of encoding the JSON requests yourself and adding them as one of the parts. This is where I got lazy and just moved on with my work.

    The code for creating the image in WordPress is this:

    extension = File.extname(logo_path)
    file_name = "#{name.underscore.tr("_", "-")}#{extension}"
    content_type = if extension == ".png"
      "image/png"
    else
      raise "Unexpected extension #{extension}"
    end
    media = multipart_conn.post("/wp-json/wp/v2/media", {
      date_gmt: (breach_date.to_time(:utc) + 12.hours).iso8601.to_s,
      status: "publish",
      title: title,
      comment_status: "closed",
      ping_status: "closed",
      alt_text: "Logo for #{title}",
      caption: "Logo for #{title}",
      description: "Logo for #{title}",
      post: post["id"],
      file: Faraday::Multipart::FilePart.new(StringIO.new(logo_request.body), content_type, file_name)
    })

    In reality, 100% of the images are PNG so I was ok with such a simplistic approach. When creating the FilePart I wrapped logo_request.body in a StringIO because it already contained the binary data of the image. If you have a local file you can just pass the path to FilePart.new and it just works.

    And now that I had the image, I could set it as the featured image for the post I created earlier:

    response = conn.post("/wp-json/wp/v2/posts/#{post["id"]}", {
      featured_media: media.body["id"]
    })
    if response.status != 200
      raise "Unexpected response #{response.status}: #{response.body}"
    end

    The reason why I didn’t create the image before creating the post was so that I could pass the post id to the image and thus the image would be connected to the post. I’m not sure how useful that is.

    And that’s all.

    I wonder if this code should be put in a gem and made reusable. WordPress points to the wp-api-client gem as the Ruby solution, which is read-only and abandoned. There’s also wordpress_v2_api, but I wasn’t a fan of the API (it’s almost like using HTTP directly), it hasn’t been touched in 6 years and I don’t believe it supports writing. I’m half tempted to fork wp-api-client, but does anybody else care, or is it just me? Please leave a comment if this is something you want to use.

  • I just figured out how to use Font Awesome 6 in a Rails 7 project that uses importmaps. I’m not entirely sure why this works and why some of the workarounds are needed, but my googling yielded no results when I was searching so hopefully here I’ll be saving the next person some time.

    If you search Rubygems for gems with the name “font awesome” you’ll find quite a few but I didn’t like any of them. They all use the font version of the icons, instead of the SVG, or they are very outdated, or they expect you to use SCSS, which I’m not using at the moment. But ultimately, the team at Font Awesome maintains the NPM packages and we should use those directly, not re-wrap packages that will always be out of date.

    For me, using NPM packages directly was higher priority than using importmaps. That’s how strongly I feel about it. I would have installed Webpacker to use Font Awesome’s main package.

    I managed to make this work, but if I’m frank, I’m not 100% sure why the workarounds are needed, so if you have any insights about it or how to improve this, please drop a comment.

    Font Awesome’s documentation says you should install the fontawesome-free package:

    npm install --save @fortawesome/fontawesome-free

    Instead we are going to pin that package, but also some of the dependencies we need later:

    ./bin/importmap pin @fortawesome/fontawesome-free \
                        @fortawesome/fontawesome-svg-core \
                        @fortawesome/free-brands-svg-icons \
                        @fortawesome/free-regular-svg-icons \
                        @fortawesome/free-solid-svg-icons

    This adds the following lines to your importmap.rb:

    pin "@fortawesome/fontawesome-free", to: "https://ga.jspm.io/npm:@fortawesome/[email protected]/js/fontawesome.js"
    pin "@fortawesome/fontawesome-svg-core", to: "https://ga.jspm.io/npm:@fortawesome/[email protected]/index.es.js"
    pin "@fortawesome/free-brands-svg-icons", to: "https://ga.jspm.io/npm:@fortawesome/[email protected]/index.es.js"
    pin "@fortawesome/free-regular-svg-icons", to: "https://ga.jspm.io/npm:@fortawesome/[email protected]/index.es.js"
    pin "@fortawesome/free-solid-svg-icons", to: "https://ga.jspm.io/npm:@fortawesome/[email protected]/index.es.js"

    Then Font Awesome’s documentation says you should add these lines to your code:

    <script defer src="/your-path-to-fontawesome/js/brands.js"></script>
    <script defer src="/your-path-to-fontawesome/js/solid.js"></script>
    <script defer src="/your-path-to-fontawesome/js/fontawesome.js"></script>

    Which might make you think this is a good idea:

    <script defer src="https://ga.jspm.io/npm:@fortawesome/[email protected]/js/brands.js"></script>
    <script defer src="https://ga.jspm.io/npm:@fortawesome/[email protected]/js/solid.js"></script>
    <script defer src="https://ga.jspm.io/npm:@fortawesome/[email protected]/js/fontawesome.js"></script>

    But it doesn’t work. It fails with this error:

    Here I’m a bit confused. How come it fails with that error? Any ideas?

    What did work was editing app/javascript/application.js and adding::

    import {far} from "@fortawesome/free-regular-svg-icons"
    import {fas} from "@fortawesome/free-solid-svg-icons"
    import {fab} from "@fortawesome/free-brands-svg-icons"
    import {library} from "@fortawesome/fontawesome-svg-core"
    import "@fortawesome/fontawesome-free"
    library.add(far, fas, fab)

    I can’t help but feel that there’s a function or method in fontawesome-free that I could call that would do all the setup automatically with less imports and less library building, but I couldn’t find it yet.

  • When I create a new Rails project I like to have a robust seeds that can be used to quickly bootstrap development, testing and staging environments to interact with the application. I think this is critical for development speed.

    If a developer creates a feature to, for example, connect two records together, you just want them to fire up the application and connect two records to see it work. You don’t want them spending time creating the records because that’s a waste of time, but also, because all developers end up having different subsets of testing data and generally ignoring everything that’s not included in them. It’s better to grow a set of testing data that’s good and complete.

    One of the problem I run into is that generating testing data, or sample data, doesn’t happen often enough and changes in the application often break it. Because of that, I wrote this simple test:

    RSpec.describe "rake db:seed" do
      it "runs" do
        Imok::Application.load_tasks if Rake::Task.tasks.none? { |t| t.name == "db:seed" }
        ENV["verbose"] = "false"
        Rake::Task["db:seed"].invoke
      end
    end

    It doesn’t have any assertions, but with just a few lines of code it probably covers 90% of seed data creation problems, that generally result in a crash. I would advice against having assertions here, as they may cost more time than the time they’ll save because sample data evolves a lot and it’s not production critical.

  • I like my models to be printed nicely, to make the class of the model as well as the id and other data available, so, when they end up in a log or console, I can now exactly what it is. I’ve been doing this since before Rails 3 and since Rails projects now have an ApplicationRecord class, it’s even easier.

    On my global parent for all model classes, ApplicationRecord I add this:

    def to_s(extra = nil)
      if extra
        extra = ":#{extra}"
      end
      "#<#{self.class.name}:#{id}#{extra}>"
    end

    That makes all records automatically print as:

    <ModelName:id>

    For example:

    <User:123>

    which makes the record findable.

    But also, allows the sub-classes to add a bit of extra information without having to re-specify the whole format. For example, for the User class, it might be:

      def to_s
        super(email)
      end

    so that when the user gets printed, it ends up being:

    <User:123:[email protected]>

    which I found helps a lot with quickly debugging issues, in production as well as development environments.

  • Rails 6 shipped with a very nice feature to keep encrypted credentials on the repo but separate them by environment, so you can have the credentials for development, staging and production, encrypted with different keys, that you keep safe at different levels.

    For example, you might give the development key to all developers, but the production keys are kept very secret and only accessible to a small set of devops people.

    You edit these credentials by running:

    bundle exec rails credentials:edit --environment development

    for the development credentials, or

    bundle exec rails credentials:edit --environment production

    for production ones. You get the idea.

    When you run it, if the credentials don’t exist, it generates a key. If they exist, you need to have the keys. After decrypting, it runs your default editor and on Windows, this is the error I was getting:

    No $EDITOR to open file in. Assign one like this:
    
    EDITOR="mate --wait" bin/rails credentials:edit
    
    For editors that fork and exit immediately, it's important to pass a wait flag,
    otherwise the credentials will be saved immediately with no chance to edit.

    It took me a surprisingly long time to figure out how to set the editor on Windows, so, for me and others, I’m documenting it in this post:

    $env:EDITOR="notepad"

    After that, running the credentials:edit command works and opens Notepad. Not the best editor by far, but for this quick changes, it works. Oh, and I’m using Powershell. I haven’t run cmd in ages.

  • For those cases in which there can be one and only one record on the database with certain fields and I don’t just want to get the first one and silently get the wrong one. I want to make sure there’s one and only one, so, I wrote this little extension to ActiveRecord that does exactly that:

    [code lang=”ruby”]
    module ActiveRecordExtension
    extend ActiveSupport::Concern

    class_methods do
    def one_and_only
    records = limit(2).all.to_a
    if records.count &gt; 1
    raise "#{self} generated more than one record when expecting only one."
    else
    records.first
    end
    end

    def one_and_only!
    one_and_only.tap do |record|
    if record.nil?
    raise "#{self} didn’t generate any records."
    end
    end
    end
    end
    end

    ActiveRecord::Base.send(:include, ActiveRecordExtension)
    [/code]
    The first method, one_and_only, will raise an exception if there’s more than one item but it’ll return null if there aren’t any. one_and_only! will fail if there isn’t exactly one and only one record in the database.

    If you don’t know why I’m calling this The Highlander query, you should go and watch Christopher Lambert’s masterpiece.

  • If you are using Simple Form and Bootstrap 3, you probably initialized your app with something similar to:

    [code language=”bash”]
    rails generate simple_form:install –bootstrap
    [/code]

    which adds a file called simple_form_bootstrap.rb to your application with many customizations that make Simple Form output nicely formatted Bootstrap 3 forms.

    For Call to Buzz, an app I’m building, I had several booleans to display, like this:

    Call to Buzz booleans

    My problem was that I wanted a label to explain what those booleans are, something like this:

    Call to Buzz booleans with label

    I achieved that by creating a wrapper that’s a mix between :horizontal_boolean and horizontal_radio_and_checkboxes which I called horizontal_boolean_with_label and looks like this:

    [code language=”ruby”]
    config.wrappers :horizontal_boolean_with_label, tag: &quot;div&quot;, class: &quot;form-group&quot;, error_class: &quot;has-error&quot; do |b|
    b.use :html5
    b.optional :readonly

    b.use :label, class: &quot;col-sm-3 control-label&quot;

    b.wrapper tag: &quot;div&quot;, class: &quot;col-sm-9&quot; do |wr|
    wr.wrapper tag: &quot;div&quot;, class: &quot;checkbox&quot; do |ba|
    ba.use :label_input
    end

    wr.use :error, wrap_with: {tag: &quot;span&quot;, class: &quot;help-block&quot;}
    wr.use :hint, wrap_with: {tag: &quot;p&quot;, class: &quot;help-block&quot;}
    end
    end[/code]

  • Rails 4.1 introduced the concept of secrets.yml, a file in which you store all the credentials for your app, separated by environment, so for example, development can talk to Test Stripe and production to Live Stripe. Furthermore, this file is capable of picking up environment variables which allows you to divorce credentials from code. Not properly separating credentials from code recently cost Uber the leakage of 50,000 driver names and license numbers.

    At Qredo we are very strict about handling credentials, including the ones for SMTP, which in Rails projects are normally stored in config/environments/development.rbconfig/environments/production.rb, etc. Trying to read Rails.application.secrets from those files doesn’t work, because they are loaded before the secrets, so, we came up with this alternative solution.

    The environment files that need to use SMTP for delivering email have this common configuration:

    config.action_mailer.delivery_method = :smtp
    config.action_mailer.smtp_settings = {
      address: "smtp.example.com",
      port: 587,
      authentication: "plain",
      enable_starttls_auto: true
    }

    and then, on config/initializers/email.rb we finish configuring our SMTP credentials by reading it from secrets.yml:

    if ActionMailer::Base.delivery_method == :smtp
      ActionMailer::Base.smtp_settings[:domain]    = Rails.application.secrets.smtp_domain
      ActionMailer::Base.smtp_settings[:user_name] = Rails.application.secrets.smtp_user_name
      ActionMailer::Base.smtp_settings[:password]  = Rails.application.secrets.smtp_password
    end
    

    In config/secrets.yml you need set those credentials:

    development:
      secret_key_base: 123...
      smtp_domain: example.org
      smtp_user_name: [email protected]
      smtp_password: <%= ENV["SMTP_PASSWORD"] %>
    
    test:
      secret_key_base: c7f3f62597b14c7287d75c239168fd3a89d3a6cb51beb909679c2609e912aaa3ca0a0aa5df2e191648baed6fe49698527a75dd265d222697576405971f478c98
    
    staging:
      secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
      smtp_domain: example.net
      smtp_user_name: [email protected]
      password: <%= ENV["SMTP_PASSWORD"] %>
    
    production:
      secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
      smtp_domain: example.com
      smtp_user_name: [email protected]
      smtp_password: <%= ENV["SMTP_PASSWORD"] %>

    And that’s it! Enjoy bringing SMTP credentials into Rails >4.0 secrets management.

  • This is an update of an old post of similar name but for a newer version of Devise and with better design decisions. The old post was for Devise 1.0.8, this one covers 4.0.0

    I was trying to have a single page with both sign in and sign up forms with Devise 4.0.0 but this applies to whenever you want to show log in or registering individually or together anywhere on your site other than the views and controllers Devise creates for you.

    For my task, I created a custom controller for it with a single new action as the create actions would be in the respective already existing Devise controllers. Something like this:

    class Users::SessionsOrRegistrationsController < ApplicationController
      def new
      end
    end

    And then I created a new.html.erb (actually, new.html.haml, but I digress) that contained both log in and sign up one after the other. Something like this:

    <h2>Sign up</h2>
    
    <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
      <%= devise_error_messages! %>
    
      <div class="field">
        <%= f.label :email %>
        <%= f.email_field :email, autofocus: true %>
      </div>
    
      <div class="field">
        <%= f.label :password %>
        <% if @minimum_password_length %>
          <em>(<%= @minimum_password_length %> characters minimum)</em>
        <% end %>
        <%= f.password_field :password, autocomplete: "off" %>
      </div>
    
      <div class="field">
        <%= f.label :password_confirmation %>
        <%= f.password_field :password_confirmation, autocomplete: "off" %>
      </div>
    
      <div class="actions">
        <%= f.submit "Sign up" %>
      </div>
    <% end %>
    
    <hr/>
    
    <h2>Log in</h2>
    
    <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
      <div class="field">
        <%= f.label :email %>
        <%= f.email_field :email, autofocus: true %>
      </div>
    
      <div class="field">
        <%= f.label :password %>
        <%= f.password_field :password, autocomplete: "off" %>
      </div>
    
      <% if devise_mapping.rememberable? %>
        <div class="field">
          <%= f.check_box :remember_me %>
          <%= f.label :remember_me %>
        </div>
      <% end %>
    
      <div class="actions">
        <%= f.submit "Log in" %>
      </div>
    <% end %>

    I actually ended up creating two _form partials and including them. In either case, when you try to render those views, you’ll get errors about some missing methods. You need to provide those as helper methods so my controller actually looks like this:

    class Users::SessionsOrRegistrationsController < ApplicationController
      def new
      end
    
      private
    
      def resource_name
        :user
      end
      helper_method :resource_name
    
      def resource
        @resource ||= User.new
      end
      helper_method :resource
    
      def devise_mapping
        @devise_mapping ||= Devise.mappings[:user]
      end
      helper_method :devise_mapping
    
      def resource_class
        User
      end
      helper_method :resource_class
    end

    And now it works.

  • There’s a gem called bundler-audit that checks whether any of the gems in your project have open security advisors against them. A year or so ago there was an infamous month in which Rails itself got three of those. It was terrible and I think bundler-audit is a good idea. My only problem with it is having to remember to run it: it just won’t happen. I need to run it automatically and an easy way to do that is to run it as part of my tests.

    Unfortunately, bundler-audit doesn’t make it easy. It’s designed for the command line and that’s it, but these days it’s easier than a year ago and I recommend everybody to add them to their integration tests. Here’s how we do it at Watu:

    require "test_helper"
    require "bundler/audit/database"
    require "bundler/audit/scanner"
    
    class SecurityTest < ActionDispatch::IntegrationTest
      should "not have any vulnerable gems" do
        Bundler::Audit::Database.update!
        scanner = Bundler::Audit::Scanner.new
        scanner.scan do
          raise "There are vulnerable gems in your Gemfile. Run bundle-audit check to know more"
        end
      end
    end
    

    I don’t try to show the vulnerable gems because I found those methods to not be easily reusable and I didn’t want to copy them because they look like they might change at any moment. It’s not a big problem, if something is wrong, you should run bundle-audit check anyway.