8 – Set and read data in session between anonymous and authenticated user

On my Drupal 8 site, when an anonymous user “A” visit this link: https://www.example.com/page-1?data=12345678, I want to store the data value in a session and save it later to the user account under field_user_data when this same user “A” login/signup to the site.

Note: The query data will be unique for every single user and will not be used twice ever.

I thought the best way to go is by using the data value in a session and use it later when the same anonymous user becomes authenticated on the site.

So on site load and when user “A” is using the site as anonymous, I am saving the data value in a session using:

// Get query values from url.
$request_query = Request::createFromGlobals();
$data = $request_query->query->get('data');

// Save the "$data" in a session variable.
$tempstore = Drupal::service('user.private_tempstore')->get('my_module');
$tempstore->set('data', $data);

and later, in another php file but within the same module, when the anonymous user “A” authenticate to the site, I am saving the session value to his user account as shown below:

// Get 'data' Session value
$tempstore = Drupal::service('user.private_tempstore')->get('my_module');
$data = $tempstore->get('data');

// Set 'data' value to user account.
$authenticated_user->set('field_user_data', $data);

// Save
$authenticated_user->save();

The problem is $data session is shared between all users and not being unique per user while $data value must be unique for every single user.

What is wrong with my code ?
Using cookies will be a better approach for my use case ?

8 – Disable auto logout for authenticated user until they manually /user/logout

When a session is deleted by the session garbage collector, authenticated users are logged out, and the contents of the user’s $_SESSION variable is discarded.

However, I want to never automatically logout users until they choose to do so by manually clicking on /user/logout link.

From services.yml file at /public_html/site-name/web/sites/default, I can change this behavior using below code:

# Set session lifetime (in seconds), i.e. the time from the user's last
# visit to the active session may be deleted by the session garbage
# collector. When a session is deleted, authenticated users are logged out,
# and the contents of the user's $_SESSION variable is discarded.
# @default 200000
gc_maxlifetime: 200000

but also a google search for a remember me option redirected me to Persistent Login module.

So my 2 related questions are:

1) Setting gc_maxlifetime to 0 will disable the time of session expiration and users will not be logged out until they manually do so ?

2) If yes, can gc_maxlifetime: 0 setting replace Persistent Login module in order to achieve what i am asking for ?

Thank you

secure boot – Time Authenticated EFI Variable

I’m setting up custom secure boot keys on an Asus Z87I-Deluxe motherboard. On other computers I’ve setup with secure boot, I’ve been able to either write the PK, KEK, and DB keys into the EFI variables via the /sys/firmware/efi/efivars filesystem or I’ve been able to load them in via the BIOS menu. I’ve always used DER encoded x509 certificates.

On this board, I’m able to write to the PK, KEK, and DB keys in both manners and read the variables back after a reboot. However, the computer will not boot a signed UEFI image. It doesn’t give any errors, it just drops you back at the UEFI menu each time you select the boot device. I’ve verified the UEFI signature with sbverify successfully.


$ for file in PK KEK DB; do                                                                                                     
    sudo openssl x509 -inform DER -in /root/secure-boot/$file.cer -outform PEM 
      | sudo openssl verify -CAfile /root/secure-boot/$file.crt
  done
stdin: OK
stdin: OK
stdin: OK

$ efibootmgr --verbose
BootCurrent: 0000
Timeout: 1 seconds
BootOrder: 0000,0001,0002,0003
Boot0000* linux HD(1,GPT,12684b61-8989-4df2-bc61-c2d7c6d640d0,0x800,0x64001)/File(EFIlinux.efi)


$ sudo sbverify --cert=/root/secure-boot/DB.crt /boot/EFI/linux.efi
warning: data remaining(24749608 vs 24759224): gaps between PE/COFF sections?
Signature verification OK

The user manual for the Z87I mentions that when loading the variables in via the BIOS menu, they must be formatted as “a UEFI variable structure with time-based authenticated variable” (section 8.2.2, I presume?).

I’ve never seen another BIOS that requires such a thing and I’m not aware of any software that can generate the required format.

Am I interpreting this correctly? I’ve tried writing straight DER and PEM files without any success.

files – How can I restrict access to a static HTML page served from a subfolder of my site to authenticated users only?

I have a Drupal 8 site that has all roles and permissions necessary for authenticated users. One of the new requirements is to serve a static microsite (like a campaign site, totally different design compare to Drupal site) which is a single folder with all the static assets (HTML, CSS, JS , images). We have to serve this site under the same domain drupal is hosted and should be accessed ONLY by authenticated users.

I tried to host it under sites/default/files/ directory, but it means it is public and anyone can access it.

Authentication – What are the disadvantages of a reverse OTP flow, where OTP is visible on a website and re-entered on an authenticated mobile client?

I want to authenticate a user on a third party website (this is more or less an OAuth flow so that website can then take actions on behalf of my user, e.g. get some of their data). My user is already authenticated in my mobile app.

Usually, multiple websites implement OTP so that I enter a code that is generated on a mobile device (SMS message, authentication app or a native app).

I want to reverse this flow, i. H. Generate an OTP on a website and allow the user to enter it in a mobile app in which they are already authenticated.

A third party website may open a new child window where OTP is downloaded directly from my server using an API.

Question: Are there any? disadvantage to this river?

PS
I see the following advantage: In the normal process, a fake third-party website would "steal" my OTP. In my workflow, the actual comparison is only carried out after entering the code in an authenticated environment, even if you could see the OTP. Before the user enters the OTP, it is not even assigned to the user!

After changing the Microsoft password, you can continue to log on to previously authenticated Windows devices with an old password indefinitely. Why?

Has anyone else noticed that if you use a Microsoft account for Windows after changing your password, the old password can still be used indefinitely on previously authenticated devices? I have one that I changed a year ago and registration is still working. The change was made in the Windows settings.

I'm sure login credentials are cached without network access, but there is no point in continuing indefinitely. A secondary set of credentials for the local account bound to the Microsoft account can be static even after a password change (therefore the local account password is never updated). But this appears to be wrong for security reasons. Users would expect a password change to invalidate the old credentials.

Or is it just me somehow?

woocommerce – How do I create a plugin that supports authenticated POST requests to the REST API from external servers?

One of the main purposes of an API is to enable the integration of different services / systems.

Assume that the WordPress REST API can have both public and protected endpoints, where public endpoints do not require authentication and protected endpoints do.

  • Example of a public endpoint: GET https://main.loc/wp-json/wp/v2/posts
  • Example of a protected endpoint: POST https://main.loc/wp-json/wp/v2/posts

WordPress internally protects the POST endpoint as follows:

WordPress 5.4: wp-contains / rest-api / endpoints / class-wp-rest-posts-controller.php: 550

if ( ! current_user_can( $post_type->cap->create_posts ) ) {
    return new WP_Error(
        'rest_cannot_create',
        __( 'Sorry, you are not allowed to create posts as this user.' ),
        array( 'status' => rest_authorization_required_code() )
    );
}

Ultimately, this means that it is a cookie-based authentication method current_user_can() -> _wp_get_current_user -> determine_current_user Filters -> wp_validate_auth_cookie and wp_validate_logged_in_cookie Hook actions.

These actions check whether the request contains a valid authentication cookie that is checked by the function. However, it is difficult to generate them from an external system for two reasons:

  • The cookie name itself requires knowledge of the value of COOKIEHASHFor most websites, this is a simple MD5 of the website URL. However, some can override this cookie hash to be something else by setting the constant to a different value.
  • However, the cookie value is the real challenge. A value in this format is expected: wordpress_logged_in_COOKIE_HASH=username|expiration|token|hmac

However, there is no way to get AFAIK token outside the context of WordPress itself, so I cannot possibly send a cookie-based authenticated request from a third party, only within WordPress itself.

I understand that there are initiatives like https://github.com/WP-API/jwt-auth to provide a JWT based authentication method. However, as a plugin developer, I want to avoid being dependent on another third party plugin to authenticate my protected endpoints.

The only place I've ever seen anything I would like to reach is WooCommerce. It sticks to the determine_current_user To add your own authentication logic, in addition to the standard logic based on cookies: https://github.com/woocommerce/woocommerce/blob/master/includes/class-wc-rest-authentication.php#L69-L90

This adds basic auth authentication for websites using HTTPS and OAuth for HTTP.

For Basic Auth you have to create WooCommerce -> Settings -> REST-API -> New Key and assign this key to a user. That will give you one Consumer key and Consumer secret with which you can send authenticated requests:

Authenticated request:
authenticated request

Same request without authentication:
same request without authentication

Finally, my understanding is that the WordPress 5.4 REST API therefore does not provide a way to immediately send authenticated requests from external servers to protected endpoints, and you can either use a third-party JWT route, or build a robust solution to basic Provide authentication methods for authentication or OAuth, as WooCommerce did. Is that correct?

Authenticated Websites – Web Scraping – Python

BR: I'm trying to automate a process of getting data over the web using Python. In my case, I have to get the information from https://sistema.justwebtelecom.com.br/adm.php. However, before you switch to this page, you must log in to https://sistema.justwebtelecom.com.br/login.php. Theoretically, the following code should log onto the site:

from selenium import webdriver
from bs4 import BeautifulSoup

import time
import requests

browser = webdriver.Firefox()
browser.get("https://sistema.justwebtelecom.com.br/login.php")
time.sleep(3)
username = browser.find_element_by_id("email")
password = browser.find_element_by_id("senha")

username.send_keys("MEU-USUARIO")
password.send_keys("MINHA-SENHA")

time.sleep(2)
login_attempt = browser.find_element_by_id('entrar').click()
time.sleep(5)

url = 'https://sistema.justwebtelecom.com.br/adm.php'

r = requests.get(url)
soup = BeautifulSoup(r.text, 'lxml')
lista = soup.find_all('html')

print(lista)

BR: By printing the list variables, however, I get the source code of the page https://sistema.justwebtelecom.com.br/login.php, ie before logging in. I am the one who asks to print the page after logging in and I have access to the panel … / adm.php.

BR: I would like to know if I can get this information because if I go over the network in the browser, I can use the POST method to access some information from files. However, I cannot print this information.

Python – A register of government regulations authenticated with blockchain and public key cryptography (2)

This is a follow-up to this question after following some of the advice kindly given by other users.

The full repo is here, but I'll post the key excerpts below.

background

I'm trying to get a grip on blockchain and public key cryptography, and thought it would be fun to create a government regulation drafting and procurement program – the government in question is one I just invented.

The system should work as follows:

  1. A government official who has the required passwords creates a decree, stamps it with a digital stamp, and then adds it to the registry.
  2. A private person then meets decrees at work:

    • If he comes across a decree in isolation, he can judge its authenticity by means of its stamp and the public key.
    • If he has access to the register, he can check that the government has not tried to hide an earlier decree by checking the chain of hashes.

The code

This is the code in which I make and review mine Stamps, the certificates that authenticate decrees:

"""
This code defines two classes: one of which produces a digital stamp for
documents issued by the Chancellor of Cyprus, and the other of which verfies
the same.
"""

# Standard imports.
import getpass
import os
from pathlib import Path

# Non-standard imports.
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa

# Local constants.
PATH_TO_DIGISTAMP = str(Path.home())+"/chancery-b/digistamp/"
PATH_TO_PRIVATE_KEY = PATH_TO_DIGISTAMP+"stamp_private_key.pem"
PATH_TO_PUBLIC_KEY = PATH_TO_DIGISTAMP+"stamp_public_key.pem"
PUBLIC_EXPONENT = 65537
KEY_SIZE = 2048
ENCODING = "utf-8"

