This project is read-only.
The goal of this scenario is to teach how to use the AuthorizationFilter attribute to secure your controllers’ action.
We assume that the process of creating a specific contact is dedicated to administrator or manager users.
  • If no user is logged in then the login view will invite the current user to specify his authentication details.
  • If a user is connected and is not an administrator or a manager, then a message should be displayed to indicate that the current user is not authorize to create a contact
  • If the connected user is an administrator or a manager then the process of creation continues.

Creating the Login ViewData

Right click on the folder ./data/viewdata and create a new folder login. Right click on the new created folder and add a new class named LoginViewData. Make sure that the new created class is public and extends the AbstractBaseData provided by the koossery.MVCwin framework. The listing 1 below shows the content of the LoginViewData class :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Koossery.MVCwin.Data;

namespace ContactManager.data.viewdata.login
{
	/// <summary>
	/// The loginView Data
	/// </summary>
	public class LoginViewData : AbstractBaseData
	{
		#region Fields

		private String login;
		private String password;

		#endregion Fields

		#region Public Properties

		public String Login
		{
			get { return login; }
			set
			{
			    login = value;
			    PropertyHasChanged("Login");
			}
		}

		public String Password
		{
			get { return password; }
			set
			{
			    password = value;
			    PropertyHasChanged("Password");
			}
		}

		#endregion Public Properties
	}
}

The class contains properties that stores authentication details entered by the user.

Use MSVAB to mark Login and Password as mandatory (The process is shown in the previous ContactManager tutorial please check it)

Creating the Login View

Right click on the /views folder and create the folder login. Right click on the created folder and add a new Windows Form named LoginView. Make sure that LoginView is public and extends the BaseView class provided by the koossery.MVCwin framework.

Add a new data source that point to the LoginViewData class (this process is shown in the previous tutorial). Drag and drop the data source from the Data Source tab to the form to generate the content of the view. You can then delete useless controls. Add an ErrorProvider tool in order to provide high level user interface experience. The result view should look like the figure below:

loginview.png

Controls are all databound to their respective datasource. The next step is to override the BindDataToView method in order to initialize bindings. The purpose of ‘Label Context Message (Name=lblContextMessageVisibility)’ is to display notification to user.
The listing 2 below shows the code:

public partial class LoginView : BaseView
{
    public LoginView()
    {
        InitializeComponent();
    }

    public override void BindDataToView()
    {
        LoginViewData loginViewData = this.GetSessionData(typeof(LoginViewData).Name) as LoginViewData;
        bsLoginViewData.DataSource = loginViewData;

        //Displaying error message
        DisplayMessages();

        //call the base view. This method is very important
        base.BindDataToView();
    }
    . . .
}

DisplayMessages is the method that displays errors and confirmation messages to the user. The listing 3 shows the content of the method:

private void DisplayMessages()
{
    lblMessageVisibility.Visible = this.GetContextVisibility();
    //Retrieving error message
    if (GetMessage(MessageType.ERROR_MESSAGE) != null)
    {
        lblMessageVisibility.ForeColor = Color.IndianRed;
        lblMessageVisibility.Text = GetMessage(MessageType.ERROR_MESSAGE);
    }
    else if (GetMessage(MessageType.OTHER_MESSAGE) != null)
    {
        lblMessageVisibility.ForeColor = Color.Green;
        lblMessageVisibility.Text = GetMessage(MessageType.OTHER_MESSAGE);
    }           
}

Edit the click’s event of the Login button to write code that invokes the action Login of the LoginController. The code should look like the one below:

private void btLogin_Click(object sender, EventArgs e)
{
    string typeOfLoginViewData = typeof(LoginViewData).Name;
    LoginViewData loginViewData = this.GetSessionData(typeOfLoginViewData) as LoginViewData;

    //Validating the data
    ValidationResults results = Utility.ValidateData<LoginViewData>(loginViewData);

     epLoginView.DataSource = bsLoginViewData;

     //Check the validation result
     if (!results.IsValid)
     {
         epLoginView.DataSource = bsLoginViewData;
         return;
     }

     //Invoke the action
     this.InvokeController(typeof(LoginController).Name, ApplicationData.InitAction);            
}

Creating the Login Controller

Once the view is created we can write logic for the authentication process in the LoginController class.
Right click on the /controllers folder and add a new class named LoginController. Make sure that the new created class is public and extends the ControllerBase class provided by the koossery.MVCwin framework.
Add the Init and the login action. Those actions are shown below:

public IActionResult Init()
{
    //Clearing errors messages
    this.ClearErrorsAndMessage();

    //Display the login view
    return this.View(typeof(LoginView).Name);
 }

public IActionResult Login()
{
     LoginViewData loginViewData = null;
     String rule = string.Empty;
     String strMsg = null;
     Object[] args = null;

     try
     {
         //Retrieve the associated data
         loginViewData = this.GetSessionData(typeof(LoginViewData).Name) as LoginViewData;

         //Authenticate the user
         rule = Utility.Authenticate(loginViewData.Login, loginViewData.Password);

          //If the user does not exist
          if (rule == null)
          {
               //The user does not exists
              strMsg = String.Format(XmlUtility.getKeyValueFromXmlConfigFile(errorMessageXmlFileName, "LOGIN", "LOGIN-002"), new object[1]);
               this.AddMessage(strMsg, MessageType.ERROR_MESSAGE);
               return View(typeof(LoginView).Name);
            }

      //The user exist, we store the user. THIS METHOD IS IMPORTANT OTHERWISE THE KOOSSERY.MVCWIN WILL NOT BE ABLE TO DECTECT THE CONNECTED USER
      Utilities.SetUser(loginViewData.Login, loginViewData.Password, rule, true);

      //Redirect to the ListView
      return View(typeof(ListView).Name);
 }
 catch (Exception ex)
 {
      arg = new Object[3];
      arg[0] = loginViewData.Login;
      arg[1] = FrontEndConstants.TECHNICAL_DETAIL;
      arg[2] = ex.Message;
      strMsg = String.Format(XmlUtility.getKeyValueFromXmlConfigFile(errorMessageXmlFileName, "LOGIN", "LOGIN-001"), arg);
      log.Error(strMsg);
      String strReturnMessage = strMsg.Substring(0, strMsg.IndexOf(FrontEndConstants.TECHNICAL_DETAIL));

      this.AddMessage(strReturnMessage, MessageType.ERROR_MESSAGE);
      return this.View(typeof(LoginView).Name);
  }
}

The Login action will authenticate the user. If the authentication succeeds, the current user will be store in the Koossery.MVCwin for other uses (like AuthenticationAttribute ...)
To authenticate the user we use the static method Utiliy.Authenticate. The listing below shows the content of this method.

public class Utility
{
    private static Dictionary<string, string> authentificationMap = null;
    private static Dictionary<string, string> ruleMap = null;

    private static void CreateCredentials()
    {
        //Adding authentication details
        authentificationMap = new Dictionary<string, string>();
        authentificationMap.Add("yacoubou", "minastirite");
        authentificationMap.Add("peter", "admin");
        authentificationMap.Add("bake", "tochange");

        //le dictionnaire des rules
        ruleMap = new Dictionary<string, string>();
        ruleMap.Add("yacoubou", "ADMINISTRATOR");
        ruleMap.Add("peter", "USER");
        ruleMap.Add("bake", "COORDINATOR");
    }

    public static String Authenticate(String login, String password)
    {
        if (authentificationMap == null || ruleMap == null) CreateCredentials();

        String rule = null;
        String pwd = null;

        if (authentificationMap.ContainsKey(login))
            pwd = authentificationMap[login];
        if (pwd == null || pwd != password) return null;

        if (ruleMap.ContainsKey(login))
            rule = ruleMap[login];

        return rule;
    }
    . . .
}

