LDAP-Gruppen in HCL Connections angepasster JSP auslesen und Menüeinträge nur für Gruppenmitglieder anzeigen

Möchtest du mithilfe von zentralen LDAP-Gruppen bestimmte Anpassungen innerhalb von Connections vornehmen? Dann bist du hier richtig: Dieser Artikel zeigt, wie sich alle Gruppen in denen ein Nutzer Mitglied ist, per Java in den JSPs (z.B. header.jsp) auslesen lassen. Damit ist es möglich, HCL Connections anzupassen. Beispielsweise kann man einzelne Menüeinträge nur für Mitglieder einer Gruppe anzeigen.

Hierfür habe ich mir ein JSP Codeschnipsel erstellt:

<%@ page import="com.ibm.websphere.security.cred.WSCredential" %>
<%@ page import="com.ibm.websphere.security.auth.WSSubject" %>
<%@ page import="javax.security.auth.Subject" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.Set" %>

<%
String userName = WSSubject.getCallerPrincipal();
Subject subject = WSSubject.getCallerSubject();
Set credSet = subject.getPublicCredentials(WSCredential.class);
Iterator iter = credSet.iterator();
WSCredential creds = (WSCredential) iter.next();
List credsList = creds.getGroupIds();
List<String> lowerGroupNames = new ArrayList();

System.out.println("--------------------------------------------");
System.out.println("Members [by header.jsp] for user = " + userName);
for(Object rawGroup:credsList){
    String groupName = rawGroup.toString().split("/")[1];
    groupName = groupName.split(",")[0]
        .split("=")[1];
    System.out.println("Group: " + groupName);
    lowerGroupNames.add(groupName.toLowerCase());
}
System.out.println("--------------------------------------------");
%>

Es ließt alle Gruppen des Benutzers aus und schreibt diese in die SystemOut.log Datei. Wobei dessen Lokation von der jeweiligen App abhängt, die wir aufrufen. Beispiel: Wikis liegt im AppsCluster, somit finden wir die Logs dort – etwa unter /opt/IBM/WebSphere/AppServer/profiles/CnxNode01/logs/AppsCluster_server1/SystemOut.log. Wird auf der Startseite (Homepage) getestet, befindet sich die Anwendung im UtilCluster und somit in UtilCluster_server1.

[8/30/21 15:21:01:950 UTC] 00000133 SystemOut     O --------------------------------------------
[8/30/21 15:21:01:950 UTC] 00000133 SystemOut     O Members [by header.jsp] for user = max
[8/30/21 15:21:01:950 UTC] 00000133 SystemOut     O Group: cnx-tester
[8/30/21 15:21:01:950 UTC] 00000133 SystemOut     O --------------------------------------------

Dies nur als Unterstützung zum Testen. In der Praxis möchten wir natürlich eher etwas ausführen, wenn eine Gruppenmitgliedschaft vorliegt (oder eben nicht). Mein Code erzeugt eine ArrayList lowerGroupNames. Darüber lässt sich unabhängig von der Schreibweise Im Java-Code prüfen, ob eine gewisse Gruppe vorhanden ist:

boolean canUseTestFeatures = lowerGroupNames.contains("cnx-tester");
System.out.println("IsTester: " + canUseTestFeatures);
System.out.println("--------------------------------------------");

Um damit nun dynamisches HTML zu erzeugen, werden JSF (JavaServer Faces) benötigt. Diese können erst genutzt werden, wenn die Bibliothek eingebunden ist. Im Beispiel der header.jsp also nach folgender Zeile:

--%><%@ taglib prefix="c"        uri="http://java.sun.com/jsp/jstl/core" %><%--

Variablen aus dem Java-Code sind hier nicht automatisch verfügbar, sondern müssen zunächst gesetzt werden.

<c:set var="lowerGroupNames" value="<%=lowerGroupNames%>" />
<b style="color:red; margin-left:15px;">
        <c:choose>
            <c:when test='${fn:contains(lowerGroupNames, "cnx-tester")}'>
                Du bist ein Tester! :)
            </c:when>
            <c:otherwise>
                Leider bist du noch kein Tester :/
            </c:otherwise>
        </c:choose>
