1 Introduction

Ada Security provides a security framework which allows applications to define and enforce security policies. This framework allows users to authenticate by using OpenID Authentication 2.0 as well as OAuth 2.0 protocol. It allows a web application to integrate easily with Yahoo!, Facebook and Google+ authentication systems. The Ada05 library includes:

Ada Security Overview

2 Installation

This chapter explains how to build and install the library.

2.1 Before Building

Before building the library, you will need:

First get, build and install the Ada Utility Library.

2.2 Configuration

The library uses the configure script to detect the build environment, check for Ada Utility Library library. The configure script provides several standard options and you may use:

In most cases you will configure with the following command:

./configure

2.3 Build

After configuration is successful, you can build the library by running:

make

After building, it is good practice to run the unit tests before installing the library. The unit tests are built and executed using:

make test

And unit tests are executed by running the bin/security_harness test program.

2.4 Installation

The installation is done by running the install target:

make install

If you want to install on a specific place, you can change the prefix and indicate the installation direction as follows:

make install prefix=/opt

2.5 Using

To use the library in an Ada project, add the following line at the beginning of your GNAT project file:

with "security";

3 Security

The Security package provides a security framework that allows an application to use OpenID or OAuth security frameworks. This security framework was first developed within the Ada Server Faces project. It was moved to a separate project so that it can easily be used with AWS. This package defines abstractions that are close or similar to Java security package.

The security framework uses the following abstractions:

The Security_Context holds the contextual information that the security controller can use to verify the permission. The security context is associated with a principal and a set of policy context.

3.1 Overview

An application will create a security policy manager and register one or several security policies (yellow). The framework defines a simple role based security policy and an URL security policy intended to provide security in web applications. The security policy manager reads some security policy configuration file which allows the security policies to configure and create the security controllers. These controllers will enforce the security according to the application security rules. All these components are built only once when an application starts.

A user is authenticated through an authentication system which creates a Principal instance that identifies the user (green). The security framework provides two authentication systems: OpenID and OAuth 2.0 OpenID Connect.

When a permission must be enforced, a security context is created and linked to the Principal instance (blue). Additional security policy context can be added depending on the application context. To check the permission, the security policy manager is called and it will ask a security controller to verify the permission.

The framework allows an application to plug its own security policy, its own policy context, its own principal and authentication mechanism.

3.2 Permission

The Security.Permissions package defines the different permissions that can be checked by the access control manager. An application should declare each permission by instantiating the Definition package:

package Perm_Create_Workspace is new Security.Permissions.Definition ("create-workspace");

This declares a permission that can be represented by “create-workspace” in configuration files. In Ada, the permission is used as follows:

 Perm_Create_Workspace.Permission

3.3 Principal

A principal is created by using either the [Security_Auth OpenID], the [Security_OAuth OAuth] or another authentication mechanism. The authentication produces an object that must implement the Principal interface. For example:

P : Security.Auth.Principal_Access := Security.Auth.Create_Principal (Auth);

or

P : Security.OAuth.Clients.Access_Token_Access := Security.OAuth.Clients.Create_Access_Token

The principal is then stored in a security context.

3.4 Security Context

The security context provides contextual information for a security controller to verify that a permission is granted. This security context is used as follows:

For example the security context is declared as follows:

Context : Security.Contexts.Security_Context;

A security policy and a principal must be set in the security context. The security policy defines the rules that govern the security and the principal identifies the current user.

Context.Set_Context (Policy_Manager, P);

A permission is checked by using the Has_Permission operation:

if Security.Contexts.Has_Permission (Perm_Create_Workspace.Permission) then
  -- Granted
else
  -- Denied
end if;

4 Authentication

The Security.Auth package implements an authentication framework that is suitable for OpenID 2.0, OAuth 2.0 and later for OpenID Connect. It allows an application to authenticate users using an external authorization server such as Google, Facebook, Google +, Twitter and others.