################
# MAIN CLASSES #
################

class StampMachine:
    """ A class which produces a string which testifies as to the
        authenticity of a given document. """
    def __init__(self, data):
        if not os.path.exists(PATH_TO_PRIVATE_KEY):
            raise Exception("No private key on disk.")
        self.private_key = load_private_key()
        self.data = data

    def make_stamp(self):
        """ Ronseal. """
        data_bytes = bytes(self.data, ENCODING)
        result_bytes = self.private_key.sign(
            data_bytes,
            padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
                        salt_length=padding.PSS.MAX_LENGTH),
                        hashes.SHA256())
        result = result_bytes.hex()
        return result

class Verifier:
    """ A class which allows the user to verify a stamp produced as
    above. """
    def __init__(self, stamp, data):
        self.stamp_bytes = bytes.fromhex(stamp)
        self.public_key = load_public_key()
        self.data = data

    def verify(self):
        """ Decide whether the stamp in question is authentic or not. """
        data_bytes = bytes(self.data, ENCODING)
        try:
            self.public_key.verify(
                self.stamp_bytes,
                data_bytes,
                padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
                            salt_length=padding.PSS.MAX_LENGTH),
                hashes.SHA256())
        except InvalidSignature:
            return False
        else:
            return True

####################
# HELPER FUNCTIONS #
####################

def get_bytes_password():
    """ Get a password from the user, and convert it into bytes. """
    password = getpass.getpass(prompt="Digistamp password: ")
    result = bytes(password, ENCODING)
    return result

def get_bytes_password_new():
    """ Get a NEW password from the user, and convert it into bytes. """
    password = getpass.getpass(prompt="Digistamp password: ")
    password_ = getpass.getpass(prompt="Confirm password: ")
    if password != password_:
        print("Passwords do not match.")
        return get_bytes_password_new()
    result = bytes(password, ENCODING)
    return result

def generate_public_key(private_key):
    """ Generate a public key from a private key object. """
    public_key = private_key.public_key()
    pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo)
    with open(PATH_TO_PUBLIC_KEY, "wb") as public_key_file:
        public_key_file.write(pem)

def generate_keys():
    """ Generate a new private and public key. """
    if os.path.exists(PATH_TO_PRIVATE_KEY):
        raise Exception("Private key file already exists.")
    bpw = get_bytes_password_new()
    private_key = rsa.generate_private_key(public_exponent=PUBLIC_EXPONENT,
                                           key_size=KEY_SIZE,
                                           backend=default_backend())
    pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.BestAvailableEncryption(bpw))
    with open(PATH_TO_PRIVATE_KEY, "wb") as private_key_file:
        private_key_file.write(pem)
    generate_public_key(private_key)

def load_private_key():
    """ Load the private key from its file. """
    bpw = get_bytes_password()
    with open(PATH_TO_PRIVATE_KEY, "rb") as key_file:
        result = serialization.load_pem_private_key(
            key_file.read(),
            password=bpw,
            backend=default_backend())
    return result

def load_public_key():
    """ Load the public key from its file. """
    with open(PATH_TO_PUBLIC_KEY, "rb") as key_file:
        result = serialization.load_pem_public_key(
            key_file.read(),
            backend=default_backend())
    return result

###########
# TESTING #
###########

def test():
    """ Run the unit tests. """
    stamp = StampMachine("123").make_stamp()
    assert Verifier(stamp, "123").verify()
    assert not Verifier(stamp, "abc").verify()
    print("Tests passed!")

###################
# RUN AND WRAP UP #
###################

def run():
    test()

if __name__ == "__main__":
    run()

This is the code that contains a decree uploaded to register:

"""
This code defines a class, which uploads a record to the ledger.
"""

# Standard imports.
import datetime
import hashlib
import os
import sqlite3

# Local imports.
import ordinance_inputs
from digistamp.digistamp import StampMachine

