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:
This chapter explains how to build and install the library.
Before building the library, you will need:
First get, build and install the Ada Utility Library.
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:
--prefix=DIR
to control the installation directory,--enable-shared
to enable the build of shared libraries,--disable-static
to disable the build of static libraries,--enable-distrib
to build for a distribution and strip symbols,--disable-distrib
to build with debugging support,--enable-coverage
to build with code coverage support (-fprofile-arcs -ftest-coverage
),--with-ada-util=PATH
to control the installation path of Ada Utility Library,--help
to get a detailed list of supported options.In most cases you will configure with the following command:
./configure
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.
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
To use the library in an Ada project, add the following line at the beginning of your GNAT project file:
with "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:
Policy and policy manager: The 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.
Principal: The Principal
is the entity that can be authenticated. A principal is obtained after successful authentication of a user or of a system through an authorization process. The OpenID or OAuth authentication processes generate such security principal.
Permission: The Permission
represents an access to a system or application resource. A permission is checked by using the security policy manager. The policy manager uses a security controller to enforce the permission.
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.
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.
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:
This declares a permission that can be represented by “create-workspace” in configuration files. In Ada, the permission is used as follows:
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:
or
The principal is then stored in a 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:
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.
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;
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:
Discovery
: to resolve and use the OpenID provider and redirect the user to the provider authentication form.Verify
: to decode the authentication and check its result.The authentication process is the following:
Verify
procedure is called with the association to check the result and obtain the authentication results.The initialization process must be done before each two steps (discovery and verify). The Authentication manager must be declared and configured.
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.
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.
The Google+ authentication is based on OAuth 2.0 and the OpenID Connect Basic Client Profile.
See https://developers.google.com/accounts/docs/OAuth2Login
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:
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.
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.
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.
The Security.OAuth.Clients
package implements the client OAuth 2.0 authorization.
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");
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);
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.
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.
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.
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);
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.
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.
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:
This example defines the view-profile
permission.
The grant-permission
is another pre-defined permission that gives the permission whatever the security context. The permission is defined as follows:
This example defines the anonymous
permission.
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.
An instance of the Role_Policy
must be created and registered in the policy manager. Get or declare the following variables:
Create the role policy and register it in the policy manager as follows:
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.
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:
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.
An instance of the URL_Policy
must be created and registered in the policy manager. Get or declare the following variables:
Create the URL policy and register it in the policy manager as follows:
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.
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: