Tuesday, September 17, 2024

Implementing SSO Authentication with Saml Using LotusScript & Java

Introduction

SAML (Security Assertion Markup Language) is a widely used protocol for Single Sign-On (SSO), allowing users to authenticate once and access multiple applications. In this article, we'll walk through implementing SAML authentication in a Domino application, detailing the steps needed to provide metadata, make the SSO call, and handle the assertion response (ACS call).

Prerequisites

Before you begin, ensure you have:

  • Access to a SAML Identity Provider (IdP) such as Microsoft Azure AD or Okta.
  • Basic knowledge of Java and HTTP requests.
  • A configured Domino server with the capability to execute Java agents.

Step-by-Step Implementation

1. Providing Metadata

The first step in setting up SAML authentication is to provide metadata that the Identity Provider (IdP) and Service Provider (SP) can use for configuration. You’ll need to supply both IdP and SP metadata to the Domino application.

Basically it means you will need to make 2 public URL: https://yourdomain.com/meta/{id} that provide XML response (and that will be used by IdP) and additionally to that IdP sohuld provide their URL you are going work with.

You also need to find a suitable Java library that can provide your a SamlClient (I use https://github.com/lastpass/saml-sdk-java)

2. Making the SSO Call

Once the metadata is set, you can make the SSO request to the IdP. Here’s the code that generates the SAML authentication request and sends it to the IdP.

String idpURL = config.getItemValueString("IdPURL");
idpMeta = new URL(idpURL).openStream();

String spData = config.getItemValueString("MetaXML");
spMeta = new ByteArrayInputStream(spData.getBytes(StandardCharsets.UTF_8));

// 2. initialize saml client
SAMLInit.initialize();
IdPConfig idpConfig = new IdPConfig(idpMeta);
SPConfig spConfig = new SPConfig(spMeta);
SAMLClient client = new SAMLClient(spConfig, idpConfig);

// 3. send login request
String requestId = SAMLUtils.generateRequestId();
String authrequest = client.generateAuthnRequest(requestId);
String url = client.getIdPConfig().getLoginUrl() + "?SAMLRequest=" + URLEncoder.encode(authrequest, "UTF-8");

The SAMLClient is used to generate the authentication request, and the resulting URL is the login endpoint where the user must be redirected.

3. Handling the Assertion Consumer Service (ACS) Call

After the user is authenticated by the IdP, the SAML response is sent back to the Domino server via the Assertion Consumer Service (ACS) endpoint. The following code handles this response and extracts user attributes from it.

String authresponse = web.getParam("AuthResponse");
String idpURL = config.getItemValueString("IdPURL");
String spData = config.getItemValueString("MetaXML");

SAMLInit.initialize();
IdPConfig idpConfig = new IdPConfig(idpMeta);
SPConfig spConfig = new SPConfig(spMeta);
SAMLClient client = new SAMLClient(spConfig, idpConfig);

AttributeSet aset = client.validateResponse(authresponse);
Map> attr = aset.getAttributes();
for (Map.Entry> entry : attr.entrySet()) {
    String key = entry.getKey();
    List values = entry.getValue();
    doc.replaceItemValue(key, new Vector<>(values));
}

This code validates the SAML response using the SAMLClient, extracts the attributes (such as username, email, etc.), and stores them in the Domino document.

Conclusion

Implementing SAML authentication in Domino involves configuring metadata for the IdP and SP, making an authentication request, and handling the ACS call to process the response. While this example uses the LastPass SAML library, similar methods apply when using other libraries. The overall process is straightforward and can greatly improve the security and convenience of user authentication in your Domino applications.

Monday, September 16, 2024

Implementing SSO Authentication with OpenID Using LotusScript

Introduction

Single Sign-On (SSO) simplifies the user authentication process by allowing users to log in once and gain access to multiple applications. OpenID Connect is an authentication layer on top of OAuth 2.0 that facilitates SSO. In this article, we'll walk through implementing SSO authentication using OpenID Connect with LotusScript.

Prerequisites

Before diving into the code, ensure you have:

  • Basic knowledge of LotusScript and HTTP requests.
  • Familiarity with OpenID Connect and OAuth 2.0.
  • Access to a Microsoft Azure AD tenant or another OpenID Connect provider.

Step-by-Step Implementation

1. Setting Up Your Environment

Make sure your Lotus Domino server is properly configured to handle HTTP requests and that you have access to your OpenID Connect provider's endpoints.

2. Retrieving the Access Token

The access token is obtained after the user successfully authenticates. I am pretty sure outdays most of developer know how to parse values from DocumentContext (fieldsÆ QUERY_STRING or REQUEST_CONTENT). In example below I just use my own class but really you can do it in a few lines if needed

access_token = web.GetRequestParam("access_token")
token_type = web.GetRequestParam("token_type")

3. Making API Requests

Use NotesHTTPRequest to communicate with the OpenID Connect provider’s API. Set the Authorization header with the access token:

Dim session as NotesSession
Dim http As NotesHTTPRequest
Dim jsonNav as NotesJSONNavigator

Set session = new NotesSession
Set http = session.Createhttprequest()
http.Preferjsonnavigator = True
Call http.Setheaderfield("Authorization", token_type & " " & access_token)
Set jsonNav = http.Get("https://graph.microsoft.com/v1.0/me")

4. Parsing the Response

Handle and parse the JSON response to extract user information:

On Error 4843 Resume Next
Dim jsonEl As NotesJSONElement
Dim jsonObj As NotesJSONObject
Set jsonEl = jsonNav.getelementbyname("error")
If Not jsonEl Is Nothing Then
    Set jsonObj = jsonEl.Value
    Print |Status: 401|
    Print "<h2>Error</h2>"
    Print "<p>" & jsonObj.Getelementbyname("code").Value & "</p>"
    Print "<p>" & jsonObj.Getelementbyname("message").Value & "</p>"
    Call scriptLog.LogInfo(jsonNav.Stringify())
    Exit Function
End If

mail = jsonNav.Getelementbyname("mail").Value
displayName = jsonNav.Getelementbyname("displayName").Value

Knowing user's email or other unique data will help you to find a user in your application and make necessary steps for auth.

Conclusion

In my case, I have a web application written in Domino where users can register and sign in without using names.nsf. This approach allows for seamless authentication using OpenID, bypassing the traditional Domino authentication model.

While this solution does not allow users to authenticate directly with Domino, it is still a significant step in that direction. By integrating OpenID Connect, we are moving closer to a more flexible authentication model that can eventually be expanded to support Domino authentication.

Thursday, November 02, 2023

Configuring Entitlement Tracking in Domino 12

In the realm of HCL Domino Server 12.0, the feature of "Entitlement Tracking" has become a vital component for organizations.

While comprehensive information regarding Entitlement Tracking is available through HCL, I needed to know some practical management aspects, such as disabling the feature and adjusting intervals etc.

Disabling Entitlement Tracking:

To disable entitlement tracking, add the following entry and restart the server:

DISABLE_ENTITLEMENT_TRACKING=1

Debugging for Entitlement Tracking Issues:

Debug settings can be incredibly useful for troubleshooting any issues related to Entitlement Tracking. Here's how to configure debugging options:

DEBUG_ENTITLEMENT_AGGREGATOR_INTERVAL=60
DEBUG_UPDATE_ENTITLEMENT_TRACKING=2
ES_OPT_TIMING=1
DEBUG_DIRCAT=3

Hope that will help somebody.

Friday, October 27, 2023

Overcoming Domino's Agent Scheduling Limitations with JavaAddin

If you're frustrated with Domino's limitations on scheduling agents to run more frequently than once every 5 minutes, you're not alone. As a programmer, you understand the need for flexibility and control in your applications. In this article, we'll discuss a practical solution: creating a JavaAddin for Domino that can trigger agents at shorter intervals, allowing you to gain more fine-grained control over your scheduled tasks.

Understanding the Domino Agent Scheduler


Domino provides a robust environment for running scheduled agents. However, it imposes a minimum time gap of 5 minutes between consecutive runs of the same agent. This limitation can be a roadblock for applications that require more frequent execution.


The Power of JavaAddins


JavaAddins offer a way to extend Domino's functionality using Java code. This opens up a world of possibilities, including overcoming the 5-minute scheduling restriction. Here's how you can do it:

1. Setting Up Your JavaAddin

To get started, you'll need to create a JavaAddin. This involves writing Java code to interface with Domino. The code should enable you to trigger agents at shorter intervals than what Domino's native scheduling allows.

2. Utilizing Timers

One of the most effective ways to bypass the 5-minute limitation is to use timers in your JavaAddin. With timers, you can execute your agent at precise intervals, even down to seconds. Here's a simplified example of how this could look in your Java code:
import lotus.domino.*;
public class CustomScheduler extends JavaServerAddin {
    public void runNotes() {
        try {
            Session session = NotesFactory.createSession();
            Database database = session.getDatabase("", "YourDatabase.nsf");
            Agent agent = database.getAgent("YourAgent");

            // Set the execution interval in milliseconds
            int interval = 30000; // 30 seconds

            while (true) {
                agent.runWithDocumentContext(null);
                sleep(interval);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
This code defines a Java thread that runs your specified agent every 30 seconds, effectively bypassing Domino's 5-minute restriction.

3. Deploying Your JavaAddin

Once you've created your JavaAddin, you need to deploy it within your Domino environment. Ensure that the necessary permissions and access controls are in place.

4. Monitoring and Maintenance

Regularly monitor the execution of your custom scheduling solution. Ensure that it's working as expected and doesn't place undue stress on your Domino server.


Conclusion


By creating a JavaAddin for Domino that can trigger agents more frequently, you can take control of your scheduling needs. This solution empowers you to run your agents at shorter intervals, achieving the level of precision your applications require. While this approach requires some development effort, the benefits of fine-grained agent scheduling can greatly enhance your Domino-based applications.

In summary, if you're tired of being constrained by Domino's 5-minute scheduling limitation, consider the power of JavaAddins to break free and gain control over your scheduled agents.

I have also created a bit more advanced setup which you can get on github: DominoAgentsHelper 

Stay tuned for more technical insights, delivered directly to the point, in future articles.

Monday, September 25, 2023

Change Database ReplicaID programmatically

Here is a solution that change ReplicaId of NotesDatabase. Since native capabilities of LotusScript/Java classes do not allow such operation (at least yet), there is a way to do it using C Notes API. Our envrionment consists of both: Windows and Linux servers therefore I had to make a solution that cover both OS.

Declare

Public Const W32_LIB = {nnotes.dll}
Public Const LINUX_LIB = {libnotes.so}

Type TIMEDATE
	Innards(0 to 1) As Long
End Type

Type DBREPLICAINFO
	ID As TIMEDATE			'ID that is same for all replica files
	Flags As Integer		'Replication flags
	CutoffInterval As Integer	'Automatic Replication Cutoff
	Cutoff As TIMEDATE		'Replication cutoff date
End Type

Declare sub W32_OSCurrentTimeDate Lib W32_LIB Alias "OSCurrentTIMEDATE"(Ret As TIMEDATE)
Declare Function W32_NSFDbOpen Lib W32_LIB Alias "NSFDbOpen" (ByVal dbName As String, hdb As Long) As Integer
Declare Function W32_NSFDbClose Lib W32_LIB Alias "NSFDbClose" (ByVal hdb As Long) As Integer
Declare Function W32_NSFDbReplicaInfoGet Lib W32_LIB Alias "NSFDbReplicaInfoGet" (ByVal hdb As Long, hdbr As DBREPLICAINFO) As Integer
Declare Function W32_NSFDbReplicaInfoSet Lib W32_LIB Alias "NSFDbReplicaInfoSet" (ByVal hdb As Long, hdbr As DBREPLICAINFO) As Integer

Declare Sub LINUX_OSCurrentTimeDate Lib LINUX_LIB Alias "OSCurrentTIMEDATE"(Ret As TIMEDATE)
Declare Function LINUX_NSFDbOpen Lib LINUX_LIB Alias "NSFDbOpen" (ByVal dbName As String, hdb As Long) As Integer
Declare Function LINUX_NSFDbClose Lib LINUX_LIB Alias "NSFDbClose" (ByVal hdb As Long) As Integer
Declare Function LINUX_NSFDbReplicaInfoGet Lib LINUX_LIB Alias "NSFDbReplicaInfoGet" (ByVal hdb As Long, hdbr As DBREPLICAINFO) As Integer
Declare Function LINUX_NSFDbReplicaInfoSet Lib LINUX_LIB Alias "NSFDbReplicaInfoSet" (ByVal hdb As Long, hdbr As DBREPLICAINFO) As Integer

Code C API (main part of it)

'GET CURRENT TIMEDATE (TO BUILD NEW REPLICAID): OSCurrentTimeDate
If IS_WINDOWS Then
	Call W32_OSCurrentTimeDate(ReplicaID)
Else
	Call LINUX_OSCurrentTimeDate(ReplicaID)
End If
ReplicaInfo.ID = ReplicaID
	
'SET NEW REPLICAID: NSFDbReplicaInfoSet
If IS_WINDOWS Then
	rc = W32_NSFDbReplicaInfoSet(hDb, replicaInfo)
Else
	rc = LINUX_NSFDbReplicaInfoSet(hDb, replicaInfo)
End If

You can find all solution on GitHub: DominoChangeDatabaseReplicaID