# Local constants.
ENCODING = "utf-8"

##############
# MAIN CLASS #
##############

class Uploader:
    """ The class question. """
    def __init__(self):
        self.connection = None
        self.cursor = None
        self.block = BlockOfLedger()

    def make_connection(self):
        """ Ronseal. """
        self.connection = sqlite3.connect("ledger.db")
        self.connection.row_factory = dict_factory
        self.cursor = self.connection.cursor()

    def close_connection(self):
        """ Ronseal. """
        self.connection.close()

    def add_ordinal_and_prev(self):
        """ Add the ordinal and the previous block's hash to the block. """
        self.make_connection()
        query = "SELECT * FROM Block ORDER BY ordinal DESC;"
        self.cursor.execute(query)
        result = self.cursor.fetchone()
        self.close_connection()
        if result is None:
            self.block.set_ordinal(1)
            self.block.set_prev("genesis")
        else:
            self.block.set_ordinal(result("ordinal")+1)
            self.block.set_prev(result("hash"))

    def add_hash(self):
        """ Add the hash to the present block. """
        hash_maker = hashlib.sha256()
        hash_maker.update(bytes(self.block.ordinal))
        hash_maker.update(bytes(self.block.ordinance_type, ENCODING))
        hash_maker.update(bytes(self.block.latex, ENCODING))
        hash_maker.update(bytes(self.block.year))
        hash_maker.update(bytes(self.block.month))
        hash_maker.update(bytes(self.block.day))
        if self.block.annexe:
            hash_maker.update(self.block.annexe)
        hash_maker.update(bytes(self.block.prev, ENCODING))
        self.block.set_the_hash(hash_maker.hexdigest())

    def add_new_block(self):
        """ Add a new block to the legder. """
        new_block_tuple = (self.block.ordinal, self.block.ordinance_type,
                           self.block.latex, self.block.year,
                           self.block.month, self.block.day,
                           self.block.stamp, self.block.annexe,
                           self.block.prev, self.block.the_hash)
        query = ("INSERT INTO Block (ordinal, ordinanceType, latex, year, "+
                 "                   month, day, stamp, annexe, prev, "+
                 "                   hash) "+
                 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")
        self.make_connection()
        self.cursor.execute(query, new_block_tuple)
        self.connection.commit()
        self.close_connection()

    def upload(self):
        """ Construct a new block and add it to the chain. """
        self.add_ordinal_and_prev()
        self.add_hash()
        self.add_new_block()

################################
# HELPER CLASSES AND FUNCTIONS #
################################

class BlockOfLedger:
    """ A class to hold the properties of a block of the ledger. """
    def __init__(self):
        date_and_time = datetime.datetime.now()
        self.ordinal = None
        self.ordinance_type = ordinance_inputs.ordinance_type
        self.latex = ordinance_inputs.latex
        self.year = date_and_time.year
        self.month = date_and_time.month
        self.day = date_and_time.day
        self.annexe = annexe_to_bytes()
        self.prev = None
        self.the_hash = None
        self.stamp = None

    def set_ordinal(self, ordinal):
        """ Assign a value to the "ordinal" field of this object. """
        self.ordinal = ordinal

    def set_prev(self, prev):
        """ Assign a value to the "prev" field of this object. """
        self.prev = prev

    def set_the_hash(self, the_hash):
        """ Assign a value to the "the_hash" field of this object. """
        self.the_hash = the_hash
        self.stamp = StampMachine(self.the_hash).make_stamp()

def dict_factory(cursor, row):
    """ A function which allows queries to return dictionaries, rather than
    default tuples. """
    result = {}
    for idx, col in enumerate(cursor.description):
        result(col(0)) = row(idx)
    return result

def annexe_to_bytes():
    """ Convert the annexe folder to a zip, load the bytes thereof into
    memory, and then delete the zip. """
    if len(os.listdir("annexe/")) == 0:
        return None
    os.system("zip -r annexe.zip annexe/")
    with open("annexe.zip", "rb") as annexe_zip:
        result = annexe_zip.read()
    os.system("rm annexe.zip")
    return result

###################
# RUN AND WRAP UP #
###################

def run():
    Uploader().upload()

if __name__ == "__main__":
    run()

And that's the code in which decrees are extracted off the register and into something more legible, d. H. a PDF:

"""
This code defines a class which takes a given record in the ledger and
converts it into a directory.
"""

# Standard imports.
import os
import sqlite3
import sys

# Non-standard imports.
from pdfrw import PdfReader, PdfWriter

# Local imports.
from digistamp.digistamp import Verifier
from uploader import dict_factory

# Local constants.
MONTH_NAMES = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
               "Sep", "Oct", "Nov", "Dec")