</b>

<%

Damit erzeugen wir links oben in der Navigation einen unschönen, aber als POC ausreichenden Text, abhängig davon ob der Benutzer Mitglied in der LDAP-Gruppe cnx-tester ist.

Anpassen des Header-Menüs: Eintrag nur für Mitglieder einer Gruppe anzeigen

Mein primärer Anwendungsfall dafür war: Wir haben neue, experimentelle Funktionen, die wir zwar gerne als Link in das Menü einbinden möchten. Doch vorerst soll nicht jeder die Links sehen, sodass wir dies nach erfolgreichen Tests erst für alle Verfügbar machen können. Dazu erstellen wir ${SHARED_DIRECTORY}/customization/common/nav/templates/menu und kopieren apps.jsp aus einer beliebigen Anwendung (z.B. Wikis) dort hinein:

mkdir /opt/IBM/shared/customization/common/nav/templates/menu
cp /opt/IBM/WebSphere/AppServer/profiles/CnxNode01/installedApps/CnxCell/Wikis.ear/wikis.web.war/nav/templates/menu/apps.jsp /opt/IBM/shared/customization/common/nav/templates/menu

/opt/IBM/shared/customization/common/nav/templates/menu/apps.jsp kann nun mit einem Texteditor geöffnet werden, um Anpassungen vorzunehmen. In diesem Falle möchte ich ein Element an das Ende setzen, also suche ich den schließenden Tag der Tabelle:

--%></table></div><%--

Davor laden wir die Gruppen des Benutzers und binden eine neue Zeile ein, falls er in unserer Testgruppe Mitglied ist. Damit es ansprechender aussieht, kommt ein Icon des Herstellers zum Einsatz und es wird ein kleiner roter NEU Banner angefügt.

<%--
 Custom navigation: Show new boards only for members of the tester group
--%>
<%@ page import="com.ibm.websphere.security.cred.WSCredential" %>
<%@ page import="com.ibm.websphere.security.auth.WSSubject" %>
<%@ page import="javax.security.auth.Subject" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.Set" %>

<%
String userName = WSSubject.getCallerPrincipal();
Subject subject = WSSubject.getCallerSubject();
Set credSet= subject.getPublicCredentials(WSCredential.class);
Iterator iter= credSet.iterator();
WSCredential creds = (WSCredential) iter.next();
List credsList = creds.getGroupIds();
List<String> lowerGroupNames = new ArrayList();

for(Object rawGroup:credsList){
    String groupName = rawGroup.toString().split("/")[1];
    groupName = groupName.split(",")[0]
        .split("=")[1];
    lowerGroupNames.add(groupName.toLowerCase());
}
%>


