How to Create a Cloud Storage Server

(How to Create a WebDAV Server)

Web File Manager

The Web File Server object transforms your Mako Server into a powerful and secure storage system, letting you access and share files from any connected computer or device in the world. You can either access it through a Web browser or mount it as a WebDAV drive, in which case it works like a local drive.

The combined WebDAV Server and Web File Manager is called the Web File Server. This tutorial will show how to create a Web File Server (WFS) object programmatically

Note: If you want a quick way to enable the WebDAV server using the Mako Server on a private network without authentication, simply create the following mako.conf configuration file:

fileserver={
   fsname="fs",
   ioname="disk",
   path="/",
   lockdir="/tmp/.LOCK", -- Linux,Mac,QNX.
   noauth=true -- No authentication needed.
}

Enabling the combined WebDAV Server and Web File Manager using mako.conf.

The video above shows how to connect a Windows and Mac computer to a WebDAV server.

The WFS uses internal magic to provide a Web File Manager and WebDAV server on the same URL. You can also create a DAV-only object. The WFS object's benefit is that both browsers and WebDAV clients can use it. The only drawback with the WFS is that it triggers the execution of Lua code, thus slightly slowing down the operation when using WebDAV. You should use the DAV object directly if your objective is to create a high-speed WebDAV server since the DAV object uses a pure "C" implementation and not a hybrid solution.

The Mako Server’s Virtual File System

The Virtual File System (VFS) makes it possible for you to build application server resources in a running system programmatically. The VFS is responsible for mapping URLs to resources such as your application's LSP pages. The Mako Server provides several pre-defined objects that can be created and installed into the VFS, such as the WebDAV object. You can also define your objects and insert these objects into the VFS.

Creating VFS nodes such as the WebDAV object at system startup is commonly done in a ".preload" script. Your application's ".preload" script is run at startup, and this script can create VFS nodes and populate the Mako Server's VFS. It is also possible to dynamically insert, say, a WebDAV object into the VFS from a Lua Server Page (LSP). The user would then first have to visit the LSP before the WebDAV object would be accessible. This would be inconvenient since the user would have to visit the LSP before the user could access the WebDAV resource.

The Lua Tutorials includes several examples that teach you the VFS fundamentals. You can also read the VFS introduction in our online documentation.

WebDAV and Web-File-Server Virtual File System Nodes

You can find the two file server objects in the documentation. Here are the links to the online documentation:

DAV locking is a part of the WebDAV standard, and we need to provide a directory for the DAV object for file lock operations. You can use the DAV object without a lock directory, but some WebDAV clients will be in "read-only" mode when connecting to a DAV object that does not support file locking.

The following example shows how to create a lock directory using the Mako Server's root directory object:

local wdio = ba.openio"disk" -- Use the "disk" IO as the WebDAV IO
local env = mako.env
local ldir = mako.dos2unix(env and (env.TMP or env.TEMP) or "/tmp").."/.LOCK"
if not wdio:stat(ldir) then
   if not wdio:mkdir(ldir) then
      trace("Cannot open WebDAV lock directory:",ldir)
      ldir=nil -- WebDAV may be in read only mode when used by some clients
   end
end

Example 1: Shows how to create the WebDAV lock directory.

The above code tries to find the host system's TEMP directory from the system’s environment variables. It then uses the root IO when creating the ".LOCK" sub-directory in the TEMP directory. The dos2unix function makes sure the code also works when run on Windows. The IO interface requires UNIX-style paths on all operating systems. For example, a TEMP directory such as c:\TEMP is translated by dos2unix to the path /c/TEMP/. The Mako server provides several IO interfaces. See Mako Specific features for more information on the IO interfaces provided. You can also create your own custom IO interface that, for example, uses a database for storing files – in other words, you can have a WebDAV server that stores all resources in a database.

We can create the DAV object and insert it into the Virtual File System (VFS) once we have a lock directory. The lock directory must be set to nil if you do not want to use a lock directory.

local maxUploads=50
local maxLocks=100
require"wfs" -- install ba.create.wfs by loading it from mako.zip
fsdir=ba.create.wfs("fs",wdio,ldir,maxUploads,maxLocks)
fsdir:insert() -- Insert as a root node with name 'fs' in the VFS

Example 2: Shows how to create a WFS object and insert the object into the Virtual File System.

You can copy the above Lua scripts in example 1 and 2, paste it into a .preload script, and use the Mako Server to load the .preload script. You can then access the Web File Manager using a browser by navigating to the URL http://server-name/fs/.

Security

Running the above code for anything but test purposes is not recommended since you have no security, and anyone can access the Web File Manager and WebDAV server without logging in. What’s missing from the above code is an authenticator and authorizer object. The following code installs a Digest authenticator object in the WFS object.