The first private CreateCredentials fills the authentication map and rules map with a number of users and roles. The Authenticate method uses those maps to check if credentials entered by the user are correct, and then return the role of that user.

Noticed the call for the method Utilities.SetUser provided by the Koossery.MVCwin framework. This method is important it allow the Koossery.MVCwin to know the current connected user details.

Establishing Spring.NET configuration

As you learnt in the first ContactManager, the next step is to register LoginView, LoginController and LoginViewData objects to be created by spring.net. Listings below shows configurations
  • LoginViewData : spring-ContactManager_SESSIONS.xml
<object id="Session" type="Koossery.MVCwin.Data.Session, Koossery.MVCwin">
    <property name="SessionData">
      <dictionary key-type="string" value-type="Koossery.MVCwin.Data.AbstractBaseData, Koossery.MVCwin">
        <entry key="ListViewData">
          <ref object="ListViewData" />
        </entry>
        <entry key="SaveViewData">
          <ref object="SaveViewData" />
        </entry>
        <entry key="LoginViewData">
          <ref object="LoginViewData" />
        </entry>
      </dictionary>
    </property>
  </object>
  <object id="ListViewData" type="ContactManager.data.viewdata.contact.ListViewData, ContactManager" />
  <object id="SaveViewData" type="ContactManager.data.viewdata.contact.SaveViewData, ContactManager" />
  <object id="LoginViewData" type="ContactManager.data.viewdata.login.LoginViewData, ContactManager" />
  • LoginView : spring-ContactManager_VIEWS.xml
<objects xmlns="http://www.springframework.net">

  <import resource="spring-ContactManager_SESSIONS.xml"/>

  <!--BaseView-->
  <object id="BaseView" type="Koossery.MVCwin.Views.impl.BaseView" abstract="true">
    <property name="Session">
      <ref object="Session"/>
    </property>
    <property name="IsChildForm">
      <value>false</value>
    </property>
  </object>

  <object id="ListView" type="ContactManager.views.contact.ListView" parent="BaseView">
    <property name="ViewName">
      <value>List your contacts</value>
    </property>
  </object>
  
  <object id="SaveView" type="ContactManager.views.contact.SaveView" parent="BaseView">
    <property name="ViewName">
      <value>Save your contacts</value>
    </property>
  </object>

  <object id="LoginView" type="ContactManager.views.login.LoginView" parent="BaseView">
    <property name="ViewName">
      <value>Enter your credentials</value>
    </property>
  </object>  
</objects>
  • LoginController : spring-ContactManager_CONTROLLERS.xml