See OpenID Authentication 2.0 - Final https://openid.net/specs/openid-authentication-2_0.html

See OpenID Connect Core 1.0 https://openid.net/specs/openid-connect-core-1_0.html

See Facebook API: The Login Flow for Web (without JavaScript SDK) https://developers.facebook.com/docs/facebook-login/login-flow-for-web-no-jssdk/

Despite their subtle differences, all these authentication frameworks share almost a common flow. The API provided by Security.Auth defines an abstraction suitable for all these frameworks.

There are basically two steps that an application must implement:

The authentication process is the following:

4.1 Initialization

The initialization process must be done before each two steps (discovery and verify). The Authentication manager must be declared and configured.

Mgr   : Security.Auth.Manager;

For the configuration, the Initialize procedure is called to configure the Auth realm and set the authentication return callback URL. The return callback must be a valid URL that is based on the realm. Example:

Mgr.Initialize (Name      => "http://app.site.com/auth",
                Return_To => "http://app.site.com/auth/verify",
                Realm     => "openid");

After this initialization, the authentication manager can be used in the authentication process.

4.2 OpenID Configuration

The Open ID provider needs the following configuration parameters:

openid.realm          The OpenID realm parameter passed in the authentication URL.
openid.callback_url   The OpenID return_to parameter.

4.2.1 Google+

The Google+ authentication is based on OAuth 2.0 and the OpenID Connect Basic Client Profile.

See https://developers.google.com/accounts/docs/OAuth2Login

4.3 Discovery: creating the authentication URL

The first step is to create an authentication URL to which the user must be redirected. In this step, we have to create an OpenID manager, discover the OpenID provider, do the association and get an End_Point. The OpenID provider is specified as an URL, below is an example for Google OpenID:

Provider : constant String := "https://www.google.com/accounts/o8/id";
OP       : Security.Auth.End_Point;
Assoc    : constant Security.Auth.Association_Access := new Security.Auth.Association;

The following steps are performed:

Mgr.Discover (Provider, OP);  --  Yadis discovery (get the XRDS file).
Mgr.Associate (OP, Assoc.all);--  Associate and get an end-point with a key.

After this first step, you must manage to save the association in the HTTP session. Then you must redirect to the authentication URL that is obtained by using:

Auth_URL : constant String := Mgr.Get_Authentication_URL (OP, Assoc.all);

4.4 Verify: acknowledge the authentication in the callback URL

The second step is done when the user has finished the authentication successfully or not. For this step, the application must get back the association that was saved in the session. It must also prepare a parameters object that allows the OpenID framework to get the URI parameters from the return callback.

Assoc      : Association_Access := ...;  --  Get the association saved in the session.
Credential : Security.Auth.Authentication;
Params     : Auth_Params;

The auth manager must be initialized and the Verify procedure is called with the association, parameters and the authentication result. The Get_Status function must be used to check that the authentication succeeded.

Mgr.Verify (Assoc.all, Params, Credential);
if Security.Auth.Get_Status (Credential) = Security.Auth.AUTHENTICATED then ...  -- Success.

4.5 Principal creation

After the user is successfully authenticated, a user principal can be created and saved in the session. The user principal can then be used to assign permissions to that user and enforce the application permissions using the security policy manger.

P : Security.Auth.Principal_Access := Security.Auth.Create_Principal (Credential);

5 OAuth

The Security.OAuth package defines and implements the OAuth 2.0 authorization framework as defined by the IETF working group in RFC 6749: The OAuth 2.0 Authorization Framework.

5.1 OAuth Client

The Security.OAuth.Clients package implements the client OAuth 2.0 authorization.

5.1.1 Application setup

For an OAuth2 client application to authenticate, it must be registered on the server and the server provides the following information:

The Security.OAuth.Clients.Application tagged record is the primary type that allows to perform one of the OAuth 2.0 authorization flows. It is necessary to declare an Application instance and register the client_id, the client_secret and the authorisation URLs to connect to the server.

