Step 4: Making data more reliable and secure

So far we have written code to create contribution objects. However, we are not done yet if we want to create reliable and secure objects because:

[Note]Note
Preventing bad data to be stored in objects is one of the most effective ways to construct reliable and secure software. According to the Open Web Application Security Project (OWASP), injection and cross-site-scripting are the two top items in their list of Top ten security risks

Luckily Obix makes it easy to define specific types that protect against invalid data.


A solution for attributes first_name and last_name is to define a new type that holds a string composed only of letters and spaces, and limited to a length of 40 characters. Let's call this type user_name. Create it like this:

In the above code we use several techniques:

[Note]Note
This chapter and the following one introduce a number of techniques which are unique to Obix. Please note that the details of these techniques are not fully explained in this tutorial. The aim is to give you a quick overview about the techniques available in Obix, without digging too deep. Please refer to the links in the text, the language manual, the API documention and the system source code if you need more information, or post a message.

Types that hold string values restricted by a regular expression are very common. Just think of things like a telephone number, an email adress, an ISBN number, etc. Therefore Obix's standard library contains a source code template you can use in order to minimize the code to type and to avoid code duplication. The template that shortens our code is named non_empty_regex_string_type. Its source code is defined as follows:

template non_empty_regex_string_type

   param type_suffix end
   param regex end

   type {type_suffix} default_factory:yes

      inherit simple_non_empty_string
         attribute value
            and_check
               script
                  const regex regex = fa_regex.create ( "{regex}"~ )
                  check i_value.matches_regex ( c_regex ) &
                     error_message: """'{{i_value.to_string}}' is invalid because it doesn't match the regular expression '{{c_regex}}'""" &
                     error_id: matches_regex_violation
               end
            end
         end
      end
   end
   
end

This template has two parameters:

  1. type_suffix is the type suffix of the type we want to create

  2. regex is the regular expression to apply in the type

To use this template replace the content of file ty_user_name.osc with:

%non_empty_regex_string_type < &
   type_suffix: "user_name" &
   regex: "^[a-zA-Z ]{1,40}$" >

What remains to be done is to change the type of attributes first_name and last_name in type contribution from string to user_name.

Replace the old code:

attribute first_name type:string end
attribute last_name  type:string end

with this one:

attribute first_name type:user_name end
attribute last_name  type:user_name end

Remark: Don't re-compile now. Wait until the end of this chapter.


We can now use the same technique to restrict attribute random_number to values between 1 and 15:

Types that hold positive integer numbers limited to a maximum value are also very common. Therefore you can again use a template in Obix's standard library in order to simplify the code and avoid code duplication. Replace the above code with:

%positive32_with_maximum_type < &
   type_suffix: "random_1_15" &
   max_value: "15" >

Finally you have to change the declaration of attribute random_number in type contribution.

The old code:

attribute random_number type:positive32 end

becomes:

attribute random_number type:random_1_15 end

Because we have now changed the types of 3 attributes in type contribution we have to adapt the test script in factory contribution. The type of attribute first_name, for example, changed from string to user_name. Therefore we can't assign anymore a string literal (like "Albert") to first_name. Static typing requires us to use an object of type user_name, and such an object can be created with the syntax fa_user_name.create ( "Albert"~ ). Please note the ~ symbol in this expression. Writing "Albert" denotes a string literal, whereas "Albert"~ denotes a string_value literal.

The code below shows the adapted code of the factory's test script. Please note the changes made for attributes first_name, last_name and random_number and adapt your source code file:

   test
      script
         // create a 'contribution' object and assign it to constant 'c'
         const contribution c = fa_contribution.create ( &
            identifier = 123 &
            first_name = fa_user_name.create ( "Albert"~ ) &
            last_name = fa_user_name.create ( "Newton"~ ) &
            random_number = fa_random_1_15.create ( 5~ ) &
            date_time = fa_local_date_time.create ( "2011-08-27T16:16:30"~ ) )
            
         // verify all attributes
         verify c.identifier =v 123
         verify c.first_name.value =v "Albert"~
         verify c.last_name.value =v "Newton"~
         verify c.random_number.value =v 5~
         verify c.date_time.to_string =v "2011-08-27T16:16:30"

         // verify the value returned by the 'to_string' command
         verify c.to_string =v "Albert Newton contributed '5' on 2011-08-27T16:16:30"
      end
   end

To check that everything is still ok, compile and build your project, then execute run_tests.


You can now relax because the result of the above effort is reliable and secure code! Contribution objects can now only be created if all data are valid. Malicious data is rejected at runtime and inhibits the creation of objects. And once a valid object is created it remains in a valid state and nothing wrong can happen anymore because it is immutable. Therefore it can also be freely shared without any precautions to be taken, such as synchronization. It is prepared to be fearlessly used in multi-threading and multi-processor environments.

The increased type safety achieved through the addition of specific types for attributes first_name, last_name and random_number has also a nice side effect. The compiler now protects against bugs related to semantically incompatible assignments. Suppose, for example, that variable age_of_elephant of type positive32 exists in a script. Then a stupid assignment like

identifier = age_of_elephant

in an instruction that creates a contribution object is immediately rejected by the compiler. Although both values are integers, they are semantically incompatible and the Obix compiler is able to detect this.

Another benefit results from types user_name and random_1_15. Their conditions coded to validate data can be programmatically used to check untrusted data. For example, a first_name value coming from an external source can be checked with the following expression:

const attribute_check_error input_error = ty_user_name.value.check ( first_name_entered_in_web_page )
if input_error #r void then
   // ...
end if

Server side code of a web application can use this technique to check data received in a HTTP request, without the need to duplicate the data validation code. We will use this technique in a later chapter.

Finally it is interesting to note that you can add specific features (behavior) to types defined in the above way. Suppose, for example, you create a type ISBN (which is a string restrained by a regular expression) in a bookstore application. You could then add command get_publisher_code to type ISBN in order to extract the publisher code of a book.


One last point we didn't consider yet is the requirement for unique, auto-incremented values for attribute identifier. We will take care about that later.