<objects xmlns="http://www.springframework.net">

  <import resource="spring-ContactManager_VIEWS.xml"/>
  <import resource="spring-ContactManager_MODEL.xml"/>

  <!--Base Controller-->
  <object id="ControllerBase" type="Koossery.MVCwin.Controller.impl.ControllerBase" abstract="true">    
    <property name="Session">
      <ref object="Session"/>
    </property>
  </object>
  
  <!--Configuriing controllers-->
  <object id="ListController" type="ContactManager.controllers.contact.ListController" parent="ControllerBase">
    <!--Injecting the concrete implementation-->
    <constructor-arg index="0">
      <ref object="ContactManagerLocal" />
    </constructor-arg>
  </object>
  <object id="SaveController" type="ContactManager.controllers.contact.SaveController" parent="ControllerBase">
    <!--Injecting the concrete implementation-->
    <constructor-arg index="0">
      <ref object="ContactManagerLocal" />
    </constructor-arg>
    <property name="AsynchMethods">
      <list element-type="System.String">
        <value>Save</value>
      </list>
    </property>
  </object>
  <object id="LoginController" type="ContactManager.controllers.login.LoginController" parent="ControllerBase">
    <!--Injecting the concrete implementation-->
    <constructor-arg index="0">
      <ref object="ContactManagerLocal" />
    </constructor-arg>
    <property name="AsynchMethods">
      <list element-type="System.String">
        <value>Login</value>
      </list>
    </property>
  </object>
    
  <!--Manager-->
  <object id="ControllerManager" type="Koossery.MVCwin.ControllerManager.impl.ControllerManager, Koossery.MVCwin">
    <property name="DefaultController">
      <value>ListController</value>
    </property>

    <!--Controllers-->
    <property name="Controllers">
      <dictionary key-type="string" value-type="Koossery.MVCwin.Controller.itf.IController, Koossery.MVCwin">
        <entry key="ListController">
          <ref object="ListController"/>
        </entry>
        <entry key="SaveController">
          <ref object="SaveController"/>
        </entry>
        <entry key="LoginController">
          <ref object="LoginController"/>
        </entry>
      </dictionary>
    </property>

    <!--Views-->
    <property name="Views">
      <dictionary key-type="string" value-type="Koossery.MVCwin.Views.itf.IView, Koossery.MVCwin">
        <entry key="ListView">
          <ref object="ListView"/>
        </entry>
        <entry key="SaveView">
          <ref object="SaveView"/>
        </entry>
        <entry key="LoginView">
          <ref object="LoginView"/>
        </entry>
      </dictionary>
    </property>
  </object>
</objects>

Creating custom Authorization Filter

We are going to extend the base AuthorizationFilter class. Right click on the project and add a new folder named filters. Right click on the new create folder and add a class. Named the class CustomAuthorizationFilterAttribute and make sure that it’s public and it extend the base AuthorizationFilterAttribute class provided by the framework.
Once the class is created, override the OnAuthorization method and add the following code:

public override void OnAuthorization(Koossery.MVCwin.Filters.context.AuthorizationContext filterContext)
{
      //Check authentication and authorization
      base.OnAuthorization(filterContext);

      //If the user is authenticated and authorized the return
      if (filterContext.Result == null) return;

      //Display the message
      MessageBox.Show("You are not allow to execute this action", "Insufficient right", MessageBoxButtons.OK, MessageBoxIcon.Information);
}

The custom authentication class is now created. The next step is to add that custom attribute to the Init action of the SaveController. In fact only specific user can create a contact (see scenario 1)

[CustomAuthorizationFilter(Roles="ADMINISTRATOR,COORDINATOR", ControllerType=typeof(LoginController), ActionName="Init")]
public IActionResult Init()
{
    //Retrieving the view data
    SaveViewData saveView = this.GetSessionData(typeof(SaveViewData).Name) as SaveViewData;

    //In creation mode we reset the viewData
    if (saveView.IsCreation) saveView.Reset();

    //Render the SaveView
    return View(typeof(SaveView).Name);
}

The attribute indicates that all users with ADMINISTRATOR or COORDINATOR roles can execute the Init action. For those who are not member of these roles the Init action of the LoginController is executed.

Run the application and try to add a new user. You will get the below notification

notalloweduser.png

You will then be displayed the login view in order to enter a valid credentials for authentication and authorization.

loginview_execution.png

Enter the user = {peter,admin}. Note that peter is member of the role USER so he is not allowed to execute the action. Try to create a new contact once again with that user. The result is the same; you are still not allowed to create a contact. In fact peter is not allowed to execute the action.

Now enter the user {yacoubou, minastirite} or {bake, tochange} and try to create a new contact.

savecontactview.png

Yes you can! Of course yacoubou (or bake) is a member of ADMINISTRATOR role and the custom authorization allowed this role. Create a contact you want.

creatingcontact.png

The new created contact appears on the listView

listview.png

Summary

In this scenario, we learn how to create a custom authorization filter attribute that display a message to the user. We also create a LoginView to allow users to identify themselves. Authorization filter can be very useful for securing controller action.

Last edited Sep 1, 2009 at 11:08 AM by yacoubou, version 4

Comments

No comments yet.