Creating a custom module to store sensitive data in Drupal 10.
Displaying data in a page using a Preact app with Drupal — Part 1
I have been searching of a ways to collaborate other technologies in to Drupal, so this will be an experimental step towards that. At the time of writing this article, I have not started on the rest of the steps mentioned further below and have zero experience with Preact.
This will be a series of articles where we will explore how to utilise Drupal and Preact to display some content into a page.
Before we begin;
It is important that I mention this will be similar to my previous articles in which the purpose is to explore the capabilities of Drupal and diversify use of its functionalities to learn more things along the way.
There can be many different approches to achieve a goal and there will be much simpler and straightforward options out there, however I think the best way to learn the inner workings of Drupal or any tech stack is to dive in deep.
Having said that I do appreciate your input so please feel free to leave any suggestions, thougths in the comments below.
For this exercise, I will be choosing the long way around. And following are the 4 steps that I will be taking.
- The data will be pulled from an API, and I will use the theguardian.com API to fetch them. There’s no particular reason for this, so feel free to use any other suitable endpoint.
- To store the sensitive data such as the API key, I will be creating a custom module called key safe. Rather than using the Drupal Configuration API and saving it to a .yml file, we will use the database to store our data. Yes, we can use the much more sophisticated Key module in Drupal for this, but let’s build a minimal version so that we can learn along the way. There will be a lot of things that can be done to improve our custom module, like encrypting our data, but we will try to build the fundamental groundwork first.
- A content block will be used as a placeholder so that we can inject our Preact app and assets via Drupal libraries, and
template_preprocess_block()
We will see how we can work with caching as well. - Final chapter will be building the Preact app, the app will collect the API key from Drupal using
drupalSettings
and render the data from the API on the page.
Creating the key safe module
The key safe module will consist of a couple of key components apart from the mandatory info.yml
file. Let’s go through them one by one, briefly. I will include the GitHub file links for each item and I will include the files in the order I originally created them.
The schema
Since we are using the database to store key combinations, we will need to create a new table in the database. The schema for the table needs to be defined as well. Ideally, the module should create the table schema when installed and should remove it when being uninstalled. According to the Core database API in web/core/lib/Drupal/Core/Database/database.api.php
under hook_schema()
we can see that for the above to be possible we need to implement a .install
file in the module root.
For this module, we will create a table in called playground_keysafe
and it will include three columns. Firstly iid
as the primary key which will be incremented automatically, then the keysafe_key
a unique key name which will act as an identifier and finally the keysafe_value
which will be the secret or the sensitive piece of data. Ideally, after the user adds a record from the add form, this value will not be visible. Below is the .install
file that I’ve created.
When the module is installed, the following table will be created in the database automatically.
The Permissions
It is good practice to add a permission file and define a new permission for this module, as we wouldn’t want anyone to alter our keys. This will be simple as playground_keysafe.permissions.yml
keysafe key admin:
title: 'Playground Keysafe Admin'
The Routing
We can have a general idea of how the routing should be. We know that there will be two forms, one to add and one to delete.
The form that takes care of adding a record do not need to accept any parameter from the route because we are not planning to reveal the values to the end user once a record is created. However, for learning purpose we will accept a non required argument from the route path. We will make use of the permissions here as well.
playground_keysafe.admin_page:
path: '/admin/config/system/keysafe/{default_key}'
defaults:
_form: '\Drupal\playground_keysafe\Form\KeysafeAdmin'
_title: 'Keysafe Keys'
default_key: ''
requirements:
_permission: 'keysafe key admin'
The delete form should be able to accept an argument ID, and this should be mandatory because we need to use this ID to check in our table for a record before deletion.
playground_keysafe.delete:
path: '/admin/config/system/keysafe/delete/{keysafe_id}'
defaults:
_form: '\Drupal\playground_keysafe\Form\KeysafeDelete'
_title: 'Delete key'
requirements:
_permission: 'keysafe key admin'
The menu link.
In order for the routing paths to be displayed under /admin/config/system we need to add a playground_keysafe.links.menu.yml
file.
playground_keysafe.admin_page:
title: 'Playground Keysafe'
description: 'Stored keys.'
route_name: playground_keysafe.admin_page
weight: 10
parent: system.admin_config_system
The service
Now to the interesting part. We will create a service for our module which will act as a manager for our key value related actions and by creating it as a service we will be able to inject it to our forms through the create()
method. Also, having a service will make it easy when creating Unit tests.
By having this Key safe Manager class as a service, we can separate the Database connections and the related queries from the form.
/**
* Instantiates a new instance of this class.
*
* This is a factory method that returns a new instance of this class. The
* factory should pass any needed dependencies into the constructor of this
* class, but not the container itself. Every call to this method must return
* a new instance of this class; that is, it may not implement a singleton.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The service container this instance should use.
*/
public static function create(ContainerInterface $container);
I will fast forwarding to our KeysafeAdmin.php
file; which is our Add form, to show you what I tried to describe earlier.
Important thing to know here is that if you place your debugger at create()
method and in the __construct()
of our KeysafeAdmin
form class which is an extend of FormBase
you will notice the debugger first stops at the create()
method where it allows us to inject our service in to the form which then will be used in the __construct()
playground_keysafe.services.yml
will be something like this.
services:
playground_keysafe.manager:
class: Drupal\playground_keysafe\PlaygroundKeySafeManager
arguments: ['@database']
Drupal\ban\BanIpManagerInterface: '@playground_keysafe.manager'
The Keysafe manager
From here, we will implement an PlaygroundKeySafeManager
Interface to define the template for the Key safe object.
The interface code can be found here PlaygroundKeyManagerInterface.php
;
By creating an interface, we can choose which methods to implement in the Keysafe manager object PlaygroundKeySafeManager.php
.
All our database queries can be added there.
The forms
Now that we have a much better picture of all these pieces, let’s try to look further ahead and understand how to put them all together.
Add form
We need a form to add the key and value pairs, for that we will need a form. Following is a screenshot of the add form. And I’ve followed the Core’s Ban module as a template. Our add form will be under Configuration > System and the routing will be done through the playground_keysafe.routing.yml
file.
In the form we have the create()
to inject our manager service;
/**
* @inheritDoc
*/
public static function create(ContainerInterface $container) {
return new static($container->get('playground_keysafe.manager'));
}
Which will be used throughout the buildForm()
as $this->keysafeManager->fetchAll();
to populate the table, and in the validateForm()
to check if there is an existing key $this->keysafeManager->keyExists($key)
and finally under submitForm()
to save the record $this->keysafeManager->setKey($key, $value);
Delete form
The delete form will be an extend of ConfirmFormBase
as we only need to confirm before a deletion of a key. The route will look like; /admin/config/system/keysafe/delete/3
The code is pretty straight forward and can be found here. https://github.com/pasankg/d10-playground/blob/develop/web/modules/custom/playground_keysafe/src/Form/KeysafeDelete.php
We utilise our key safe manager service in the delete form, same as we did in the add form.
Wrap up
After when all the things are put together, our module ends up with a fair bit of files and will look something like this;
web/modules/custom/playground_keysafe
├── playground_keysafe.info.yml
├── playground_keysafe.install
├── playground_keysafe.links.menu.yml
├── playground_keysafe.permissions.yml
├── playground_keysafe.routing.yml
├── playground_keysafe.services.yml
└── src
├── Form
│ ├── KeysafeAdmin.php
│ └── KeysafeDelete.php
├── PlaygroundKeyManagerInterface.php
└── PlaygroundKeySafeManager.php
You can find the full module code in my GitHub repository if interested
https://github.com/pasankg/d10-playground/tree/develop/web/modules/custom/playground_keysafe
Here are some screenshots from the final product.
Next
In the next part of this series, we will try to create a content block and attach a dummy library so that we can test out our theory to use the API key from the Key safe module and populate some data from the guardian website.
Resources
- web/core/lib/Drupal/Core/Database/database.api.php
- web/core/lib/Drupal/Core/Extension/module.api.php
- web/core/modules/ban