<c:set var="lowerGroupNames" value="<%=lowerGroupNames%>" />
<c:if test="${fn:contains(lowerGroupNames, 'cnx-tester')}">
                <tr>
                  <th scope="row" class="lotusNowrap">
                        <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAFCnpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjarVdtkuuqDvzPKu4SEAIkloP5qLo7eMu/jcFO4jgzJ6deXGMBFiC6G8GY9r9/u/kHP0fRGR9EY4rR4ueTTy6joHb+piXr9/eq2KPw0m7ODw5NDMuzGtvyz2gPjw7iV/        +++v22m6krHF0DXSMvAbkMbNDYfnpGojdbKdVN2n1y/5pOeuPZR/idL7WvQCMGtDIzrjGxBbvOGbh8UecYRVv5uFELCh7Tvub77EzZ/EC3lm6YGfzaudXKIyNyyFeMFrtFO6x2xG6sHbM/PIhbFbs8+8Ju96r9t7m6rKPQCqatahjKXsJjhugnGhEPIK/                                                                                         +++gLLsT8KjWGIB6BVsbniKoUQOaHfyVClTp7bbQgUhetecwDpXwMFoUxaXXOFBgR8PdSegoRqw47iANUazO2Ohfd60z1dIMXMleDrCYIQeb4+5a/yb5xyo9yFdIqsnVojLDQEijMHceMMLhFBfmIYd3/0xT7qxT8QyGAw7zIoFZrvNIbZAD23xzjPDL1hv7JQ7SV0DACLMHRAMMRiwkThQJCvOCRFwVPCTEblj7zYwQCG4SqaDG+YIctSNudFHaPd1wc1mpBYQEThiq+     +++jYKCDL+wD9iFdoKAcO3oQQYpCgIYUcOfoYYowSR47KwuIlSBQRlSRZWb0GjSqqmjQnlxgpLKSYxCRNKeWMSTOGzuid4ZHz5jbe/Ba2uMmmW9pygXyKL6HEIkVLKrm6yhXbv8YqpmpNNTdqkFLzLbTYpGlLLXdorXP3PfTYpWtPPZ+sLVZfWaMLcz+zRou1wZjf/eTBGppFjiFopJMwOANjzhMYl8EABO0GZ1bJezeYG5zZ5LApggNrFAY5lQZjYNA3cqHTyd2DuR95M8F/ +++xZv7xJwZ1P0/mDODusXcO283rNW8nyi8EzR24cDUckdig0PT7DSPM+nedsS3F3vJpW/IavsX+2TNteHWcgtpL249eG4jwV1nNH8dwsUa90ch/R6i+SqUpMgvPoyT9mrNpw/3Vt9BwE1mFE3uOLy7yiNo6GgGDRW19ujh7Wr3gpWVuta2+Y7D4wp2WYOoot/F9dUzy4hw7C5PLY7Ehpo01Ovb+pFGSh3F0DNOPNsEEvazQ4hSy3MHs/                             +++doSstBmxTsBZ7VmJrodXhdg9HodnqaO1c/lV5guZSDxeL79lkX5pNQcp0AtVa6LZUXXLbxmysPCx3htN7sT54uT6Q0IZP5hVpCJvZ+IjLucOYo/GzHHW/HoqbUvN6IyuyFbbkFKi3IjM0if84wkbHP4ML8GJBe05JKc7ZHNe6XDHK1ta3uxXbGRWXV2JTu38TWapxM1bEFGqelzCa+R6T2Gb5CiwfhtJkliqrQJ25As1YktaRfbUMzRvvzwajmQ+sQPhfhw9sgN+       +++MuOBfVrW/plIcsJQL2grveWq12rvx1qsWFcXbPlUtzdY8rbO9piVY+CoATp+PeqXpshpnGHn40F2RDKm2tjYrk/LUgf7Oxr4jAKo7JlSSi4qJWV6Tx8A1lagD3a4ssshATxkGXp7K/zvy31rzx4yblOHATAlzpteA/Jv+juA3UfSfua24/UvvHlG6+21vrrKq4065dxm4GYZ72vseNqBzKRYbe/Fs2KUdqbVml8LnVejC8aMPdtMuXtKOw4sVe+                    +++3gSxpUWtfyG2xv9H4H8E2u+SWq3oaxIzP1d4/vQzF+H8DJz76Zij5v/AK4ZC0T5k1m1AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5QgeDzEtgjVXBgAAAWlJREFUOMul0z9rFUEUBfDfxMVCiTaC4CNBCLEPCNpYGET9ACkFmydb5gsEK+1MYSVLxMovYBOSxhQKFgFTKqhpQnwqSIrElz9PMha7L7s7aEQ8MMWZO+          +++dw586cIEEsTCHHNCar7Q94iSLk1prnQ0N4Co/RdTyeYjbk+kcGlXiJcM1fEeEVbg9NxMJCLMS4vhpbSPlWL8bCcC3ASHXnrn9HNxamsmpgJb68ax9J+c731CTPqmmX6PdY36zLZy+y8ZrBXslPjqYG01njqdjbZvNBXb7ygo1lfiyVvDOXGkyO+E9k+IQJcLjLpUd19fMqnZu4UfKD3VT/PsObI4PePL1GuTPHzwH72yU/M5YaLGZ4hjt/                         +++7PHrSj0DrRkM8CQLuZVYWMat3xqcv87+5bqD+pEehtzH4Ve+UF1lvK0+QTjd4IfEnWEe7oW8HaZxPMdxeejjPuZDnqSxMoEZ3MVVnMMB3mKxivO3puYX3dKOKWGlWaAAAAAASUVORK5CYII=" />
                        <a href="/boards/auth/connections">
                          <strong>Boards</strong>
                          <span class="kudos-new">NEU</span>
                          <style>
                                  .kudos-new{
                                        position: absolute;
                                        right: -5px;
                                        top: 0;
                                        background: red;
                                        text-align: center;
                                        border-radius: 1px 1px 1px 1px;
                                        color: white;
                                        font-size: 10px;
                                        padding: 1px !important;
                                  }
                          </style>
                        </a>
                  </th>
                </tr>
        </c:if><%--