-- The username/password callback function.
local function getpassword(username)
   if username == "admin" then return "admin" end
end
-- Create the username database from our getpassword func.
local authuser=ba.create.authuser(getpassword)
-- Create authenticator by using the username database.
local authenticator=ba.create.authenticator(authuser)
-- Enable authentication for the directory.
fsdir:setauth(authenticator)

Example 3: Installing a Digest Authentication object in the WFS object.

The above password function makes it possible to log in with the username ‘admin’ and password ‘admin’. This function can easily be redesigned such that the function fetches the credentials from a SQL database.

Many WebDAV clients require Digest Authentication and this is also the default authenticator type created by function ba.create.authenticator. You may want to use form-based authentication for browser users. The Barracuda Application Server provides many authentication types, and you can make an authenticator object use form-based authentication for browsers and Digest for WebDAV clients. Ensure you have a local copy of the Barracuda Application Server documentation, and then read up on the various authenticator types.

Besides providing authentication, it’s common to provide an authorizer object for WebDAV objects. An authorizer object may limit the authenticated user’s access rights. Creating an authorizer object is explained below.

Download Example Code (Example 1)

WebDAV Example 1 is a ready-to-run example implementing the above security principles.

Download example 1 from GitHub

Creating a User Database

In the previous example, we had a single hard-coded user that was verified within the getpassword() callback function. You can implement your own user database interaction and utilize the getpassword() callback to interact with this database. However, the Barracuda App Server offers a user-friendly user authentication database called jsonuser, which employs JSON as its storage format. In Example 4, we use a jsonuser instance to authenticate users, specifically guest, mom, dad, and kids.

-- Create the user db with the users: guest, kids, dad, and mom.
-- The password is the same as the username.
local function createUserDB()
   -- Setup the users, Defaults: maxusers=3,recycle=false,inactive=false
   -- We are not using roles, but we must at least provide an empty array
   local userGuest={pwd='guest', roles={}}
   local userKids={pwd='kids', roles={}}
   local userDad={pwd='dad', roles={}}
   local userMom={pwd='mom', roles={}}
   return {guest=userGuest,kids=userKids,dad=userDad,mom=userMom}
end

-- Create a JSON user database object
local authuser=ba.create.jsonuser()
 -- This would typically be loaded via rwfile.json()
authuser:set(createUserDB())
-- Create a digest authenticator (using default values).
local authenticator=ba.create.authenticator(authuser)

-- Install the authenticator
fsdir:setauth(authenticator)

Example 4: Using the jsonuser authenticator.

The user accounts are hard-coded directly into the source code in Example 4. In a real-world application, storing user information in a JSON file is best practice. To facilitate reading and saving data from this JSON file, you can use the rwfile.json() function. This approach allows for more flexibility and manageability as user accounts can be easily updated and maintained in an external JSON file rather than being embedded directly in the code.

Creating Access Control Lists (ACL)

The following example expands on the previous example and implements ACL for a secure photo album. The so-called authorizer will be configured to use principals and roles.

To effectively customize security, it's crucial to understand principals and roles. A principal essentially identifies an entity capable of interacting or performing tasks within a system. This entity can be a person, company, process, or virtually anything.

Roles group specific actions together, allowing us to assign particular principals to these roles, thereby granting them permission to perform those actions. This concept is similar to a UNIX system's user and group concept. Users typically represent individuals who may access the system, while groups denote the positions or categories users can hold. For instance, a company system might have a user named Allen Smith, who belongs to the "human resources" group.

In this example, we will create a user database containing four users: guest, mom, dad, and kids. We will also design a Security Realm that restricts the actions principals can perform. The following roles will be established:

rolesprincipals
guestguest, mom, dad, kids
familymom, dad, kids
daddad
mommom

The Security Realm we design in this example will give the following access rights to the roles.

DirectoryRoles: read accessRoles: write access
+---album                 
    +---family            
        +---dad           
        +---mom           
        +---kids          
guest
family
mom, dad
mom, dad
mom, dad, family
mom, dad
mom, dad
dad
mom
family

Several HTTP commands associated with reading, including WebDAV HTTP commands, are used for providing read access to the photo album, and several HTTP commands associated with writing are used for providing write access.

The security constraints are designed so that constraints for a directory apply to any subdirectory unless overridden. For instance, the path 'album/noSuchDir' will have the same access rights as the 'album' path.

