1. Our Goal

This tutorial will guide you about how to create an admin page. In the very first tutorial, we’ll only display hello world in the screen using admin page template (containing menu and admin default theme). In the end of this tutorial, you’ll learn how to create admin page, add menu to the admin module, and integrate it with admin security module. Though it’s not necessary, it might help you a lot to learn how to create Magento hello world in the frontend.

2. Creating Configuration

First, we’ll create a module configuration. The configuration will tell Magento basic information about our module and the location of our module. The basic Magento module configuration is located on /app/etc/modules. You can see that there some modules configuration that already there. Create a file named Bippocoid_Investorcatalog.xml.

<?xml version = "1.0"?>
<config>
	<modules>
		<!--
			format of tag below is: Namespace_Module
			in our case namespace will be Bippocoid, and module name is Investorcatalog
		-->
		<Bippocoid_Investorcatalog>
			<!--
				Tag active: true will tell magento to enable our module in magento
			-->
			<active>true</active>
			<codePool>local</codePool>
		</Bippocoid_Investorcatalog>
	</modules>
</config>

So we already have basic module configuration that ask Magento to load our module. This will also tell Magento where our artifact located, that is: /app/code/[community|core|local]/[Namespace]/[Module]. Our code will be located on local instead of community or core. Community will be used by modules that created by community, and core will be used by Magento core module (so it will be update when your Magento installation is upgraded).

The configuration above only tell that we have new Module and the location of our Module. Now we need to create second configuration file, config.xml file, that contain detail of our modules. This will be located on /etc folder of our module, that is: /app/code/local/Bippocoid/Investorcatalog/etc/config.xml.

<?xml version="1.0"?>
<config>
	<modules>
		<Bippocoid_Investorcatalog>
			<version>1.0.0</version>
		</Bippocoid_Investorcatalog>
	</modules>
</config>

At this point, you can validate whether your Modules configuration is working or not using admin page on menu: System|Configuration|Advanced. In the ‘Disable Modules Output’ you’ll see your Modules.

2. Creating Controller

The controller will be called when specified URL is sent to Magento. So in the controller we’ll write ‘Hello Wolrd!’ and then link specified URL to our controller in our module configuration. Our admin controller will be named as Controller.php and located in /controllers/Adminhtml/. We’ll named our controller as Investorcatalog, so we’ll have /app/code/local/Bippocoid/Investorcatalog/controllers/Adminhtml/InvestorcatalogController.php. Our controller class will be extended from Mage_Adminhtml_Controller_Action class.

<?php
class Bippocoid_Investorcatalog_Adminhtml_InvestorcatalogController extends Mage_Adminhtml_Controller_Action {

	public function indexAction() {
		echo "Hello World";
	}

}

Then we need to tell our magento to load our controller. We’ll add configuration to our etc/config.xml.

	<modules>	
	.....
	</modules>
	<!-- After modules-->
	<admin>
		<routers>
			<adminhtml>
				<args>
					<modules>
					<!-- Use your namespace and module to make it unique -->
						<bippocoid_investorcatalog after="Mage_Adminhtml">Bippocoid_Investorcatalog_Adminhtml</bippocoid_investorcatalog>
					</modules>
				</args>
			</adminhtml>
		</routers>
	</admin>

The configuration above tells Magento:

  1. Our controllers is an admin module (see admin tag)
  2. Our controller classes located in /controllers/Adminhtml (Bippocoid_Investorcatalog_Adminhtml)
  3. We’ll use /admin url (attribute after=”Mage_Adminhtml”), so you’ll share with admin url module. If you invoke /admin/investorcatalog/index, it will load InvestorcatalogController class and call indexAction method.

Well now you should be able to call your module via /admin/investorcatalog/index URL. But wait, it’ll redirect the page to Dashboard page. That because you need special URL to access admin mdoule. If you check your browser URL right now you’ll see additional URL param at the end of dashboard page (key/xxxxxxxx..). We’ll create this link using admin menu configuration.

3. Creating Menu

Menu is created using configuration file. You can added to you exiting /etc/config.xml or special config file, admin.xml, in the /etc folder.

<?xml version="1.0"?>
<config>
	<menu>
		<catalog>
			<children>
				<investorcatalog  translate="title">
					<!-- This is our menu configuration (display option) -->
					<title>Investor Catalog</title>
					<sort_order>100</sort_order>
					<action>adminhtml/investorcatalog</action>
				</investorcatalog>
			</children>
		</catalog>
	</menu>
	<!-- Access control list configuration -->
	<acl>
		<resources>
			<all>
				<title>Allow Everything</title>
			</all>
			<admin>
				<children>
					<catalog>
						<children>
							<investorcatalog  translate="title">
								<!-- This is what will be displayed in the security module (Roles) -->
								<title>Investor Catalog</title>
								<sort_order>100</sort_order>
							</investorcatalog>
						</children>
					</catalog>
				</children>
			</admin>
		</resources>
	</acl>
</config>

The title Tag required you to have a Helper class, so you’ll need to create a helper class and add it to your module configuration file. The helper class will be extended from Mage_Core_Helper_Abstract class.

<?php
class Bippocoid_Investorcatalog_Helper_Data extends Mage_Core_Helper_Abstract
{

}

Update your configuration file:

	<modules>	
	.....
	</modules>
	<!-- After modules-->
	<global>
		<helpers>
			<investorcatalog>
				<class>Bippocoid_Investorcatalog_Helper</class>
			</investorcatalog>
		</helpers>
	</global>
	<admin>	
	.....
	</admin>

Now, refresh your magento cache and check the menu under the Catalog menu, you should see Investor Catalog menu. When you click it, it will display a plain html page containing Hello World!. But we need the admin look and feel, now we’ll move to the next step. Now change the indexAction method in the InvestorcatalogController.php class to:

	public function indexAction() {
		$this->loadLayout();
		$this->renderLayout();
		//echo "Hello World";
	}

This will provide you a basic empty admin page.

You can also review your if your access control list configuration is working by selecting System | Permissions | Roles. Edit your Administrators Roles and in the Role Resources tab, select Custom. Your’ll see ‘Investor Catalog’ under the Catalog.

Investor Catalog ACL

Investor Catalog ACL

4. Displaying Content

On the previous example, we could easily display ‘Hello world!’ using our controller. But because now we already have basic admin page, we will using another approach: using layout, block and template. If you’re not familiar with those concepts, I suggest you to read my previous post:Magento Layouts, Blocks and Templates Part 1 and Magento Layouts, Blocks and Templates Part 2.

