Skip to content
El RIDO edited this page Dec 4, 2023 · 35 revisions

If you just want to contribute a small little change, fixing a typo or updating a translation file, just submit a pull request with your change and we will help you out with any failed automated checks.

Introduction

This fork of the original ZeroBin of Sebsauvage was refactored into an object oriented structure, both in the PHP and JavaScript logic. Unit testing was introduced to ensure overall quality is maintained, even when core functionality is refactored or new features are introduced.

Here below is some background on the principles that are used to ensure the project stays maintainable long term and the automation we have set up to keep and improve the quality of the project.

Getting started

An easy way to get started developing PrivateBin is using devcontainers, which are containers having everything installed you usually need for developing directly in your browser.

More information on how the IDE works, here.

Design Goals

Basic Features

The project aims to provide a simple pasted text sharing service. By default the installation should be reasonably secure and cover basic features. The basic use case is for a web site visitor to paste copied text into the large text area and press a single button to store this to the server and gets presented with a URL that may be shared to view the stored text. Another user may visit such a URL and will be presented the shared text, without requiring any interactions other then visiting the site. After a while, the stored text gets removed from the server.

The server administrator has the option to change some of the defaults of the service, for example the default duration the text gets stored, the look and feel via templates, etc. For the advanced formatters, the preview feature allows to review how they will display the pasted text before it is stored.

When adding new features, consider if these make sense in the default use case. If they require additional interactions by the user, then add them in such a way that they are options in the menu and avoid using blocking modal windows. If the features may not be suitable for all installations, make them configurable so they can be disabled if not needed. If they clutter the interface too much, add them disabled by default.

Ease of installation

While it is acceptable to require additional tooling for developers to change the project (i.e. to re-generate SRI-hashes, run unit tests, update and install libraries), the main audience of this project are web server administrators that want to install the software. The primary supported installation method is to use a the release archive generated using the git archive command. We do not expect them to have to take additional steps except unpacking (although they should secure their installations and may want to configure the service a bit). Therefore all libraries and dependencies need to be committed to the repository. The .gitattributes we can remove the things from the archive that are required for the tooling, but aren't needed for the installation.

Additional benefits of this method are that developers can use git to track the latest changes on a development web server pointing to a cloned git repository and that we have accountability, which version of a library was used in any given PrivateBin commit.

Code Style

As usual, the rule of thumb is to keep the code style as consistent as possible, so that it stays easy to read and therefore to maintain.

To make it simple to adopt the projects code style, we provide configurations for various tools. You just have to install the necessary tools in your IDE and they should use these automatically.

Code Structure

The index.php only loads an autoloader (lib/auto.php) and starts the application by instancing the PrivateBin singleton.

The main code or controller is found in lib/PrivateBin.php.

The data storage abstractions are found in the lib/Data/ folder.

The data models for pastes and comments are found in the lib/Model/ folder.

The view is in lib/View.php and uses PHP template files located in the tpl/ folder.

Any feature that not every server administrator might want to offer should be implemented with configuration options in the file cfg/conf.ini, default settings have to be added to lib/Configuration.php. See below for a step by step guide on how to introduce these.

More details can be found in the API documentations for the PHP and JavaScript code.

Paste Format

Summary: Changes in the paste format may be done, but without removing support for reading all older paste formats.

The pastes and each of their comments are essentially a file format, regardless if stored as JSON files on disk or as a row in a database table. Due to the nature of the application, the fact that the server can't decrypt their contents, any changes to the encrypted payload needs to be done in a backwards compatible fashion on the read side, while writing may be limited to the latest PrivateBin format plus the legacy ZeroBin format (see caveat section below).

Consider the following use cases that we need to cover:

  • A paste is created in format A. Later, an upgraded PrivateBin version writes a paste in format B, but still can display (read) the old format A paste.
  • A paste is created in format A, discussion is enabled for it. Later, an upgraded PrivateBin version adds a comment in format B. It can display a paste including all its discussion comments, regardless which version each of the elements were created in.

While we could introduce a mechanism that can read an old paste and convert it to the latest format and write it back to the server, this means that any visitor of a paste can therefore also change the content. The server has no way of detect that as the content is encrypted. For the client this is extra work, still requiring it to be able to read all old formats and no benefit. Therefore a write once, read only, zero update and delete on expiration (on first view, in case of burn-after-reading) or by deletetoken are the only mechanisms implemented.

Caveat: PrivateBin incorporates the legacy Base64.js 1.7 library for supporting upgrades from legacy ZeroBin installations. If that mode is chosen, then the writes also are done in this legacy format.

Due to the issues experienced with the incompatible change from the legacy ZeroBin format, it is highly recommended that any future changes of the format are done in an easily detectable fashion, preferable by incrementing the formats version number. All formats need to be documented, both in code with format specific unit tests and here in the Wiki, so they can be implemented by third party projects like the CLI clients.

Server Design

The server part of PrivateBin is currently implemented in PHP. Since the paste and comments are not decypherable to it, it essentially becomes a pure storage service. It could be rewritten in other languages, reusing the existing JS logic for the webinterface. Similarly, as long as the JSON REST-API is preserved, the client logic does not notice if the pastes are stored in files or in a database, as the current implementation shows. Due to this, we also offer JSONP interfaces, which allow discovery of server capabilities by the client.

The overarching goal is that the client logic can be run independently of the server. Consider these use cases:

  • PrivateBin server and client being hosted on a single installation (the default).
  • CLI clients may upload pastes to all compatible servers.
  • A Webinterfaces hosted on server A could read pastes from server B and clone it to server C.