App : Security.OAuth.Clients.Application;
...
   App.Set_Application_Identifier ("app-identifier");
   App.Set_Application_Secret ("app-secret");
   App.Set_Provider_URL ("https://graph.facebook.com/oauth/access_token");

5.1.2 Resource Owner Password Credentials Grant

The RFC 6749: 4.3. Resource Owner Password Credentials Grant allows to authorize an application by using the user’s name and password. This is the simplest OAuth flow but because it requires to know the user’s name and password, it is not recommended and not supported by several servers. To use this authorization, the application will use the Request_Token procedure and will give the user’s name, password and the scope of permissions. When the authorization succeeds, a Grant_Type token object is returned.

Token  : Security.OAuth.Clients.Grant_Type;
...
  App.Request_Token ("admin", "admin", "scope", Token);

5.1.3 Refreshing an access token

An access token has an expiration date and a new access token must be asked by using the refresh token. When the access token has expired, the grant token object can be refreshed to retrieve a new access token by using the Refresh_Token procedure. The scope of permissions can also be passed.

 App.Refresh_Token ("scope", Token);

5.2 OAuth Server

OAuth server side is provided by the Security.OAuth.Servers package. This package allows to implement the authorization framework described in RFC 6749 “The OAuth 2.0 Authorization Framework”.

The authorization method produces a Grant_Type object that contains the result of the grant (successful or denied). It is the responsibility of the caller to format the result in JSON/XML and return it to the client.

Three important operations are defined for the OAuth 2.0 framework. They will be used in the following order:

Authorize is used to obtain an authorization request. This operation is optional in the OAuth 2.0 framework since some authorization method directly return the access token. This operation is used by the “Authorization Code Grant” and the “Implicit Grant”.

Token is used to get the access token and optional refresh token. Each time it is called, a new token is generated.

Authenticate is used for the API request to verify the access token and authenticate the API call. This operation can be called several times with the same token until the token is revoked or it has expired.

Several grant types are supported.

5.2.1 Application Manager

The application manager maintains the repository of applications which are known by the server and which can request authorization. Each application is identified by a client identifier (represented by the client_id request parameter). The application defines the authorization methods which are allowed as well as the parameters to control and drive the authorization. This includes the redirection URI, the application secret, the expiration delay for the access token.

The application manager is implemented by the application server and it must implement the Application_Manager interface with the Find_Application method. The Find_Application is one of the first call made during the authenticate and token generation phases.

5.2.2 Resource Owner Password Credentials Grant

The password grant is one of the easiest grant method to understand but it is also one of the less secure. In this grant method, the username and user password are passed in the request parameter together with the application client identifier. The realm verifies the username and password and when they are correct it generates the access token with an optional refresh token. The realm also returns in the grant the user principal that identifies the user.

Realm : Security.OAuth.Servers.Auth_Manager;
Grant : Security.OAuth.Servers.Grant_Type;
  Realm.Token (Params, Grant);

5.2.3 Accessing Protected Resources

When accessing a protected resource, the API implementation will use the Authenticate operation to verify the access token and get a security principal. The security principal will identify the resource owner as well as the application that is doing the call.

 Realm : Security.OAuth.Servers.Auth_Manager;
 Grant : Security.OAuth.Servers.Grant_Type;
 Token : String := ...;
   Realm.Authenticate (Token, Grant);

When a security principal is returned, the access token was validated and the request is granted for the application.

6 Security Policies

The Security Policy defines and implements the set of security rules that specify how to protect the system or resources. The Policy_Manager maintains the security policies. These policies are registered when an application starts, before reading the policy configuration files.

While the policy configuration files are processed, the policy instances that have been registered will create a security controller and bind it to a given permission. After successful initialization, the Policy_Manager contains a list of securiy controllers which are associated with each permission defined by the application.

6.1 Authenticated Permission