--%></table></div><%--

Bei einem Tester (= Mitglied der Gruppe) sieht das so aus:

Ein anderer Benutzer, der kein Mitglied dieser Gruppe ist, sieht dagegen das 0815 Standard-Menü:

Achtung: Die Anwendungsnavigation wird per Ajax nachgeladen! Meiner Erfahrung nach cachen viele Browser das sehr aggressiv. Ihr aktualisiert also die Seite und seht das alte Menü – obwohl ihr den CNX Debugmodus aktiviert bzw. neu gestartet habt. Daher solltet ihr mit F12 die Entwicklerkonsole öffnen und dort Cache deaktivieren anhaken. So lange die Konsole geöffnet ist, bleibt dies aktiv. Ihr könnt sie verkleinern bzw. in einem neuen Fenster öffnen und minimieren, falls sie im Weg ist.

Erweiterungen: Lokalisierung + Untereinträge

Obiges Beispiel ist zur reinen Demonstration in Ordnung. Für den Praxiseinsatz sollten noch zwei weitere Dinge eingebaut werden:

1. Lokalisierung

Zumindest wenn ihr Connections im internationalen Raum einsetzt oder dies vorhabt, sollten Texte nicht hartkodiert eingebaut werden. Stattdessen die in CNX eingebaute Lokalisierung verwenden, die sich auch mit eigenen Sprachschnipseln anpassen lässt. Im Falle von Boards haben wir die sogar bereits, sofern das alte WebSphere-Boards installiert ist. Dann können wir Zeile 1 durch Zeile 2 ersetzen:

<strong>Boards</strong>
<strong><fmt:message key="connections.component.name.kudos.boards" /></strong>

Für das „NEU“ Badge müsste jedoch eine eigene Lokalisierung erzeugt werden, sofern man dies nicht einheitlich „NEW“ oder ähnlich bezeichnen kann.

2. Untergeordnete Einträge hinzufügen

Auf den obigen Screenshots sieht unser eigener Eintrag leer aus, da er in der von Connections vorgesehenenen zweiten und dritten Ebene nichts enthält. Falls untergeordnete Links für euren Anwendungsfall Sinn machen, würde ich diese einbauen. Im Beispiel des neuen, docker-basierten Boards könnten wir einen Direktlink zum gewohnten Aufgaben-Board einbauen. Dazu einfach ein td Element unter th einbauen:

</th>
<td class="lotusNowrap lotusLastCell">
      <a href="/boards/todos/kanban/assigned">
        <fmt:message key="label.menu.kudos.boards.todos" />
      </a>
</td>

Natürlich könnten wir das auch hier hart kodieren statt mit <fmt:message zu lokalisieren, wobei letzteres zu bevorzugen wäre. Als Ergebnis haben wir in beiden Fällen einen zweiten Link, neben dem Hauptelement:

Bei Bedarf kann man mit einem entsprechenden weiteren <td> Element auch die dritte Ebene füllen.

Neues Anwendungsmenü für alle sichtbar, die berechtigt sind? Die verschiedenen Ebenen des Caches

Auch wenn die Frage trivial erscheint, hat sich dies bei meinen Tests als nicht so einfach erwiesen. Es gibt Caching auf verschiedenen Ebenen. Teils serverseitig, abhängig vom Browser wird zudem clientseitig ebenfalls zwischengespeichert.

1. WAS Authentifizierungscache

