-----------------------------------------------------------------------
-- security-oauth-servers -- OAuth Server Authentication Support
-- Copyright (C) 2016, 2017, 2018 Stephane Carrez
-- Written by Stephane Carrez (Stephane.Carrez@gmail.com)
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-----------------------------------------------------------------------
with Ada.Strings.Unbounded;
with Ada.Calendar;
with Ada.Finalization;
with Ada.Strings.Hash;
with Ada.Containers.Indefinite_Hashed_Maps;
with Util.Strings;
with Security.Auth;
with Security.Permissions;
-- == 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.
--
-- === 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.
--
-- === 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);
--
-- === 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.
--
package Security.OAuth.Servers is
-- Minimum length for the server private key (160 bits min length).
-- (See NIST Special Publication 800-107)
MIN_KEY_LENGTH : constant Positive := 20;
Invalid_Application : exception;
type Application is new Security.OAuth.Application with private;
-- Check if the application has the given permission.
function Has_Permission (App : in Application;
Permission : in Security.Permissions.Permission_Index) return Boolean;
type Principal is limited interface and Security.Principal;
type Principal_Access is access all Principal'Class;
-- Check if the permission was granted.
function Has_Permission (Auth : in Principal;
Permission : in Security.Permissions.Permission_Index)
return Boolean is abstract;
-- Define the status of the grant.
type Grant_Status is (Invalid_Grant, Expired_Grant, Revoked_Grant,
Stealed_Grant, Valid_Grant);
-- Define the grant type.
type Grant_Kind is (No_Grant, Access_Grant, Code_Grant,
Implicit_Grant, Password_Grant, Credential_Grant,
Extension_Grant);
-- The Grant_Type holds the results of the authorization.
-- When the grant is refused, the type holds information about the refusal.
type Grant_Type is record
-- The request grant type.
Request : Grant_Kind := No_Grant;
-- The response status.
Status : Grant_Status := Invalid_Grant;
-- When success, the token to return.
Token : Ada.Strings.Unbounded.Unbounded_String;
-- When success, the token expiration date.
Expires : Ada.Calendar.Time;
-- The token expiration date in seconds.
Expires_In : Duration := 0.0;
-- When success, the authentication principal.
Auth : Principal_Access;
-- When error, the type of error to return.
Error : Util.Strings.Name_Access;
end record;
type Application_Manager is limited interface;
type Application_Manager_Access is access all Application_Manager'Class;
-- Find the application that correspond to the given client id.
-- The Invalid_Application exception should be raised if there is no such application.
function Find_Application (Realm : in Application_Manager;
Client_Id : in String) return Application'Class is abstract;
type Realm_Manager is limited interface;
type Realm_Manager_Access is access all Realm_Manager'Class;
-- Authenticate the token and find the associated authentication principal.
-- The access token has been verified and the token represents the identifier
-- of the Tuple (client_id, user, session) that describes the authentication.
-- The Authenticate procedure should look in its database (internal
-- or external) to find the authentication principal that was granted for
-- the token Tuple. When the token was not found (because it was revoked),
-- the procedure should return a null principal. If the authentication
-- principal can be cached, the Cacheable value should be set.
-- In that case, the access token and authentication principal are inserted
-- in a cache.
procedure Authenticate (Realm : in out Realm_Manager;
Token : in String;
Auth : out Principal_Access;
Cacheable : out Boolean) is abstract;
-- Create an auth token string that identifies the given principal. The returned
-- token will be used by Authenticate to retrieve back the principal. The
-- returned token does not need to be signed. It will be inserted in the public part
-- of the returned access token.
function Authorize (Realm : in Realm_Manager;
App : in Application'Class;
Scope : in String;
Auth : in Principal_Access) return String is abstract;
procedure Verify (Realm : in out Realm_Manager;
Username : in String;
Password : in String;
Auth : out Principal_Access) is abstract;
procedure Verify (Realm : in out Realm_Manager;
Token : in String;
Auth : out Principal_Access) is abstract;
procedure Revoke (Realm : in out Realm_Manager;
Auth : in Principal_Access) is abstract;
type Auth_Manager is tagged limited private;
type Auth_Manager_Access is access all Auth_Manager'Class;
-- Set the auth private key.
procedure Set_Private_Key (Manager : in out Auth_Manager;
Key : in String;
Decode : in Boolean := False);
-- Set the application manager to use and and applications.
procedure Set_Application_Manager (Manager : in out Auth_Manager;
Repository : in Application_Manager_Access);
-- Set the realm manager to authentify users.
procedure Set_Realm_Manager (Manager : in out Auth_Manager;
Realm : in Realm_Manager_Access);
-- Authorize the access to the protected resource by the application and for the
-- given principal. The resource owner has been verified and is represented by the
-- Auth principal. Extract from the request parameters represented by
-- Params the application client id, the scope and the expected response type.
-- Handle the "Authorization Code Grant" and "Implicit Grant" defined in RFC 6749.
procedure Authorize (Realm : in out Auth_Manager;
Params : in Security.Auth.Parameters'Class;
Auth : in Principal_Access;
Grant : out Grant_Type);
-- The Token procedure is the main entry point to get the access token and
-- refresh token. The request parameters are accessed through the Params interface.
-- The operation looks at the "grant_type" parameter to identify the access method.
-- It also looks at the "client_id" to find the application for which the access token
-- is created. Upon successful authentication, the operation returns a grant.
procedure Token (Realm : in out Auth_Manager;
Params : in Security.Auth.Parameters'Class;
Grant : out Grant_Type);
-- Make the access token from the authorization code that was created by the
-- Authorize operation. Verify the client application, the redirect uri, the
-- client secret and the validity of the authorization code. Extract from the
-- authorization code the auth principal that was used for the grant and make the
-- access token.
procedure Token_From_Code (Realm : in out Auth_Manager;
App : in Application'Class;
Params : in Security.Auth.Parameters'Class;
Grant : out Grant_Type);
procedure Authorize_Code (Realm : in out Auth_Manager;
App : in Application'Class;
Params : in Security.Auth.Parameters'Class;
Auth : in Principal_Access;
Grant : out Grant_Type);
procedure Authorize_Token (Realm : in out Auth_Manager;
App : in Application'Class;
Params : in Security.Auth.Parameters'Class;
Auth : in Principal_Access;
Grant : out Grant_Type);
-- Make the access token from the resource owner password credentials. The username,
-- password and scope are extracted from the request and they are verified through the
-- Verify procedure to obtain an associated principal. When successful, the
-- principal describes the authorization and it is used to forge the access token.
-- This operation implements the RFC 6749: 4.3. Resource Owner Password Credentials Grant.
procedure Token_From_Password (Realm : in out Auth_Manager;
App : in Application'Class;
Params : in Security.Auth.Parameters'Class;
Grant : out Grant_Type);
-- Create a HMAC-SHA1 of the data with the private key.
-- This function can be overridden to use another signature algorithm.
function Sign (Realm : in Auth_Manager;
Data : in String) return String;
-- Forge an access token. The access token is signed by an HMAC-SHA1 signature.
-- The returned token is formed as follows:
-- ..HMAC-SHA1(, .)
-- See also RFC 6749: 5. Issuing an Access Token
procedure Create_Token (Realm : in Auth_Manager;
Ident : in String;
Grant : in out Grant_Type);
-- Authenticate the access token and get a security principal that identifies the app/user.
-- See RFC 6749, 7. Accessing Protected Resources.
-- The access token is first searched in the cache. If it was found, it means the access
-- token was already verified in the past, it is granted and associated with a principal.
-- Otherwise, we have to verify the token signature first, then the expiration date and
-- we extract from the token public part the auth identification. The Authenticate
-- operation is then called to obtain the principal from the auth identification.
-- When access token is invalid or authentification cannot be verified, a null principal
-- is returned. The Grant data will hold the result of the grant with the reason
-- of failures (if any).
procedure Authenticate (Realm : in out Auth_Manager;
Token : in String;
Grant : out Grant_Type);
procedure Revoke (Realm : in out Auth_Manager;
Token : in String);
private
use Ada.Strings.Unbounded;
function Format_Expire (Expire : in Ada.Calendar.Time) return String;
-- Decode the expiration date that was extracted from the token.
function Parse_Expire (Expire : in String) return Ada.Calendar.Time;
type Application is new Security.OAuth.Application with record
Expire_Timeout : Duration := 3600.0;
Permissions : Security.Permissions.Permission_Index_Set := Security.Permissions.EMPTY_SET;
end record;
type Cache_Entry is record
Expire : Ada.Calendar.Time;
Auth : Principal_Access;
end record;
package Cache_Map is
new Ada.Containers.Indefinite_Hashed_Maps (Key_Type => String,
Element_Type => Cache_Entry,
Hash => Ada.Strings.Hash,
Equivalent_Keys => "=",
"=" => "=");
-- The access token cache is used to speed up the access token verification
-- when a request to a protected resource is made.
protected type Token_Cache is
procedure Authenticate (Token : in String;
Grant : in out Grant_Type);
procedure Insert (Token : in String;
Expire : in Ada.Calendar.Time;
Principal : in Principal_Access);
procedure Remove (Token : in String);
procedure Timeout;
private
Entries : Cache_Map.Map;
end Token_Cache;
type Auth_Manager is new Ada.Finalization.Limited_Controlled with record
-- The repository of applications.
Repository : Application_Manager_Access;
-- The realm for user authentication.
Realm : Realm_Manager_Access;
-- The server private key used by the HMAC signature.
Private_Key : Ada.Strings.Unbounded.Unbounded_String;
-- The access token cache.
Cache : Token_Cache;
-- The expiration time for the generated authorization code.
Expire_Code : Duration := 300.0;
end record;
-- The Token_Validity record provides information about a token to find out
-- the different components it is made of and verify its validity. The Validate
-- procedure is in charge of checking the components and verifying the HMAC signature.
-- The token has the following format:
-- ...hmac(.)
type Token_Validity is record
Status : Grant_Status := Invalid_Grant;
Ident_Start : Natural := 0;
Ident_End : Natural := 0;
Expire : Ada.Calendar.Time;
end record;
function Validate (Realm : in Auth_Manager;
Client_Id : in String;
Token : in String) return Token_Validity;
end Security.OAuth.Servers;