So here I assume you’re already familiar with those concepts and only describe main steps:

  1. Add block configuration to etc/config.xml: So we will need a Block and update our configuration to include our block. We’ll add block tag inside our existing global tag.
    	<global>
    		<blocks>
    			<investorcatalog>
    				<class>Bippocoid_Investorcatalog_Block</class>
    			</investorcatalog>
    		</blocks>
    		....
    	</global>
    
  2. Add Block Class: Create a Block class that extends Mage_Core_Block_Template. The class will be located at: Block/Adminhtml. The class will provide a method getHello that return ‘Hello world!’ string.
    <?php
    class Bippocoid_Investorcatalog_Block_Adminhtml_Hello extends Mage_Core_Block_Template
    {
    	protected function getHello() {
    		return "Hello world!";
    	}
    } 
    
  3. Add layout configuration: We’ll need update our etc/config.xml to include our layout configuration file and specify our layout configuration file. Our layout configuration file will be named investorcatalog.xml. So we’ll add the layout config inside our root tag (config):
    <config>
    	....
    	<adminhtml>
    		<layout>
    			<updates>
    				<investorcatalog>
    					<file>investorcatalog.xml</file>
    				</investorcatalog>
    			</updates>
    		</layout>
    	</adminhtml>
    </config>
    
  4. Create layout configuration file: Our layout configuration file will be placed on folder /app/design/adminhtml/default/default/layout folder. It will be different from our standard frontend modules (the folder will be located on frontend folder instead of adminhtml folder). Our layout will use our previously created Adminhtml/Hello.php block and using a template file investorcatalog/hello.phtml. Our layout will have a handle admin/investorcatalog/index (see adminhtml_investorcatalog_index):
    <?xml version="1.0" ?>
    <layout version="0.1.0">
    	<adminhtml_investorcatalog_index>
    		<reference name="content">
    			<block type="investorcatalog/adminhtml_hello" name="investorcataloghello" template="investorcatalog/hello.phtml" />
    		</reference>
    	</adminhtml_investorcatalog_index>
    </layout>
    
  5. Create template file: Our template file will located on /app/design/adminhtml/default/default/template/investorcatalog. The template will only call getHello() from our Block. So this is the code:
    <?php echo $this->getHello(); ?>
    

Now we can click the Investor Catalog menu and the screen will display standard admin menu and a ‘Hello world!’ string.

You can download finished source code here.

Hello World on Admin Page

Hello World on Admin Page

1. Our Goal in this tutorial

Previous tutorial explaining how we create Controller and View. Now we move to the last part of Magento MVC’s architecture: Model. Model in Magento will responsible to persist the data into persistent media (our DBMS) and also responsible for fetch it again.

We’ll create a Contact Form and persist it to database. This form will gather customer information (name, phone and email) and customer messages. To demonstrate the fecth/query operation, we’ll also create a page that list all data that has been submitted by customers.

2. Preparing Module

Create a module named ‘Contactform’ under Bippocoid namespace. Check previous Helloworld Controller and Layout to tutorial if you don’t know how to create it.

For now, we’ll use our base code just like Helloworld Controller tutorial. Here are the artifact of our module:

  1. /app/etc/modules/Bippocoid_Contactform.xml
    <?xml version = "1.0"?>
    <config>
    	<modules>
    		<!--
    			format of tag below is: Namespace_Module
    			in our case namespace will be Bippocoid, and module name is Contactform
    		-->
    		<Bippocoid_Contactform>
    			<!--
    				Tag active: true will tell magento to enable our module in magento
    			-->
    			<active>true</active>
    			<codePool>local</codePool>
    		</Bippocoid_Contactform>
    	</modules>
    </config>
  2. /app/code/local/Bippocoid/Contactform/etc/config.xml
    <?xml version="1.0"?>
    <config>
    	<modules>
    		<!--
    		format of tag below is: Namespace_Module
    		in our case namespace will be Bippocoid, and module name is Helloworld
    		the name also reflect the location of our modules, that is
    		/app/code/local/Bippocoid/Contactform
    		-->
    		<Bippocoid_Contactform>
    			<version>1.0.0</version>
    		</Bippocoid_Contactform>
    	</modules>
    	<frontend>
    		<routers>
    			<!--
    			This tag normally will be named the same as our module name,
    			in our case, that is helloworld (all using lower case). 
    			But you can use another tag name as long as it's unique
    			-->
    			<contactform>
    				<use>standard</use>
    				<args>
    					<!-- this is our module name -->
    					<module>Bippocoid_Contactform</module>
    					<!-- myhelloworld will be used in URL to access our module -->
    					<frontName>mycontactform</frontName>
    				</args>
    			</contactform>
    		</routers>
    	</frontend>
    </config>
    
  3. /app/code/local/Bippocoid/Contactform/controllers/IndexController.php
    <?php
    class Bippocoid_Contactform_IndexController extends Mage_Core_Controller_Front_Action
    {
    	public function indexAction ()
    	{
    		echo 'Hello world!';
    	}
    }
    

Now check your /mycontactform URL and you should see ‘Hello world!’ on the browser. If it doesn’t, try to clear your Magento cache or check again your configuration.

3 Creating Form

We’ll create a form that contain 3 input text and 1 text area. This form will accept customer input and later we’ll persist it to our database. As described in previous tutorial about Layout, Block and Templates, creating a form also involves those 3 components.

3.1 Configuring Layouts

We’ll create a layout configuration file and attach it in our module by explicitly define in module configuration file (etc/config.xml). So we’ll add <layout> tag inside <frontend> tag and after <routers> tag. We’ll specify our layout configuration file, that is contactform.xml.

		<routers>...</routers>
		<!--
		Add layout configuration to our modules
		-->
		<layout>
			<updates>
				<!--
				This tag normally will be named the same as our module name,
				in our case, that is helloworld (all using lower case)
				-->
				<contactform>
					<file>contactform.xml</file>
				</contactform>
			</updates>
		</layout>

Now, we can create contactform.xml inside /app/design/frontend/default/default/layout/ folder. Our layout will have contactform_index_index handle and include a Block and input form template that we’ll create it later.

<?xml version="1.0" encoding="UTF-8"?>
<layout  version="0.1.0">
	<contactform_index_index>
		<reference name="content">
			<block type="contactform/contactform" name="contactform" output="toHtml" template="contactform/input.phtml"/>
		</reference>
	</contactform_index_index>
</layout>

3.2 Configuring Block

Based on our layout configuration file, our block we’ll have to make Block class with name Form. We also must update etc/config.xml to include our Block class. First, we’ll add <global> tag inside <config> tag and after <frontend> tag. Inside global tag we add Block class configuration.

		<frontend>...</frontend>
		<global>
			<blocks>
				<!--
				this tag is named with the name of our module
				-->
				<contactform>
					<!--
					this tell magento that we have Block Classes in folder /Bippocoid/Contactform/Block
					-->
					<class>Bippocoid_Contactform_Block</class>
				</contactform>
			</blocks>
		</global>

Next, we’ll create Contactform class that inherit Mage_Core_Block_Template class. This class located inside Block folder.

<?php
class Bippocoid_Contactform_Block_Contactform extends Mage_Core_Block_Template
{
	function getPostUrl(){
		return $this->getUrl('*/*/post');
	}
}

You can see that this class has getPostUrl() method. This method will be used by our template to fill action attribute in the HTML form tag. This method will return [your base magento address]/mycontactform/index/post.

3.3 Configuring Template

Based on our layout configuration file, our template is contactform/input.phtml, the template will be located in /app/design/frontend/default/default/template/input.phtml. The template will have HTML form tag and contain 3 input fields and 1 textarea.

<form action="<?php echo $this->getPostUrl()?>" method="post">
	<label for="cust_name">Customer Name</label>
	<input type="text" name="cust_name" id="cust_name"/>
	<br/>
	<label for="cust_email">Email</label>
	<input type="text" name="cust_email" id="cust_email"/>
	<br/>
	<label for="cust_phone">Phone</label>
	<input type="text" name="cust_phone" id="cust_phone"/>
	<br/>
	<label for="cust_messages">Messages</label>
	<textarea name="cust_messages" id="cust_messages"></textarea>
	<br/>
	<input type="submit" value="Submit"/>
</form>

3.4 Running the Form

Before we run the Form, we must tell controller to load our layout. That is we must update the indexAction() method.

	public function indexAction ()
	{
		$this->loadLayout();
		$this->renderLayout();
	}

Now, you can run /mycontactform URL and you’ll see the form.

4 Capturing User Input