VERIFICATION_INSTRUCTIONS = ("To verify this Ordinance: (1) Verify that "+
                             "the hash matches the data. It should be the "+
                             "hex digest of the SHA256 hash of the data "+
                             "points. (2) Verify that the stamp matches "+
                             "the hash, using the public key, standard "+
                             "padding and, again, SHA256. (To make your "+
                             "life easier, you could just use the "+
                             "verification software provided by this "+
                             "office.)")

##############
# MAIN CLASS #
##############

class Extractor:
    """ The class in question. """
    def __init__(self, ordinal):
        self.ordinal = ordinal
        self.block = self.fetch_block()
        self.main_tex = self.make_main_tex()

    def fetch_block(self):
        """ Fetch the block matching this object's ordinal from the
        ledger. """
        connection = sqlite3.connect("ledger.db")
        connection.row_factory = dict_factory
        cursor = connection.cursor()
        query = "SELECT * FROM Block WHERE ordinal = ?;"
        cursor.execute(query, (self.ordinal,))
        result = cursor.fetchone()
        connection.close()
        if result is None:
            raise Exception("No block with ordinal "+str(self.ordinal)+
                            " in the ledger.")
        return result

    def get_base(self):
        """ Get the base for main.tex, given the type of the Ordinance. """
        if self.block("ordinanceType") == "Declaration":
            path_to_base = "latexery/base_declaration.tex"
        elif self.block("ordinanceType") == "Order":
            path_to_base = "latexery/base_order.tex"
        else:
            raise Exception("Invalid ordinanceType: "+
                            self.block("ordinanceType"))
        with open(path_to_base, "r") as base_file:
            result = base_file.read()
        return result

    def make_main_tex(self):
        """ Make the code for main.tex, which will then be used build our
        PDF. """
        day_str = str(self.block("day"))
        if len(day_str) == 1:
            day_str = "0"+day_str
        packed_ordinal = str(self.ordinal)
        while len(packed_ordinal) < 3:
            packed_ordinal = "0"+packed_ordinal
        month_str = MONTH_NAMES(self.block("month")-1)
        result = self.get_base()
        result = result.replace("#BODY", self.block("latex"))
        result = result.replace("#DAY_STR", day_str)
        result = result.replace("#MONTH_STR", month_str)
        result = result.replace("#YEAR", str(self.block("year")))
        result = result.replace("#PACKED_ORDINAL", packed_ordinal)
        return result

    def authenticate(self):
        """ Check that the block isn't a forgery. """
        self.compare_hashes()
        self.verify_stamp()

    def compare_hashes(self):
        """ Compare the "prev" field of this block with the hash of the
        previous. """
        if self.ordinal == 1:
            if self.block("prev") != "genesis":
                raise Exception("Block with ordinal=1 should be the "+
                                "genesis block.")
            return
        prev_ordinal = self.ordinal-1
        connection = sqlite3.connect("ledger.db")
        cursor = connection.cursor()
        query = "SELECT hash FROM Block WHERE ordinal = ?;"
        cursor.execute(query, (prev_ordinal,))
        extract = cursor.fetchone()
        connection.close()
        prev_hash = extract("0")
        if prev_hash != self.block("prev"):
            raise Exception("Block with ordinal="+str(self.ordinal)+" is "+
                            "not authentic: "prev" does not match "+
                            "previous "hash".")

    def verify_stamp(self):
        """ Check that this block's stamp is in order. """
        verifier = Verifier(self.block("stamp"), self.block("hash"))
        if not verifier.verify():
            raise Exception("Block with ordinal="+str(self.ordinal)+" is "+
                            "not authentic: "prev" does not match "+
                            "previous "hash".")

    def write_main_tex(self):
        """ Ronseal. """
        with open("latexery/main.tex", "w") as main_tex:
            main_tex.write(self.main_tex)

    def compile_main_tex(self):
        """ Compile the PDF. """
        script = ("cd latexery/n"+
                  "pdflatex main.tex")
        os.system(script)

    def add_metadata(self):
        """ Add the verification metadata to the PDF. """
        os.system("mv latexery/main.pdf latexery/main_old.pdf")
        trailer = PdfReader("latexery/main_old.pdf")
        trailer.Info.instructions = VERIFICATION_INSTRUCTIONS
        trailer.Info.data_ordinal = self.block("ordinal")
        trailer.Info.data_ordinanceType = self.block("ordinanceType")
        trailer.Info.data_latex = self.block("latex")
        trailer.Info.data_year = self.block("year")
        trailer.Info.data_month = self.block("month")
        trailer.Info.data_day = self.block("day")
        decoded_annexe = self.block("annexe").hex()
        trailer.Info.data_annexe = decoded_annexe
        trailer.Info.data_prev = self.block("prev")
        trailer.Info.hash = self.block("hash")
        trailer.Info.stamp = self.block("stamp")
        PdfWriter("latexery/main.pdf", trailer=trailer).write()

    def create_and_copy(self):
        """ Create the directory, and copy the PDF into it. """
        if os.path.isdir("extracts/"+str(self.ordinal)+"/"):
            os.system("rm -r extracts/"+str(self.ordinal)+"/")
        os.system("mkdir extracts/"+str(self.ordinal)+"/")
        os.system("cp latexery/main.pdf extracts/"+str(self.ordinal)+"/")

    def write_annexe_zip(self):
        """ Write annexe to a file in the directory. """
        if self.block("annexe") is None:
            return
        with open("extracts/"+str(self.ordinal)+"/annexe.zip", "wb") 
            as annexe_zip:
            annexe_zip.write(self.block("annexe"))

    def extract(self):
        """ Do the thing. """
        self.authenticate()
        self.write_main_tex()
        self.compile_main_tex()
        self.add_metadata()
        self.create_and_copy()
        self.write_annexe_zip()

    def zip_and_delete(self):
        """ Ronseal. """
        script = ("cd extracts/n"+
                  "zip -r ordinance_"+str(self.ordinal)+".zip "+
                  str(self.ordinal)+"/n"+
                  "rm -r "+str(self.ordinal)+"/")
        if os.path.exists("extracts/ordinance_"+str(self.ordinal)+".zip"):
            os.system("rm extracts/ordinance_"+str(self.ordinal)+".zip")
        os.system(script)

###########
# TESTING #
###########

def demo():
    """ Run a demonstration. """
    extractor = Extractor(1)
    extractor.extract()

###################
# RUN AND WRAP UP #
###################

def run():
    if len(sys.argv) == 2:
        extractor = Extractor(int(sys.argv(1)))
        extractor.extract()
    else:
        print("Please run me with exactly one argument, the number of the "+
              "Ordinance you wish to extract.")

if __name__ == "__main__":
    run()

What I would like to know

  • Do I use blockchain and public key cryptography wisely? Or have I misunderstood some of the basic concepts?
  • Are there any obvious security issues? Is there an obvious exploit where someone could start making convincing fakes?

I feel like I've already raised the more style-related issue in my previous question, but if you still see blatant issues in this regard, you don't have to keep it to yourself.