WebSphere speichert Authentifizierungsdaten zwischen. Standardmäßig für 10 Minuten, es scheint sich um Global Security > Authentication cache settings zu handeln:

Damit neue Einstellungen wirksam werden, nach dem Speichern eine vollständige Neusynchronisation aller Nodes durchführen und die Nodes neu starten.

2. JSP-Cache

Die JSPs selbst (zumindest jene vom Layout wie header.jsp) werden nicht bei jedem Seitenaufruf ausgeführt. Stattdessen scheint es hier einen Zwischenspeicher zu geben, der beim neu einloggen geleert wird. Oder beim serverseitigen Neustart. Dadurch kann folgendes Szenario entstehen: Ihr habt eingebaut, dass etwas bestimmtes für Mitglieder der Gruppe X erscheint, entsprechend neu gestartet. Ein Nutzer der kein Mitglied dieser Gruppe ist, ruft Connections auf. Danach nehmt ihr ihn in Gruppe X auf. Wenn als Authentifizierungscache der Standard von 10 Minuten eingestellt ist, sieht dieser Nutzer die Berechtigungen durch die neue Gruppe nicht nach 10 Minuten, da das Ergebnis der JSP bereits zwischengespeichert ist und der Code daher nicht neu ausgeführt wird. Die Änderung wird erst sichtbar, wenn der Nutzer sich neu einloggt.

3. Browser-Cache

Zusätzlich haben wir im Falle der Navigation noch einen dritten Cache, der sich außerhalb des Servers befindet: Das Anwendugnsmenü wird nicht zusammen mit dem Hauptdokument gerendert, sondern per Ajax nachgeladen. Viele Browser speichern diese Anfragen ebenfalls gesondert zwischen. Dadurch kann es sein, dass serverseitig nun zwar alle Änderungen übernommen wurden. Aber der Browser trotzdem die alten Navigationseinträge aus seinem Cache zeigt.

Was kann man gegen diese Flut an Caches tun?

Das in meinen Augen beste was man tun kann ist, den Zeitstempel zu erhöhen zusammen mit einem Neustart. Der Zeitstempel kommt als Caching-Burster an verschiedenen Stellen zum Einsatz und soll den Browser-Cache aktualisieren. Dazu öffnet man eine wsadmin Konsole und führt folgende Befehle aus, wobei der Zielordner /tmp/checkout zuvor händisch angelegt werden muss:

execfile("connectionsConfig.py")
LCConfigService.checkOutConfig("/tmp/checkout", AdminControl.getCell())
LCConfigService.updateConfig("versionStamp","")
LCConfigService.checkInConfig()
synchAllNodes()

Anschließend alle AppServer bzw. den kompletten Node/die Nodes neu starten. Je nach verwendetem Browser reicht das clientseitig oder es muss ggf. dieser neu gestartet werden bzw. das löschen vom Cache ist erforderlich.

Tipp: Anwendungsmenü dauerhaft sichtbar machen

Da das Anwendungsmenü nur beim überfahren mit der Maus sichtbar ist, gestaltet sich das Testen im Browser als schwierig. Ich benutze gerne die Entwicklerkonsole, um Kleinigkeiten auszuprobieren. Das können wir mit etwas CSS einfacher gestalten: Entwicklerkonsole mit F12 öffnen und links oben nach #lotusBannerApps_dropdown suchen.

(1) Ist das Element, welches ihr nun finden solltet und (2) dessen CSS. Falls es nicht angezeigt wird, zunächst auf das HTML-Element links klicken. Unter dem Selektor Element den Haken bei display: none entfernen. Durch die Positionierung benötigt es noch eine Positionsangabe, um sichtbar zu werden. Im einfachsten FAlle klicken wir neben das Semikolon des letzten Elements und fügen top: 42px hinzu:

Wer es exakt so positionieren möchte wir es später aussehen wird, fügt noch left: 555.5px hinzu und geht bei top auf 42.6333px. Das wird aber dynamisch berechnet, möglicherweise sieht es bei euch mit anderen Auflösungen etwas anders aus. Wobei ich das mangels richtiger Responsiveness eher nicht erwarten würde.

Leave a Reply