Analyze generated HTML, specially from the form tag in our HTML. On line 1 in our template, we set the action URL by calling $this->getPostUrl() method. Generated html is [your base magento address]/mycontactform/index/post. So we’ll capture user input using thing URL, how we do that? By analyzing URL we now that we can do that by implementing postAction() method in the IndexController class in our module.

	public function postAction(){
		echo "<pre>";
		print_r($this->getRequest()->getPost());
		echo "</pre>";
	}

The method $this->getRequest()->getPost() will return an array containg all Post variable. So the code basically print all the user input and formatting it (using <pre> tag). Now you can try it by fill all form element and click Submit button. You should see all your input in the /mycontactform/index/post URL.

4 Creating Model

4.1 Creating Table

We want to persist user input to database. First, we’ll prepare a table to keep our customer contact form. Our table will have cust_contact_id as primary key and using auto increment. This ID will

CREATE TABLE IF NOT EXISTS `cust_contact` (
  `cust_contact_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `cust_name` varchar(200) DEFAULT NULL,
  `cust_email` varchar(100) DEFAULT NULL,
  `cust_phone` varchar(50) DEFAULT NULL,
  `cust_messages` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`cust_contact_id`)
) ;

4.2 Add Configuration

We need to add configuration to our etc/config.xml. The configuration will describe how our table (cust_contact)
will be mapped with the Model component. But mainly, the model configuration will contain:

  1. Location of our Model class, normally it will be on Model folder inside our Module folder, in our case it will be inside Contactform folder
  2. Table of our model and name abstraction of our model
  3. Component that will be used to read and write from our table

Add <modles> tag inside <global> tag and after <blocks> tag.

		<blocks>...</blocks>

		<!-- This tag contain model configuration -->
		<models>
			<!-- The name of tag below will be the sama as our module, that is contactform-->
			<contactform>
				<!-- This will specify where our model located -->
				<class>Bippocoid_Contactform_Model</class>
				<!-- This contain where our resource model will be located -->
				<resourceModel>contactform_mysql</resourceModel>
			</contactform>
			<contactform_mysql>
				<class>Bippocoid_Contactform_Model_Mysql</class>
				<entities>
					<!-- This tag will represent our table as PHP class/our entities in our 
					Bippocoid/Concactform/Model folder, later we'll have model class
					with the name 'Contacts' -->
					<contacts>
						<!-- This is where our physical table name  -->
						<table>cust_contact</table>
					</contacts>
				</entities>
			</contactform_mysql>
		</models>
		<!-- The plugin to read and write -->
		<resources>
			<!-- Connection to write -->
			<contactform_write>
				<connection>
					<use>core_write</use>
				</connection>
			</contactform_write>
			<!-- Connection to read -->
			<contactform_read>
				<connection>
					<use>core_read</use>
				</connection>
			</contactform_read>
		</resources>

The configuration tell us:

  1. <class>Bippocoid_Contactform_Model</class> implies that we have Model class located in Bippocoid/Contactform/Model folder. This model class will provide method to persist data to database and fetch it.
  2. Model class will use resource specified in <resourceModel> tag to persist data to database. Here the value of the tag is contactform_mysql. To find where the resource class located, we have to find <contactform_mysql> tag.
  3. The resource class is Bippocoid_Contactform_Model_Mysql and currently it only have one entity, that is contacts.
  4. contacts entity is mapped as cust_contact table. Later in the model we’ll refer the entity or model using /, here we’ll refer as contactform/contacts

4.3 Create required Model classes

Refer to our Model configuration, we’ll have 2 type of classes: Model (represent entity and provide mechanism for persistence) class and Resource Model (responsible to persist to specific DBMS and will provide method to Model class).

Model class is simply class that inherit Mage_Core_Model_Abstract. All persistent method is provided by this parent class. So this is our Model class:

<?php
class Bippocoid_Contactform_Model_Contacts extends Mage_Core_Model_Abstract
{
     public function _construct()
     {
         parent::_construct();
         $this->_init('contactform/contacts');
     }
}

The resource Model also simply class that inherit from Mage_Core_Model_Mysql4_Abstract class. All method related
persistent to Mysql DBMS is provide by this parent class.

<?php
class Bippocoid_Contactform_Model_Mysql_Contacts extends Mage_Core_Model_Mysql4_Abstract
{
     public function _construct()
     {
         $this->_init('contactform/contacts', 'cust_contact_id');
     }
}

4.4 Using Model

We already configure and create our Model. We’ll use our model to persist customer data to database. We’ll modify postAction() method in our IndexController class.

	public function postAction(){
		if ($this->getRequest()->getPost())
		{
			try {
				/* get array of data from user input
				 * the content of the array could be like this:
				 * Array (
				 *     [cust_name] => john
				 *     [cust_email] => john@codeautomate.org
				 *     [cust_phone] => 12345678
				 *     [cust_messages] => hi there!
				 * )
				 * We've make name of the field (cust_name, etc) same as the name of the
				 * column in the cust_contact table
				 */
				$post = $this->getRequest()->getPost();
				
				// get model 
				$contactModel = Mage::getModel('contactform/contacts');
				
				/*
				 *  The addData method add $post array to the Model.
				 *  This will fill array element to field in the Model
				 */ 
				$contactModel->addData($post);
				
				// Persist the model to database
				$contactModel->save();

				$this->_redirect('*/*/');
			} catch (Exception $e){
				$this->_redirect('*/*/');
			}
		}
	}

Now you can try again to submit customer contact and then check your cust_contact table content.

5 Fetch Data From Database

We’ve save customer messages in database. The data will be meaningless if we can’t read it again right? So, it’s time to fetch it and display to our browser. We’ll need one more action method in our controller and create a new template to display our data.

5.1 Preparing the list screen

The basic step will be the same as creating input form:

  1. Update layout configuration file (contactform.xml), add handle for list action. Our block will reuse Contactform Block class and require new template: list.phtml
    	<contactform_index_list>
    		<reference name="content">
    			<block type="contactform/contactform" name="contactlist" output="toHtml" template="contactform/list.phtml"/>
    		</reference>
    	</contactform_index_list>
    		
  2. Create a template (list.phtml) and add HTML table skeleton inside it.
    <table>
    	<thead>
    		<tr>
    			<th>Customer Name</th>
    			<th>EMail</th>
    			<th>Phone</th>
    			<th>Messages</th>
    		</tr>
    	<thead>
    </table>
    		
  3. Add listAction() method to our IndexController class.
    	public function listAction(){
    		$this->loadLayout();
    		$this->renderLayout();
    	}
    		

Now, you can check if your list screen is ready using /mycontactform/index/list URL. It should display table header, if it’s not try to refresh your Magento cache or check again you configuration.

5.2 Fetch the data

5.2.1 Create Collection class

We’ll fetch all available customer messages in our cust_contact table. There are special component available in Magento that we can use to collect available data, that is Collection. We’ll handle collection of Contacts, and in Magento we’ll neet to create Collection class that inherit Mage_Core_Model_Mysql4_Collection_Abstract class. The location of our class is at /app/code/local/Bippocoid/Contactform/Model/Mysql/Contacts. We’ll need to create Contacts folder (yes, the name is the same as our entity name: Contacts) inside Mysql folder.

<?php
class Bippocoid_Contactform_Model_Mysql_Contacts_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract
 {
     public function _construct()
     {
         parent::_construct();
         $this->_init('contactform/contacts');
     }
}

5.2.2 Using collection

After creating it, we’ll be able to use it in our Model. We’ll create a method that return Contacts Collection inside our Contactform Block. Later, the Collection will be accessed by our our list.phtml template and displaying it to browser. Add getContacts() method to our Contactform Block class.

	function getContacts(){
		return Mage::getModel('contactform/contacts')->getCollection();
	}

As you can see, we can use our newly created Collection class in our model just by using $model->getCollection() method. This will return Collection that inherit IteratorAggregate class, so we can iterate it using foreach() statement. The collection it self will contain our Contacts Model.

5.2.2 Iterating collection

So, we already have method in our block that will return Contacts Collection. We’ll iterate it in our list.phtml. We’ll using simple iteration and echo a row for each Contacts Model. Our list.phtml we’ll be look like this.

<table>
	<thead>
		<tr>
			<th>Customer Name</th>
			<th>EMail</th>
			<th>Phone</th>
			<th>Messages</th>
		</tr>
	<thead>
		<?php
		foreach($this->getContacts() as $val){
			echo "<tr>";
			echo "<td>".$val->getData('cust_name')."</td>";
			echo "<td>".$val->getData('cust_email')."</td>";
			echo "<td>".$val->getData('cust_phone')."</td>";
			echo "<td>".$val->getData('cust_messages')."</td>";
			echo "<tr>";
		}
		?>
</table>

5.3 Putting all together

Now, you have /mycontactform/index/list that will display all the data. You can check the URL and check if your source code is working. You can modify your postAction() method so that it will redirect to listAction() controller by changing this line:

				$this->_redirect('*/*/');

to

				$this->_redirect('*/*/list');

You can download full source code of this tutorial here.

In this tutorial, I will assume that you’ve basic understanding on Magento controllers. If this is your first tutorial to Magento development, I suggest that you read my previous post: Magento Hello World – Magento Controller.

In previous post, I mentioned that Magento uses MVC architecture and demonstrated how we can create simple controller. Here, I will demonstrated how we can create View component.

Basic Concepts

There are 3 concepts in Magento related to View: Layouts, Blocks dan Template.

  1. Layout describe what component will be shown (visible element) or included in a page (invisible element), for example: checkout box, search box, include java script in header or might be a form. For each request (for example: /customer/account/login/), Magento will search layout information (in the layout configuration file) and then build every component for the pages. Each component is represented as Block. So layout is only decided what block that must be loaded in specific request.
  2. Block is component (is PHP class) that render real HTML code to browser. Common action for example: read from model and output to HTML.
  3. Template comes to help the Blocks. Output raw HTML in PHP class is hard, it will be simpler if we have HTML code and calls method. So, instead of output from Block class, Magento will use template to output HTML code. Template could call method available in blocks inside it. So, for example if Block class has method: getProductDesc(), inside template we could used those method by using keyword $this:
    <strong><?php echo $this-->getProductDesc() ?></strong>

Details of The Concepts and Implementing It

In this tutorial, we’ll use artifact from my previous post Magento Hello World – Magento Controller. Finished hello world artifact can be download here.

1. Layout Step by Step

1.1 Make our Hello World request to load the layout information

As described before, Magento will load specific layout based on specific reqeuest. There are 2 basic steps: tell magento where our layout configuration is located and described the content of our layout.

To tell where the location of our module configuration file, we’ll edit config.xml located in: /app/code/local/Bippocoid/Helloworld/etc/config.xml. Add <layout> tag inside <frontend> tag and after <routers> tag.

<!--
Add layout configuration to our modules
-->
<layout>
	<updates>
		<!--
		This tag normally will be named the same as our module name,
		in our case, that is helloworld (all using lower case)
		-->
		<helloworld>
			<file>helloworld.xml</file>
		</helloworld>
	</updates>
</layout>

This will tell Magento that we have helloworld.xml containing layout configuration file. So where is this file located? Magento will search the layout in the /app/design/frontend/default/default/layout folder. If it’s not exists, Magento will search in the /app/design/frontend/base/default/layout folder. I suggest you to read intro to layouts and build the theme. For now, create helloworld.xml in the /app/design/frontend/default/default/layout folder (create the folder if it’s not yet exist). Create empty layout tag inside the file.

<?xml version="1.0" encoding="UTF-8"?>
<layout version="0.1.0">
</layout>

Now, we have layout configuration file and we will tell Magento how our /myhelloworld/index/index should be. Now refresh the configuration cache to allow Magento re-read configuration file. Or you can disable cache in the admin menu (System | Cache Management). Now, continue to next step.

1.2 Make controller to use the layout

Layout configuration file already created. If we access /myhelloworld/index/index, the page still doesn’t change. That’s because we need to explicitly tell controller to load the layout configuration file. We’ll modify indexAction in the IndexController class.

	public function indexAction ()
	{
		//echo 'Hello world!';
		
		// Load layout
		$this->loadLayout();

		// Render layout
		$this->renderLayout();
	}

Now, you can access /myhelloworld/index/index and logically it should be empty because the configuration is still empty, but you’ll see the default 3 column layout is rendered to your browser, how this magically happened?

Magento has default layout that act as default layout for almost every layout in page. You can see the this default layout configuration file in /app/design/frontend/base/default/page.xml. This is base layout that will be used on almost all pages.

    <default translate="label" module="page">
        <label>All Pages</label>
        <block type="page/html" name="root" output="toHtml" template="page/3columns.phtml">
         .....

In fact, right now you still not able to find out whether your layout configuration file is working or not. So we need to add something to our layout configuration, for example remove the right column in default layout. Our helloworld.xml will be like this.

<?xml version="1.0" encoding="UTF-8"?>
<layout  version="0.1.0">
	<default>
	<!-- 
	Remove right column using remove tag, the 
	-->
        <remove name="right"/>
	</default>
</layout>

So this is how layout works:

  1. Magento will search <default> tag in the page.xml and prepare layout.
  2. Magento will search <default> tag in the helloworld.xml and update previous default tag, in our case it will remove right column.

Now you can open again /myhelloworld/index/index and see that right column already missing. At this state helloworld.xml configuration is working.
The default tag inside helloworld.xml will affect to all request using helloworld.xml configuration. But if how if we want to use specific configuration for specific action, for example we have onecolAction that will only display 1 column page when we request /myhelloworld/index/onecol URL. So we need to remove left column (right column already removed in the default tag).
Here we can use Handle. Let’s add myhelloworld_index_onecol inside <layout> tag in the helloworld.xml file.

	<default>...</default>
	<!--
		Handle will be named with format: ModuleName_ControllerName_MethodName, all in lower case
		This handle will be processed when magento accept /myhelloworld/index/onecol request  
	-->
	<helloworld_index_onecol>
		<remove name="left"/>
	</helloworld_index_onecol>

And don’t forget to add onecolAction in the IndexController class:

	public function onecolAction ()
	{
		$this->loadLayout();
		$this->renderLayout();
	}

So, if you access /myhelloworld/index/index, you’ll see that left and right column is removed.
So this is how handle works:

  1. Magento will search <default> tag in the page.xml and prepare layout.
  2. Magento will search <default> tag in the helloworld.xml and update previous default tag, in our case it will remove right column.
  3. Magento will search handle tag in the helloworld.xml based on our request and update previous default tag, in our case it will remove left column.

1.3 More on layouts

As previously mentioned on how handle works:

Magento will search <default> tag in the page.xml and prepare layout.

Actually, it not only load page.xml file, but also load another Magento layout configuration such as: customer.xml, newsletter.xml, checkout.xml, etc. If you see “left” block definition in the page.xml file, you’ll see it’s only contain definition only:

            <block type="core/text_list" name="left" as="left" translate="label">
                <label>Left Column</label>
            </block>

And when you request /myhelloworld/index/index, you’ll see Newsletter box. Where does it came from? Magento will load newsletter.xml and add Newsletter box in the left column.

    <default>

        <!-- Mage_Newsletter -->
        <reference name="left">
            <block type="newsletter/subscribe" name="left.newsletter" template="newsletter/subscribe.phtml"/>
        </reference>

    </default>

Newsletter block is defined in the <default> tag, so it will be loaded in almost every page in Magento. If you need to remove the Newsletter box, you only need to remove <default> tag in the newsletter.xml.

If you edit the file directly in the /app/design/frontend/base/default/layout folder, Magento will override your modification when you are upgrading to newer version. So if you want to persist it, you must create copy the file in the /app/design/frontend/default/default/layout folder.

I hope you already understand how layout works in Magento. You can download source code at this point here.

Next: Magento Layouts, Blocks and Templates Part 2

2. Blocks and Templates Step by Step

In previous article, we’ve already learned that layout is responsible to arrange every component (block) that make up a page. Arrange here means it responsible to decide what components that could be exist in a request and position of every components. The content of the component is responsibility of a Block. Block it self is a PHP class, so to make easy to compose HTML, it will use a template. Template merely HTML page containing inline PHP code. Template access method in a Block using $this variable.

In this tutorial, we’ll combine Controller, Layout, Block and Template. We’ll create fromblock method in the Controller that use Block and Template.

2.1 Update module configuration

We need to tell Magento that we have Block classes. This is done by updating config.xml (/app/code/local/Bippocoid/Helloworld/etc). Add <global> tag inside <config> tag after <frontend> tag.

	<frontend>...</frontend>
	<global>
		<blocks>
			<!-- this tag is named with the name of our module -->
			<helloworld>
				<!-- this tell magento that we have Block Classes in folder /Bippocoid/Helloword/Block -->
				<class>Bippocoid_Helloworld_Block</class>
			</helloworld>
		</blocks>
	</global>

Now, create Block folder inside /app/code/local/Bippocoid/Helloword folder.

2.2 Create Block Classes

Our Block will provide single method sayHello(). This method will return ‘Hello, this is from block!’ string. Later, this method will be called from template.

Now, create Hello.php file and class class with the name: Bippocoid_Helloworld_Block_Hello. Note that the name of the class also reflect the location of the class, that is: <Namespace>_<Module name>_<Block folder>_<File Name>. Class naming between Block and Controller is different (it’s not consistent). The controller class doesn’t include Controller folder, so the full name format of our IndexController class is <Namespace>_<Module name>_<File Name>.

The Block class will extends Mage_Core_Block_Template class. The parent class provides mechanism to load template and provide toHtml() method that echo content of template file.

This is Block Class /app/code/local/Bippocoid/Helloword/Hello.php.

<?php
class Bippocoid_Helloworld_Block_Hello extends Mage_Core_Block_Template
{
	function sayHello(){
		return 'Hello, this is from block!';
	}
}

2.3 Create template file

Now, the template will call sayHello() method and echo HTML code. The layout will responsible to place output of the template based on layout configuration. We’ll put our template in /app/design/default/default/helloworld/hello.phtml (create the folders if not exist).

<strong><?php echo $this->getHelloWords(); ?></strong>

As you can see, template file call sayHello() method using $this->sayHello().

2.4 Create new methode in controller

Create fromblockAction in the IndexController class wich will load and render layout.

	public function fromblockAction ()
	{
		$this->loadLayout();
		$this->renderLayout();
	}

2.5 Updating layout configuration

Add handle for newly created formblockAction. Inside the handle, add <block> tag that will include our previously created Block and Template.

	<helloworld_index_fromblock>
		<!-- this tag will add 'content' block with our new block -->
		<reference name="content">
			<block type="helloworld/hello" name="hello" output="toHtml" template="helloworld/hello.phtml"/>
		</reference>
	</helloworld_index_fromblock>

Inside <helloworld_index_fromblock> tag there is <reference> tag with name attribute that has “content” value. What is that mean?. This mean that we will update block tag that has attribute name=”content”. You can open page.xml (/app/design/base/default/layout/page.xml). Inside <default> tag, find this tag:

            <block type="core/text_list" name="content" as="content" translate="label">
                <label>Main Content Area</label>
            </block>

The “content” block it self located in the block that has attribute name=”root”. There are others block under “root” like: “head”, “left”, “right” and “footer”. If you want to know where those block located in the page, just open the “root”‘s template: page/3columns.phtml:

<html>
<head>
<?php echo $this->getChildHtml('head') ?>
</head>
<body<?php echo $this->getBodyClass()?' class="'.$this->getBodyClass().'"':'' ?>>
<?php echo $this->getChildHtml('after_body_start') ?>



<div class="wrapper">
    <?php echo $this->getChildHtml('global_notices') ?>



<div class="page">
        <?php echo $this->getChildHtml('header') ?>



<div class="main-container col3-layout">



<div class="main">
                <?php echo $this->getChildHtml('breadcrumbs') ?>



<div class="col-wrapper">



<div class="col-main">
                        <?php echo $this->getChildHtml('global_messages') ?>
                        <?php echo $this->getChildHtml('content') ?>
                    </div>






<div class="col-left sidebar"><?php echo $this->getChildHtml('left') ?></div>



                </div>






<div class="col-right sidebar"><?php echo $this->getChildHtml('right') ?></div>



            </div>



        </div>



        <?php echo $this->getChildHtml('footer') ?>
        <?php echo $this->getChildHtml('before_body_end') ?>
    </div>



</div>



<?php echo $this->getAbsoluteFooter() ?>
</body>
</html>
  1. head is located in the <head> tag in the html. You know it when the template calling block under “root” using echo $this->getChildHtml(‘head’). If you see on the page.xml, the “head” block merely include javascript and css artifact.
  2. left is located inside div that has class=”col-left sidebar”. By its name we know that it’s the left side of page.
  3. content is located int the div that has class=”col-main”. By its name we know that it’s the main content of the page.

So, by refer to the “content” block, it’s mean that we want to add the output of previously created block and template in the main content of page.

For <block> tag inside <reference> tag, here are the explanations:

  1. type=helloworld/hello: this will load Hello class block in Helloworld module.
  2. name=hello: you can assign the name that describe the block, for now this only used to make our block readable.
  3. output=toHtml: layout will call toHtml() method of Hello class to get block output, toHtml() method is provided by Hello parent class, that is Mage_Core_Block_Template.
  4. template=helloworld/hello.phtml: template that will be used located in folder app/design/frontend/default/default/template/helloworld/hello.phtml.

2.6 Check all the results

Open the browser and navigate to /myhelloworld/index/fromblock. You’ll see the string in the main area of the page. You can view the source and match it against the block and the template (3columns.phtml and hello.phtml).

You can download final source code here.

Learning Magento is hard in the beginning, but as soon you know how it all works, it will be easy for you to create or modify modules in Magento. Magento build using MVC architecture. Application build using MVC will be devided into 3 main component: Model (something related to data), View (how data will be displayed on the screen), and Controller (decide what application must do).

This tutorial will help you understand how Magento controller works. You’ll learn to create basic Magento configuration and write controller code. The goal of this tutorial is simple, you’ll echo ‘Hello world’ into the user’s browser.

First, we’ll create components that will be needed in our tutorial:

1. Module configuration. Configuration contain name of our module (that also implies the location of our module), URL that can be used to access the module and status of our module (enable or disable). Those settings located in 2 different files. The configuration will also tell Magento that it need to check our module to process specific request (URL).

2. Controller code. The code will perform main process: echo the ‘Hello world’

Magento Configuration

We’ll use Hellowolrd as the module name. In Magento, we need to place our module into namespace, the namespace generally will be maker of the module. For now, we’ll use Bippocoid as the namespace. As mentioned before, we’ll create 2 configuration files:

1. Module information and settings

For now, this will only contain URL path to our module. Create file config.xml in:

/app/code/local/Bippocoid/Helloworld/etc/config.xml
<?xml version="1.0"?>
<config>
	<modules>
		<!--
			format of tag below is: Namespace_Module
			in our case namespace will be Bippocoid, and module name is Helloworld
			the name also reflect the location of our modules, that is
			/app/code/local/bippocoid/helloworld
		-->
		<Bippocoid_Helloworld>
			<version>1.0.0</version>
		</Bippocoid_Helloworld>
	</modules>
</config>

On fresh Magento installation, you’ll only see 2 directories under /app/code: core and community. Core folder containing core modules used by magento and community folder containing modules developed by community. You’ll create local folder and used modules privately.

2. Enable module in Magento

We already create a module in Magento, and we need to tell Magento to load our module. Create file Bippocoid_helloworld.xml in

/app/etc/modules/Bippocoid_helloworld.xml
<?xml version = "1.0"?>
<config>
	<modules>
		<!--
			format of tag below is: Namespace_Module
			in our case namespace will be Bippocoid, and module name is Helloworld
		-->
		<Bippocoid_Helloworld>
			<!--
				Tag active: true will tell magento to enable our module in magento
			-->
			<active>true</active>
			<codePool>local</codePool>
		</Bippocoid_Helloworld>
	</modules>
</config>

You can validate the modules you’ve created using admin page and accessing menu: System | Configuration | Advance. You’ll see the module, Bippocoid_Helloworld installed in the first row.

If you don’t see it, it might be caused by Magento cache. Magento cache all configuration files, so if you change it, you might need to update it manually in: System | Cache Management, and select cache type (configuration) that you want to refresh.

Creating Controller

1. Creating Controller Class

We’ll create controller that will echo ‘Hello world!’ to user’s screen. Controller merely PHP class that is extended from Mage_Core_Controller_Front_Action class. Create file IndexController.php in:

/app/code/local/Bippocoid/Helloworld/controllers/IndexController.php
<?php
class Bippocoid_Helloworld_IndexController extends Mage_Core_Controller_Front_Action
{
	public function indexAction ()
	{
		echo 'Hello world!';
	}
	 
}

Magento use class name that also reflect it’s path. So if you see controller class above, you’ll see the pattern:

Bippocoid_Helloworld_IndexController located in /app/code/local/Bippocoid/Helloworld/controllers/IndexController.php

Enable configuration to intercept URL

We need to tell Magento that our module will handle specific URL. We’ll put configuration in our config.xml, we’ll put tag below tag.

<?xml version="1.0"?>
<config>
	<modules> 
        ...
	</modules>
	<frontend>
		<routers>
			<!-- 
				This tag normally will be named the same as our module name, 
				in our case, that is helloworld (all using lower case)
			-->
			<helloworld>
				<use>standard</use>
				<args>
					<!-- this is our module name -->
					<module>Bippocoid_Helloworld</module>
					<!-- myhelloworld will be used in URL to access our module -->
					<frontName>myhelloworld</frontName>
				</args>
			</helloworld>
		</routers>
	</frontend>
</config>

Don’t forget to update Magento cache if you still enable cache for configuration.

As you see in above configuration, it tells magento to load controller in Bippocoid_Helloworld module and we can access it using myhelloworld URL (spcified in frontName tag. So we can access it using:

http://localhost/[magento base url]/index.php/myhelloworld

So that is, magento will echo Hello world!. Actually, the full path of our URL will be:

http://localhost/[magento base url]/index.php/myhelloworld/index/index

index will be used as default if it not specified in our URL.

The pattern is:

index.php/frontName/controllerName/methodName

frontName: spesified in our confix.xml file

controllerName: the class name of our controller, if we have IndexController, the controller name is index, if we have ShopController class the controller name is shop.

methodName: the method name inside controller class. If we have indexAction, method name is index, and if we have checkoutAction, method name is checkout.

You can download full source code here.

Next: Magento Layouts, Blocks and Templates

Axis2 and rampart module make digital signature using web services easier. But there’s a case when you want to make a reliable system and making backup channel to send your messages. Your SOAP messages might not be able to sent via http, JMS, or even email, you must to save it on usb disk and bring it to your designated partner.

The codes below demonstrate how to sign xml documents, and not only sign it, but wrap it using SOAPEnvelope tag. The end result should be the same as the document generated by axis2 and rampart module, except it will not include TimeStamp tag. So, you’ll have reliable data format and same signature mechanism to communicate with your business partner system.

You’ll need wss4j library to run this code and keystore.jks containing private key to sign the document.

This is what you do for signing process:

  1. Load the xml documents and wrap it with SOAPEnvelope tag.
  2. Set configuration for WSHandler. You can choose to send your Public Key by setting parameter WSHandlerConstants.SIG_KEY_ID to “DirectReference”. Later, the server just to check whether your Subject DN is valid and certificate was published by trusted auhtority.
  3. Persist your signed documents to file.

For validating signature process, this is what you do:

  1. Load your signed XML Document (wrapped in SOAP Envelope), in the code below, I just use the Document object produced by signing process.
  2. Set configuration (crypto configuration). The crypto configuration will be used if your SOAP messages didn’t include certificate (by lookup certificate loaded in java keystore).
  3. You can get attached binary certificate by using WSSecurityEngineResult.TAG_BINARY_SECURITY_TOKEN tag in the WSSecurityEngineResult object.
So here is the code:
package org.codeautomate.tutorial.digsig;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSSConfig;
import org.apache.ws.security.WSSecurityEngine;
import org.apache.ws.security.WSSecurityEngineResult;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.common.CustomHandler;
import org.apache.ws.security.components.crypto.Crypto;
import org.apache.ws.security.components.crypto.CryptoFactory;
import org.apache.ws.security.handler.RequestData;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class DigitalSignatureDemo {

	/**
	 * Load raw texfile containing xml document and wrap it using soap envelope
	 * @param filePath
	 * @return SOAPMessages containing
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws SOAPException
	 */
	public SOAPMessage loadDocument(String filePath)
			throws ParserConfigurationException, SAXException, IOException,
			SOAPException {
		DocumentBuilderFactory docFactory = DocumentBuilderFactory
				.newInstance();
		docFactory.setNamespaceAware(true);

		DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
		Document xmlDoc = docBuilder.parse(new File(filePath));

		// You can load XML document easily by using axis2 lib
		// XMLUtils.newDocument(new FileInputStream(filePath));

		// Wrap xml document with SOAPEnvelope
		SOAPMessage soapMessage = MessageFactory.newInstance().createMessage();
		soapMessage.getSOAPBody().addDocument(xmlDoc);

		return soapMessage;
	}

	/**
	 * Sign SOAPMessage
	 * @param soapEnvelope
	 * @return signed SOAPMessage
	 * @throws SOAPException
	 * @throws TransformerException
	 * @throws WSSecurityException
	 */
	public Document signSOAPMessage(SOAPMessage soapEnvelope)
			throws SOAPException, TransformerException, WSSecurityException {
		Source src = soapEnvelope.getSOAPPart().getContent();
		TransformerFactory transformerFactory = TransformerFactory
				.newInstance();
		Transformer transformer = transformerFactory.newTransformer();
		DOMResult result = new DOMResult();
		transformer.transform(src, result);
		Document doc = (Document) result.getNode();

		final RequestData reqData = new RequestData();
		java.util.Map msgContext = new java.util.TreeMap();
		msgContext
				.put(WSHandlerConstants.ENABLE_SIGNATURE_CONFIRMATION, "true");
		msgContext.put(WSHandlerConstants.SIG_PROP_FILE, "sender.properties");

		// Set this property if you want client public key (X509 certificate) sent along with document
		// server will check signature using this public key
		msgContext.put(WSHandlerConstants.SIG_KEY_ID, "DirectReference");
		msgContext.put("password", "keystore");
		reqData.setMsgContext(msgContext);
		reqData.setUsername("clientca3");

		final java.util.List actions = new java.util.ArrayList();
		actions.add(new Integer(WSConstants.SIGN));
		CustomHandler handler = new CustomHandler();

		// sign document
		handler.send(WSConstants.SIGN, doc, reqData, actions, true);

		return doc;
	}

	/**
	 * Save Document to file
	 * @param doc Document to be persisted
	 * @param file output file
	 * @throws FileNotFoundException
	 * @throws TransformerException
	 */
	public void persistDocument(Document doc, String file)
			throws FileNotFoundException, TransformerException {
		TransformerFactory transformerFactory = TransformerFactory
				.newInstance();
		Transformer transformer = transformerFactory.newTransformer();

		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(file);
			DOMSource source = new DOMSource(doc);
			StreamResult rslt = new StreamResult(fos);
			transformer.transform(source, rslt);
		} finally {
			try {
				fos.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Check signed documents
	 * @param signedDoc
	 * @throws WSSecurityException
	 * @throws FileNotFoundException
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 * @throws SOAPException
	 */
	public void checkSignedDoc(Document signedDoc)
			throws WSSecurityException, FileNotFoundException,
			ParserConfigurationException, SAXException, IOException,
			SOAPException {
		Crypto crypto = CryptoFactory.getInstance("receiver.properties");
		WSSecurityEngine engine = new WSSecurityEngine();
		WSSConfig config = WSSConfig.getNewInstance();
		config.setWsiBSPCompliant(false);
		engine.setWssConfig(config);

		// process verification
		List res = engine.processSecurityHeader(signedDoc,
				null, null, crypto);

		for (WSSecurityEngineResult ers : res) {
			if (ers.get(WSSecurityEngineResult.TAG_BINARY_SECURITY_TOKEN) != null) {

				// You can get certificate sent by client here
				System.out.println(ers
						.get(WSSecurityEngineResult.TAG_BINARY_SECURITY_TOKEN));

				// You can get certificate info (used to sign document) here
				X509Certificate cert = (X509Certificate) ers
						.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
				System.out.println(cert.getSubjectDN());
			}
		}
	}

	public static void main(String...args) throws ParserConfigurationException, SAXException, IOException, SOAPException, TransformerException, WSSecurityException{
		DigitalSignatureDemo digsigDemo = new DigitalSignatureDemo();

		System.out.println("Creating SOAPMessages from xml file");
		SOAPMessage msg  = digsigDemo.loadDocument("/Users/hervin/Documents/Eclipse/TestApps/WSS4JXMLDigSignature/src/sourcedoc.xml");

		System.out.println("Sign document");
		Document signedDoc = digsigDemo.signSOAPMessage(msg);

		System.out.println("Check generated signature");
		digsigDemo.checkSignedDoc(signedDoc);

		System.out.println("Persist signed document to file");
		digsigDemo.persistDocument(signedDoc, "/Users/hervin/Documents/Eclipse/TestApps/WSS4JXMLDigSignature/src/signeddoc.xml");

		System.out.println("Process finished");
	}
}

This is sender.properties and receiver.properties file (both are identical).

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=keystore
org.apache.ws.security.crypto.merlin.keystore.alias=clientca3
org.apache.ws.security.crypto.merlin.keystore.file=keystore.jks
,

Currently, I’m developing web services using Axis2 and Rampart. I need Rampart module to get my SOAP messages digitally signed. Signature and verification of the signature was done. Web services will be deployed on closed networks and 2 connected parties will exchange their root CA (and intermediate CA) manually. The CAs will be stored on java keystore (JKS), and everytime client call a sevice, server will check client certificate against available CA certificate stored in JKS file.
There is a good tutorial on this case, but still, the code is not working on my computer. So, I tried to manually develop my own code to verify the chain.

The main process is here:

  1. Get client certificate that will be checked (client certificate in my case will be sent along with the signed message)
  2. Read all certificate in the JKS file and find issuer CA for client certificate in the step 1.
  3. If matched CA in the step 2 is not root CA (so, this is intermediate CA), then try again to find issuer CA for this intermediate CA (repeat this step until the program find root CA)
  4. If step 2 or 3 fail, then client is not valid certificate

I’ve attached keystore.jks file so you can test the class. For convenience, the client certificate will be came from certificate in the keystore file. Also, you can see the structure of the certificate here.

I think the code below is self explained :)


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;

import org.apache.commons.io.IOUtils;

public class CertChainValidator {
	/**
	 * Validate keychain
	 * @param client is the client X509Certificate
	 * @param keyStore containing all trusted certificate
	 * @return true if validation until root certificate success, false otherwise
	 * @throws KeyStoreException
	 * @throws CertificateException
	 * @throws InvalidAlgorithmParameterException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 */
	public static boolean validateKeyChain(X509Certificate client,
			KeyStore keyStore) throws KeyStoreException, CertificateException,
			InvalidAlgorithmParameterException, NoSuchAlgorithmException,
			NoSuchProviderException {
		X509Certificate[] certs = new X509Certificate[keyStore.size()];
		int i = 0;
		Enumeration<String> alias = keyStore.aliases();

		while (alias.hasMoreElements()) {
			certs[i++] = (X509Certificate) keyStore.getCertificate(alias
					.nextElement());
		}

		return validateKeyChain(client, certs);
	}

	/**
	 * Validate keychain
	 * @param client is the client X509Certificate
	 * @param trustedCerts is Array containing all trusted X509Certificate
	 * @return true if validation until root certificate success, false otherwise
	 * @throws CertificateException
	 * @throws InvalidAlgorithmParameterException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 */
	public static boolean validateKeyChain(X509Certificate client,
			X509Certificate... trustedCerts) throws CertificateException,
			InvalidAlgorithmParameterException, NoSuchAlgorithmException,
			NoSuchProviderException {
		boolean found = false;
		int i = trustedCerts.length;
		CertificateFactory cf = CertificateFactory.getInstance("X.509");
		TrustAnchor anchor;
		Set anchors;
		CertPath path;
		List list;
		PKIXParameters params;
		CertPathValidator validator = CertPathValidator.getInstance("PKIX");

		while (!found && i > 0) {
			anchor = new TrustAnchor(trustedCerts[--i], null);
			anchors = Collections.singleton(anchor);

			list = Arrays.asList(new Certificate[] { client });
			path = cf.generateCertPath(list);

			params = new PKIXParameters(anchors);
			params.setRevocationEnabled(false);

			if (client.getIssuerDN().equals(trustedCerts[i].getSubjectDN())) {
				try {
					validator.validate(path, params);
					if (isSelfSigned(trustedCerts[i])) {
						// found root ca
						found = true;
						System.out.println("validating root" + trustedCerts[i].getSubjectX500Principal().getName());
					} else if (!client.equals(trustedCerts[i])) {
						// find parent ca
						System.out.println("validating via:" + trustedCerts[i].getSubjectX500Principal().getName());
						found = validateKeyChain(trustedCerts[i], trustedCerts);
					}
				} catch (CertPathValidatorException e) {
					// validation fail, check next certifiacet in the trustedCerts array
				}
			}
		}

		return found;
	}

	/**
	 *
	 * @param cert is X509Certificate that will be tested
	 * @return true if cert is self signed, false otherwise
	 * @throws CertificateException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchProviderException
	 */
	public static boolean isSelfSigned(X509Certificate cert)
			throws CertificateException, NoSuchAlgorithmException,
			NoSuchProviderException {
		try {
			PublicKey key = cert.getPublicKey();

			cert.verify(key);
			return true;
		} catch (SignatureException sigEx) {
			return false;
		} catch (InvalidKeyException keyEx) {
			return false;
		}
	}

	public static void main(String... args) throws KeyStoreException,
			NoSuchAlgorithmException, CertificateException, IOException,
			InvalidAlgorithmParameterException, NoSuchProviderException {
		String storename = "keystore.jks";
		char[] storepass = "keystore".toCharArray();

		KeyStore ks = KeyStore.getInstance("JKS");
		FileInputStream fin = null;
		try {
			fin = new FileInputStream(storename);
			ks.load(fin, storepass);
			if (validateKeyChain(
					(X509Certificate) ks.getCertificate("clientint21int2"), ks)) {
				System.out.println("validate success");
			} else {
				System.out.println("validate fail");
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(fin);
		}

	}

}
,

There are several issues related to (electronic) secure message exchange: confidentiality, authentication, non-repudiation, availibility, etc. Here I want to highlight 2 basic point in secure message exchange: confidentiality and non repudiation. Confidentiality mean that information is accessible only to those authorized to have access. Non repudiation mean that someone who has sent information at later cannot deny having sent it.

First, me must know what is public key and private key in term of asymmetric encryption. The generic term, “key” mean something  is used to lock (encrypt document) and unlock (decrypt document). In symmetric encryption, you’ll use the same key for encryption and decryption. Using asymmetric encryption, you’ll use different key for encryption and decryption: one key for encryption (usually public key) and one key for decryption (usually private key). Private key will be always kept secret but public key will be available for public.

Private key is also used to create digital signature. Digital signature algorithm take 2 parameters: document and private key and then create digital signature. Other party verify the signature by running digital signature verifier who takes 3 parameter: document, digital signature, and public key from party who send information.

Here is the illustration on how confidentiality and non repudiation implemented using public key and private key.

secure messages exchange using public and private key

secure messages exchange using public and private key

Bromo want to send document to Rinjani. Both will share their public key (the private key will be kept secret). Bromo will create digital signature from the document using his private key. He will also create random key and use it to encrypt document. The random key then encrypted using Rinjani public key. Bromo will send 3 type of messages to Rinjani: encrypted document, encrypted key and digital signature to Rinjani.

Rinjani accept those 3 type of messages. Using his private key, he will decrypt the key and using the key to decrypt the encrypted document. The document then verified against the digital signature using Bromo’s public key.

Creating Public and Private Key

First, Bromo and Rinjani must create public and private key. Bromo will send his public key to Rinjani and ensure that the public key received by Rinjani is issued by Bromo. At other side, Rinjani will do the same thing. Ensuring that the public key is come from valid entity is part of the security it self. We could use the certificate authority (CA) to ensure that public key is issued by valid entity.

Preparing The Messages at Bromo (Sender)

  1. Bromo will use his private key to create digital signature of the document. It’s infeasible for other party to create the same digital signature of the document without having the private key owned by Bromo. Using this mechanism, other can ensure that the document is issued by Bromo.
  2. Bromo create random key, the key will be used to encrypt the document. The key will only be used for one session. Using AES algorithm, the key length (number of bytes) will decide how strong the key is.
  3. Bromo encrypt the document using random key, the result is encrypted document.
  4. Bromo needs to encrypt the random key before sending it to Rinjani. Encrypting it using Rinjani’s public key ensure that only Rinjani will able to open the random key.
  5. Encrypted document, encrypted public key and digital signature sent to Rinjani.

Receiving The Messages at Rinjani (Receiver)

  1. Rinjani received encrypted document, encrypted public key and digital signature sent by Bromo
  2. Using his private key, Rinjani open encrypted random key.
  3. Encrypted random key is used to decrypt the document.
  4. Document is verified against digital signature sent by Bromo using Bromo’s public key.

Keeping Confidentiality

Document is encrypted using symmetric encryption algorithm. Other who want to open document must know the key. Because the key is encrypted using Rinjani’s public key, so, it’s infeasible to open encrypted key without knowing Rinjani’s private key. The key will be also different from one session to another session, keeping it harder to decrypt the key.

Non Repudiation

Document already signed using Bromo’s private key. Other will verify the digital signature using Bromo’s public key. If someone alter the document, the verification process at Rinjani will be fail. It’s also infeasible creating document and signing it (act as Bromo) without knowing Bromo’s private key. In the future, Brimo can’t deny that he was the one who send the document.

Next: Real World Tutorial

  1. Creating Private Key and Signed Public Key using OpenSSL (will be available soon)
  2. Implementing Encryption/Decryption Using Private Key and Public Key in Java (will be available soon)
  3. Implementing Creating/Verifying Digital Signature Using Private Key and Public Key in Java (will be available soon)
,

Ajax (shorthand for asynchronous JavaScript and XML) basically is just about creating web request asynchronously. Not much programmer used it until google using it on their applications, gmail. The technology used by gmail (Ajax) is not new technology, it was already on IE5 (known as XMLHttpRequest) since 1999. But the brilliant idea how using it on gmail makes ajax popular. Lesson learned: it’s not about using new feature or technology, but using [old] technology creatively to solve your problem.

Today, Ajax and javascript is the core part of web based rich client user interface. Popular javascript library or framework make us easier to build rich client user interface. With numbers of available library, next question is which one you will choose? Well, I think it will depend on your needs.

You can use javascript library like prototypejs, mootools, jquery or others and combine it with your existing web based applications. You can get more user friendly or “fancy” web based application by using those tools. Your existing web page might be will not much change. But you can also build web based application by using very rich user interface by using Extjs. Extjs not only provides Ajax tools and simple effects, but it also provide page layout control and form control, you’ll see that Extjs based applications is just like desktop based applications.

So, which type of library will you use?

Based on my experience, if you used it for native web applications (fancy user interface, using background picture to attract customer): use solution type 1, but if you want to build data intensive web based applications, you can use solution type 2.

I’ll give you real case. For ecommerce application, you can use solustion type 1 (prototype, mootools, jquery etc) and combine with your existing web to get new user experience. If you see at www.armaniexchange.com and view the product details, you’ll see the web pages using jquery to create new user experiences like: zoom effects, popup image and image gallery. In this case, they still need html page and css which have more flexible and fancy design to attract customer.

You migth use solution type 2 like Extjs to support your backend applications. The user interface of Extjs is monotone (if you compare it to html+css web page), but this type of library is suitable for data intensive applications. You can create a record of a product catalog just like you create it in desktop based applications, insert picture, search data and view it on data grid. Users already familiar with desktop based applications for this kind of tasks.

The picture below show how you can use Extjs to create backend applications. This is the real case from application that I built a year ago, tuneeca.com (ecommerce applications).  The pictures show small part of the applications. First picture show you user interface to manage catalog data, and second picture show you user interface to manage sales. The last picture show you how you can create tab based applications and show each module in a tab. Using this solution, you only need to load page once.

Catalog Management User Interface

Catalog Management User Interface

Sales Management using Extjs

Sales Management using Extjs

Application's Tab

Application's Tab

Well, this is just simple guide how you can use different type rich client user interface and Ajax in your applications. Consider wisely which type of library you’ll use, or first questions before you use the library, do you really need those library? The library cost bandwith, and library like Extjs also cost performance of browser.

,