The auth-permission is a pre-defined permission that can be configured in the XML configuration. Basically the permission is granted if the security context has a principal. Otherwise the permission is denied. The permission is assigned a name and is declared as follows:

<policy-rules>
  <auth-permission>
    <name>view-profile</name>
  </auth-permission>
</policy-rules>

This example defines the view-profile permission.

6.2 Grant Permission

The grant-permission is another pre-defined permission that gives the permission whatever the security context. The permission is defined as follows:

<policy-rules>
  <grant-permission>
    <name>anonymous</name>
  </grant-permission>
</policy-rules>

This example defines the anonymous permission.

6.3 Role Based Security Policy

The Security.Policies.Roles package implements a role based security policy. In this policy, users are assigned one or several roles and permissions are associated with roles. A permission is granted if the user has one of the roles required by the permission.

6.3.1 Policy creation

An instance of the Role_Policy must be created and registered in the policy manager. Get or declare the following variables:

Manager : Security.Policies.Policy_Manager;
Policy  : Security.Policies.Roles.Role_Policy_Access;

Create the role policy and register it in the policy manager as follows:

Policy := new Role_Policy;
Manager.Add_Policy (Policy.all'Access);

6.3.2 Policy Configuration

A role is represented by a name in security configuration files. A role based permission is associated with a list of roles. The permission is granted if the user has one of these roles. When the role based policy is registered in the policy manager, the following XML configuration is used:

<policy-rules>
  <security-role>
    <role-name>admin</role-name>
  </security-role>
  <security-role>
    <role-name>manager</role-name>
  </security-role>
  <role-permission>
    <name>create-workspace</name>
    <role>admin</role>
    <role>manager</role>
  </role-permission>
  ...
</policy-rules>

This definition declares two roles: admin and manager It defines a permission create-workspace that will be granted if the user has either the admin or the manager role.

Each role is identified by a name in the configuration file. It is represented by a Role_Type. To provide an efficient implementation, the Role_Type is represented as an integer with a limit of 64 different roles.

6.3.3 Assigning roles to users

A Security_Context must be associated with a set of roles before checking the permission. This is done by using the Set_Role_Context operation:

 Security.Policies.Roles.Set_Role_Context (Security.Contexts.Current, "admin");

6.4 URL Security Policy

The Security.Policies.Urls implements a security policy intended to be used in web servers. It allows to protect an URL by defining permissions that must be granted for a user to get access to the URL. A typical example is a web server that has a set of administration pages, these pages should be accessed by users having some admin permission.

6.4.1 Policy creation

An instance of the URL_Policy must be created and registered in the policy manager. Get or declare the following variables:

Manager : Security.Policies.Policy_Manager;
Policy  : Security.Policies.Urls.URL_Policy_Access;

Create the URL policy and register it in the policy manager as follows:

Policy := new URL_Policy;
Manager.Add_Policy (Policy.all'Access);

6.4.2 Policy Configuration

Once the URL policy is registered, the policy manager can read and process the following XML configuration:

<policy-rules>
  <url-policy id='1'>
    <permission>create-workspace</permission>
    <permission>admin</permission>
    <url-pattern>/workspace/create</url-pattern>
    <url-pattern>/workspace/setup/*</url-pattern>
  </url-policy>
  ...
</policy-rules>

This policy gives access to the URL that match one of the URL pattern if the security context has the permission create-workspace or admin. These two permissions are checked according to another security policy. The XML configuration can define several url-policy. They are checked in the order defined in the XML. In other words, the first url-policy that matches the URL is used to verify the permission.

The url-policy definition can contain several permission. The first permission that is granted gives access to the URL.

6.4.3 Checking for permission

To check a URL permission, you must declare a URL_Permission object with the URL.

 URL    : constant String := ...;
 Perm   : constant Policies.URLs.URL_Permission (URL'Length)
           := URL_Permission '(Len => URI'Length, URL => URL);

Then, we can check the permission:

 Result : Boolean := Security.Contexts.Has_Permission (Perm);