API & shared resources

The lowest common denominator between PHP and Javascript is the JSON format. Hence the paste format, the REST-API and the translation source files (used by both the server for static messages and HTML template and the webinterface for translation of dynamic user interface elements) all use JSON.

Continuous Integration

We have set up various automated checks performed on each commits to the main PrivateBin repository and submitted pull requests. While these can occasionally be a bit obnoxious, they help us detect at what point problems or quality issues are introduced into our code base before the overall quality deteriorates too much, requiring dedicated cleanup efforts that block us in moving on with the project.

Since this is a free & open source project maintained by enthusiasts in their spare time, it is much more difficult to get these motivated to do long, boring cleanups then keep working on fun new features and fixing some small code smells on the side.

StyleCI - Code Style

As all projects, we do have some specific styles that we follow. The most important rule is, keep the code style consistent. You personally may like to indent differently than we do here, but changing it on some lines and not others will make the code harder to maintain for everyone involved.

StyleCI checks the code style, from indentation (4 spaces in PHP & JS, 8 character sized tabs in HTML parts of the template files) to PHP (PSR-4) and Javascript syntax. Some semantics we try to keep the same in both languages, like spaces between formula elements and the bracket style.

Scrutinizer - Code Review

This CI tool can detect more complex patterns in code changes and they present these in a quality score per file over time.

Amongst detected issues are known security risks, overly complex methods that may need refactoring, duplication in need of DRYing. Unit test coverage is also considered, so please consider including tests covering all new branches in any added logic. Simpler code is automatically higher scored.

Github Actions

We use Github Actions to run PHP unit tests (using phpunit) on all supported PHP versions, ensuring no changes requiring raising the minimum version accidentally. In general we try to support even older PHP versions, as long as we don't absolutely need a certain versions feature for core functionality.

Actions also runs our JS unit tests (using mocha / jsVerify). These focus on functional tests as they run in nodejs and an emulated DOM environment. At this time we don't run browser compatibility tests.

Tools & workflow

Of course every developer will use the tools they like and know best.

Unit Tests

Details on the unit testing frameworks for PHP and JavaScript can be found in the projects unit testing document.

We also provide a Docker container including all the tools.

Creating new configuration options

  1. Add your config option to cfg/conf.sample.php.
  2. Add the default value for that config option to lib/Configuration.php in the $_defaults array.
  3. If you want to use the config option in a template, you have to add it to the _view function in lib/PrivateBin.php. You can then just use the variable in the template file to conditionally include or exclude HTML code.
  4. If you want to use it in other parts of the PHP code, you can usually query the config option via the getKey method of the Configuration object.

A good example of a change, where an option was added is this one.

Data Model

If you want to create your own data models, you might want to know how the arrays, that you have to handle, need to look like:

public function create($pasteid, $paste)
{
    $pasteid = substr(hash('md5', $paste['data']), 0, 16);

    $paste['data']                      // text
    $paste['meta']['postdate']          // int UNIX timestamp
    $paste['meta']['expire_date']       // int UNIX timestamp
    $paste['meta']['opendiscussion']    // true (if false it is unset)
    $paste['meta']['burnafterreading']  // true (if false it is unset; if true, then opendiscussion is unset)
    $paste['meta']['formatter']         // string
    $paste['meta']['attachment']        // text
    $paste['meta']['attachmentname']    // text
}

public function createComment($pasteid, $parentid, $commentid, $comment)
{
    $pasteid  // the id of the paste this comment belongs to
    $parentid // the id of the parent of this comment, may be the paste id itself
    $commentid = substr(hash('md5', $paste['data']), 0, 16);

    $comment['data']                    // text
    $comment['meta']['nickname']        // text or null (if anonymous)
    $comment['meta']['vizhash']         // text or null (if anonymous)
    $comment['meta']['postdate']        // int UNIX timestamp
}

Composer

When you want to include or update the PHP dependencies of PrivateBin, please execute composer like this:

composer install --no-dev --optimize-autoloader

Note that the .gitignore will exclude all non-PHP files in the vendor dir when you create a commit.

Subresource Integrity for Javascript resources

Because we implemented Subresource Integrity (SRI), the SRI hashes in the templates need to be regenerated after each change of any JavaScript file. To make this easy, we implemented a mechanism for this into the PHP unit testing bootstrap process. After changing any JavaScript file, update the SRI hashes as follows:

  1. Run the PHP unit tests, as described above
  2. Deploy the updated JavaScript file(s) and the updated templates to your webserver

If you do not have php runtime in your local development environment, there are 2 other ways to generate SRI:

  1. You may run openssl dgst -sha512 -binary js/privatebin.js | openssl enc -base64 from the project main folder to generate SRI. This method is preferred when your development environment is Linux based. You may find this method does not work properly on Windows even if you have WSL installed, this is because your git configuration maybe converting line ending between windows style (CRLF) and Unix style (LF).
  2. You may run await fetch('/js/privatebin.js').then(r => r.blob()).then(b => b.arrayBuffer()).then(t => crypto.subtle.digest('SHA-512', t)).then(hab => console.log(btoa(String.fromCharCode(...new Uint8Array(hab))))) directly from your browser web console to generate SRI, after you navigate to your server with modified code deployed. This method is guaranteed to work across the platforms but require your own valid deployment available.

Of course you can also manually adjust the hashes in the template files. For more information how to do this have a look at this article or e.g. use some online generator.