-- Create the user db with the users: guest, kids, dad, and mom.
-- The password is the same as the username.
local function createUserDB()
   -- Setup the users, Defaults: maxusers=3,recycle=false,inactive=false
   local userGuest={pwd='guest',roles={'guest'},maxusers=50}
   local userKids={pwd='kids',roles={'guest','family'}}
   local userDad={pwd='dad',roles={'guest','family','dad'},recycle=true,inactive=60*60}
   local userMom={pwd='mom',roles={'guest','family','mom'},recycle=true,inactive=60*60}
   return {guest=userGuest,kids=userKids,dad=userDad,mom=userMom}
end

-- Create the Access Control List (ACL)
local function createConstraints()
   local read={"OPTIONS","HEAD","GET","PROPFIND","LOCK","UNLOCK"}
   local write={"POST","PUT","DELETE","MKCOL","COPY","MOVE","PROPPATCH"}
   local rw={}
   -- rw = read+write
   for _,v in pairs(read) do rw[#rw+1]=v end
   for _,v in pairs(write) do rw[#rw+1]=v end
   local constr1={urls={'/*'},methods=read,roles={'guest'}}
   local constr2={urls={'/*','/family/*'},methods=write,roles={'mom','dad'}}
   local constr3={urls={'/family/*'},methods=read,roles={'family'}}
   local constr4={urls={'/family/mom/*','/family/dad/*'},methods=read,roles={'mom','dad'}}
   local constr5={urls={'/family/dad/*'},methods=write,roles={'dad'}}
   local constr6={urls={'/family/mom/*'},methods=write,roles={'mom'}}
   local constr7={urls={'/family/kids/*'},methods=rw,roles={'family'}}

   -- Note, the constraint names are not used by the authorizer.
   return {
      Guest=constr1,FamilyPost=constr2,FamilyGet=constr3,
      Parents=constr4,Dad=constr5,Mom=constr6,Kids=constr7
   }
end

-- Create a JSON user database object
local authuser=ba.create.jsonuser()
authuser:set(createUserDB())
-- Create a digest authenticator (using default values).
local authenticator=ba.create.authenticator(authuser)

-- Create the authorizer and install the constraints.
local authorizer=authuser:authorizer()
authorizer:set(createConstraints())

-- Install the authenticator and the ACL
fsdir:setauth(authenticator,authorizer)

Example 5: Using the jsonuser authenticator and authorizer.

Just as in example 4, we hard code the user and constraint database in Example 5. The JSON data would be stored in one or two files in a real-world application.

Download Example Code (Example 2)

WebDAV Example 2 is a ready-to-run example implementing all of the above jsonuser security principles. The ACL for the photo album can be modified to your requirements or removed if you do not need access control lists.

Download example 2 from GitHub

Crafting Innovations? We're Here to Lend a Hand:

Dive deep into our treasure trove of embedded web server and IoT tutorials designed for enthusiasts like you. Yet, if deadlines loom or challenges arise, remember our experts are on standby. With Real Time Logic, you have the freedom to learn and the assurance of expert support when you need it. Let's bring your project to fruition your way.



OPC-UA

OPC-UA Client & Server

An easy to use OPC UA stack that enables bridging of OPC-UA enabled industrial products with cloud services, IT, and HTML5 user interfaces.

Edge Controller

Edge Controller

Use our user programmable Edge-Controller as a tool to accelerate development of the next generation industrial edge products and to facilitate rapid IoT and IIoT development.

On-Premises IoT

On-Premises IoT Platform

Learn how to use the Barracuda App Server as your On-Premises IoT Foundation.

Embedded Web Server

Barracuda Embedded Web Server

The compact Web Server C library is included in the Barracuda App Server protocol suite but can also be used standalone.

WebSocket Server

Microcontroller Friendly

The tiny Minnow Server enables modern web server user interfaces to be used as the graphical front end for tiny microcontrollers. Make sure to check out the reference design and the Minnow Server design guide.

WebDAV Server

Network File System

Why use FTP when you can use your device as a secure network drive.

HTTP Client

Secure HTTP Client Library

PikeHTTP is a compact and secure HTTP client C library that greatly simplifies the design of HTTP/REST style apps in C or C++.

WebSocket Client

Microcontroller Friendly

The embedded WebSocket C library lets developers design tiny and secure IoT applications based on the WebSocket protocol.

SMTP Client

Secure Embedded SMTP Library

Send alarms and other notifications from any microcontroller powered product.

Crypto Library

RayCrypto C Library

The RayCrypto engine is an extremely small and fast embedded crypto library designed specifically for embedded resource-constrained devices.

Embedded PKI Service

Automatic SSL Certificate Management for Devices

Real Time Logic's SharkTrust™ service is an automatic Public Key Infrastructure (PKI) solution for products containing an Embedded Web Server.

Modbus

Modbus TCP client

The Modbus client enables bridging of Modbus enabled industrial products with modern IoT devices and HTML5 powered HMIs.

Posted in Tutorials by bd