bloggers bloggers

Marco Napolitano
Messaggi: 79
Stelle: 0
Data: 17/02/22
Jader Jed Francia
Messaggi: 63
Stelle: 0
Data: 18/02/21
Paolo Gambetti
Messaggi: 2
Stelle: 0
Data: 11/11/19
Katia Pazzi
Messaggi: 1
Stelle: 0
Data: 27/06/19
Ezio Lombardi
Messaggi: 11
Stelle: 0
Data: 10/04/18
Chiara Mambretti
Messaggi: 25
Stelle: 0
Data: 27/02/17
Serena Traversi
Messaggi: 3
Stelle: 0
Data: 21/07/16
Francesco Falanga
Messaggi: 8
Stelle: 0
Data: 14/06/16
Antonio Musarra
Messaggi: 2
Stelle: 0
Data: 18/11/13
Simone Celli Marchi
Messaggi: 6
Stelle: 0
Data: 09/07/13
Indietro

Sincronizzare permessi tra modelli differenti!

Buongiorno a tutti e ben ritrovati con "Le cronache di Jader", appunti -quasi- sensati per sopravvivere al difficile mondo dell'IT su Liferay!

Oggi vi parlerò di un problema che i miei animali mitologici mi hanno posto la scorsa settimana: mantenere sincronizzati i permessi tra due modelli differenti!

Come al solito, partirò raccontandovi il perché di questo requisito con un caso d'uso che spero sia sensato, così che possiate anche voi trarne spunto e capire velocemente se fa al caso vostro (non il caso d'uso, la soluzione! :)).

Mantenere sincronizzati i permessi di due modelli differenti: caso d'uso

Il caso d'uso è semplice: ho due oggetti (nel mio caso un oggetto custom che chiameremo, con enorme fantasia ;), ModelloCustom che al suo interno ha una referenza ad un DlFileEntry.

Il caso d'uso vuole che, a prescindere da quale delle due entità io parta a modificare i permessi, i permessi dell'altra entità siano mantenuti sincroni.

Quindi, per spiegarmi più semplicemente, se io modifico i permessi su ModelloCustom aggiungendo, che ne so, il permesso di VIEW e il permesso di UPDATE a uno specifico ruolo, coerentemente con questo aggiornamento mi troverò modificati gli stessi due permessi sullo stesso ruolo dell'entità DlFileEntry.

Cose da NON fare per gestire questo caso d'uso!

Prima di dirvi come fare a gestire questo caso :), vi dico quello che non dovete fare per gestirlo! :)

Intendo che si, anche io ci ho messo un po' per risolverlo e, nel mio "tentare", ho provato diverse strade per riuscirci.

Abusare dei value object listener: cattiva idea.. :/

La prima strada che ho percorso era quella dei value ojbect listener: ho creato dei listener (che dovrebbero servire a gestire l'integrità referenziale ma dei quali io ho abusato per i miei scopi.. ;)), uno per ModelloCustom e uno per DlFileEntry intercettando la modifica all'evento relativo sull'entità ResourcePermission.

Sembrava funzionare ;), però poi mi sono accorto che gli update finivano per "rompere" i permessi; nel senso: le update venivano propagate correttamente ma, non so il perché :), quando il pannellino dei permessi si ricaricava questo mostrava permessi diversi da quelli che avevo impostato.

Sicuramente i miei animali mitologici si sarebbero lamentati di questo comportamento un po' bizzarro ;), quindi ho scartato questa implementazione e ho proseguito nel mio scouting..

Service Wrapper: panacea di tutti i miei mali?

Ovviamente la risposta è.. NO!

Posto che non ho capito perché ma la 7.1.2 non mi caricava il ServiceWrapper che avevo creato (o meglio: caricare lo caricava, però non lo invocava e questa cosa mi aveva un po' tediato..) ho pensato che comunque non sarebbe stata una gran implementazione: il service della ResourcePermission espone, #chevelodicoafare :), mille milioni di metodi e scoprire quali fossero quelli giusti mi sembrava già di per se troppo sbatti.. :)

Forte del fatto poi che non riuscivo a far invocare il mio ServiceWrapper mi sono arreso in -credo- 8 minuti scartando anche questa strada!

E allora.. Custom sia! :)

Confrontandomi con l'animale mitologico che nel frattempo stava umoralmente cambiando il suo mood nei confronti della cosa, sentendolo sempre più disperato e triste (e con l'avvicinarsi del terribile momento del Collaudo col Cliente -fase della vita di un (quasi) dev che mette sempre una certa adrenalina, ammettiamolo! :D) mi sono detto "beh, sai che c'è? Che se il pannellino maledetto standard non fa quello che voglio io, allora me ne faccio uno custom dove piloto quello che mi pare sui permessi e amici come prima!".

L'idea, parlandone anche con il mio animale mitologico, sembrava addirittura sensata! Ci siamo lasciati alla fine della telefonata super convinti che questa fosse LA strada!

... E invece no!

Questa cosa del custom a tutti i costi non è mai una buona idea, soprattutto quando lavori con un mastodonte come Liferay!

E quindi? Che ho fatto?

Beh, premetto che.. A posteriori "la cosa del custom a tutti i costi non è mai una buona idea", ma sul momento.. Ero partito per implementarla! :)

E siccome sono uno pigro mi son detto "non è che devo reinventare la ruota: guardiamo come fa Liferay a gestire la costruzione del pannellino e come legge poi i parametri dalla matrice Azioni / Ruoli che disegna e replico tutto nel mio pannellino custom!".

Mamma mia quanto sono pigro e furbo.. :D

Per fortuna so ancora leggere il codice (anche se faccio queste cose tipo alle 4 della mattina, ma vabbé..) e, facendo scouting del codice della com.liferay.portlet.configuration.web.internal.portlet.PortletConfigurationPortlet e più precisamente il suo simpa metodo updateRolePermissions ho conosciuto dei nuovi amici.. :)

PermissionPropagator: ecco la risposta che cercavo! ;)

Spoiler ON: faccio presente che l'obiettivo funzionale per il quale i PermissionPropagator sono stati inventati NON è quello al quale ambisco io. In realtà questi cosi sono stati progettati per Propagare sulle entità figlie i permessi (ad esempio se cambio il permesso di visibilità su un thread del forum e vorrei che tutte le risposte al suo interno fossero automaticamente allineate senza doverle modificare a mano..).

Però è anche vero che, alla fin fine, il mio obiettivo è simile: anche io vorrei che si propagassero i permessi tra il mio ModelloCustom e la mia DlFileEntry!

Quindi ho proceduto, senza indugio, a testare questa soluzione!

Vediamo un po' di codice, per capire se questa roba ha senso anche per voi! :)

Annotiamo la classe come Component

@Component(
        immediate = true,
        property = {
                "javax.portlet.name=" + PortletKeys.MODELLO_CUSTOM,
                "javax.portlet.name=" + PortletKeys.DOCUMENT_LIBRARY_ADMIN
        },
        service = PermissionPropagator.class
)

Siccome io volevo bidirezionalmente aggiornare i permessi, quindi sia che si partisse dai permessi di ModelloCustom sia che si partisse dai permessi del DlFileEntry, ho mappato il mio PermissionPropagator su entrambe le portlet di gestione!

Come sempre esiste una Base.. :)

public class MyPermissionPropagator extends BasePermissionPropagator {

...

}

Anche in questo caso ho esteso la classe base che Liferay mette sempre a disposizione...

Implementiamo il metodo richiesto dall'interfaccia!

@Override
    public void propagateRolePermissions(ActionRequest actionRequest,
String className, String primKey, long[] roleIds) throws PortalException {        
        if (ModelloCustom.class.getName().equals(className)) {
            propagateRolePermissionsToFileEntry(actionRequest, className, primKey, roleIds);
        } else if (DLFileEntry.class.getName().equals(className)) {
            propagateRolePermissionsToModelloCustom(actionRequest, className, primKey, roleIds);
        }
    }

Ho poi implementato il metodo che l'interfaccia PermissionPropagator richiede per funzionare!

Ho sfruttato le facility che la Base mi mette a disposizione! ;)

private void propagateRolePermissionsToModelloCustom(ActionRequest actionRequest,
String className, String primKey, long[] roleIds) throws PortalException {
        long fileEntryId = GetterUtil.getLong(primKey);
        ModelloCustom modelloCustom = modelloCustomLocalService.fetchByFileEntryId(fileEntryId);
        if (Validator.isNotNull(modelloCustom)) {
            for (long roleId : roleIds) {
                propagateRolePermissions(actionRequest, roleId, DLFileEntry.class.getName(),
fileEntryId, ModelloCustom.class.getName(), modelloCustom.getPrimaryKey());
            }
        }
    }

    public void propagateRolePermissionsToFileEntry(ActionRequest actionRequest,
String className, String primKey, long[] roleIds) throws PortalException {
        ModelloCustom modelloCustom = modelloCustomLocalService.getModelloCustom(GetterUtil.getLong(primKey));
        if (modelloCustom.getFileEntry() > 0) {
            for (long roleId : roleIds) {
                propagateRolePermissions(actionRequest, roleId, ModelloCustom.class.getName(),
modelloCustom.getPrimaryKey(), DLFileEntry.class.getName(), modelloCustom.getFileEntry());
            }
        }
    }

È qui che accade la magia! :)

La chiamata alla propagateRolePermissions è chiaramente quella che sfrutto per far accadere la magia e, nemmeno a dirlo, è quella messa a disposizione dalla mia Base!

Cosa fa di magico questo metodo?

In pratica fa una cosa molto.. Pratica! :)

Si preoccupa di capire quali sono i permessi in comune tra le due entità (mappati nella tabella ResourceAction) e poi li sincronizza! Tuto gratis, tutto trasparente e, soprattutto, tutto.. Che funziona! :)

Beh, anche per oggi abbiamo imparato qualche cosa di nuovo (cfr. "smettila di pensare che il custom sia l'unica via per perseguire l'obiettivo: su più di 1.000.000 di righe di codice ci sarà qualche cosa che fa già al caso tuo, no?:)") e siamo pronti a propagare permessi come se non ci fosse un domani! ;)

Alla prossima e divertitevi con Liferay 7.x, OSGi e... I vostri animali mitologici!! :)

A presto, ciao, J.

P.S. Stavo quasi per dimenticarmi!

Aggiungete questa riga nel vostro portal-ext.properties altrimenti non funzionerà mai l'implementazione che avete appena fatto!

##
## Permissions
##
    #
    # Set the following to true to enable propagation of permissions between
    # models.
    #
    # For example, when setting the permissions on a specific Wiki node, if you
    # assign a role a permission (e.g. DELETE), then the assignment of that
    # permission is also propagated to all Wiki pages that belong to that Wiki
    # node.
    #
    # The actual logic of how permissions are propagated among models is
    # specified per portlet. See liferay-portlet.xml's use of the element
    # "permission-propagator".
    #
    # Env: LIFERAY_PERMISSIONS_PERIOD_PROPAGATION_PERIOD_ENABLED
    #
    permissions.propagation.enabled=true

 

Commenti
Aggiungi Commento
Marco Napolitano
Nice job!
Inviato il 15/10/19 12.35.
Jader Jed Francia
Aggiungo un ulteriore dettaglio: questo meccanismo *non funziona* come i value object listener, quindi potete avere UN SOLO PermissionPropagator per TIPO di oggetto..
Cosa significa? Significa che se avete dei DlFileEntry collegati a due differenti oggetti, il PermissionPropagator che "ascolta" il DlFileEntry DEVE essere unico, altrimenti LR caricherà il primo che trova.. emoticon
Inviato il 18/10/19 5.46.
Marco Napolitano
Ma esattamente qual è l'evento che avvia il PermissionPropagator? E' forse il salvataggio dei permessi di una delle 2 entità?
Se l'evento è il salvataggio dei permessi, allora questa soluzione probabilmente non funziona su strutture non lineari.
Mi spiego meglio: supponiamo di essere nel caso "cartella di file" e "documenti al suo interno".
Se modifico i permessi della cartella, questi si propagano ai documenti (chiaramente il viceversa non avrebbe molto senso).
Ma se aggiungo un nuovo documento nella cartella cosa succede? I permessi vengono propagati lo stesso dalla cartella al documento? Se sì, come?
Inviato il 06/11/19 17.01.
Jader Jed Francia
Napo, dai!!! Non sei stato attento! ;)
Il PermissionPropagator viene registrato sul modello "da ascoltare"; successivamente, quando intercetta una modifica ai permessi di una istanza questo modello, si attiva e _propaga_.
Il caso d'uso che censisci tu, invece, è un'altra roba: è il concetto di "ereditarietà dei permessi" -che nel caso specifico della D&M è implementato OOTB come descrivi, per essere precisi- ma che serve a fare altro rispetto al PermissionPropagator.
Per essere espliciti, dove "Contenitore" è l'oggetto che "contiene" figli e "contenuto" sono i figli del "contenitore" (ad es. Folder -> contenitore, File -> contenuto):
- il PermissionPropagator viene utilizzato quando cambi i permessi sull'oggetto "contenitore" e devi propagare le modifiche ai suoi figli;
- l'Ereditarietà dei permessi tra "contenuto" e "contenitore", invece, è da gestire in maniera esplicita.

Spero d'aver risposto alla domanda; in caso contrario, come sempre ;), sono qui! ;)
Inviato il 07/11/19 7.39 in risposta a Marco Napolitano.
Carlos A.
Hi Jader,

We are trying to do the same at Document and Media with PermissionPropagator (see https://liferay.dev/en/ask/questions/development/inherit-propagate-permissions-on-document-library-does-not-work-as-wiki) without success

Debugging PortletConfigurationPortlet, in updateRolePermissions it seems that our custom PermissionPropagator does not ever get called or attached as a service to DL portlet It seems as it cannot be reached, but it is correctly deployed. Is there any any other point where PermissionPropagator should be activated in the portlet_

TIA

Carlos
Inviato il 25/03/22 19.17 in risposta a Jader Jed Francia.
Jader Jed Francia
Hi Carlos!
AFAIK, you need just to annotate the class with correct "listened portlets'; in my example those are mapped as per example:

@Component(
immediate = true,
property = {
"javax.portlet.name=" + PortletKeys.MODELLO_CUSTOM,
"javax.portlet.name=" + PortletKeys.DOCUMENT_LIBRARY_ADMIN
},
service = PermissionPropagator.class
)

If you has added the property permissions.propagation.enabled=true and annotated the class correctly it should works also for you..

Your use case is D&M ON D&M or, like my use case, between custom portlet e D&M?

Let me know; cheers, JF
Inviato il 25/03/22 19.50 in risposta a Carlos A..
Carlos A.
Hi Jader,

First, thanks for answering. What we are trying is to propagate permissions when your editing permissions on certain folder at D&M. So in our cae is D&M on D&M.

But it seems that our custom PermissionPropagator class is not reached or visible.

We are trying with a very dummy class at the moment just to see if it is triggered but unfortunately it is not.

package com.liferay.document.library.web.internal.security.permission;


import com.liferay.portal.kernel.dao.orm.QueryUtil;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.security.permission.propagator.BasePermissionPropagator;
import com.liferay.portal.kernel.security.permission.propagator.PermissionPropagator;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.document.library.content.model.*;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import javax.portlet.ActionRequest;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;


@Component(
immediate = true,
property = {
"javax.portlet.name=" + "com_liferay_document_library_web_portlet_DLPortlet",
"javax.portlet.name=" + "com_liferay_document_library_web_portlet_DLAdminPortlet",
"javax.portlet.name=" + "com_liferay_document_library_web_portlet_IGDisplayPortlet"
},
service = PermissionPropagator.class
)
public class DLFileEntryPermission extends BasePermissionPropagator {


@Override
public void propagateRolePermissions(
ActionRequest actionRequest, String className, String primKey,
long[] roleIds)
throws PortalException {

System.out.println("----Hi from our DLFileEntryPermission Propagator .....");

}



}
Inviato il 25/03/22 21.24 in risposta a Jader Jed Francia.
Jader Jed Francia
Hi Carlos!
First of all, I suggest to use PortletKeys instead of strings for matching portlet name.
After that, I will check via gogo shell if the component is active (eg. using scr:info <full package name of your class>.

I noticed that your class is in a package named like the standard one: try to change the package using yours, because OSGi should take "the first" and could not be yours. If you want to mantain LR package, I suggest to use "service.ranking:Integer=100" in your Component annotation ( higher value correspond a higher priority in class loading).

Try to check this stuff and let me know if the situation will solve! ;)

Cheers, J.
Inviato il 25/03/22 21.40 in risposta a Carlos A..
Carlos A.
Hi Jared,

I see some clues now. I made the changes you mentioned and found that scr:info does not show anything about my custom class. Our ext bundle for customizing D&M is deployed ok but somehow our custom class is missing or unreachable. We are adding a new class to the bundle.
.
Perhaps we are wrong about ext bundles. Can you only override existing classes and not add new ones to one ext bundle?

TIA

Carlos
Inviato il 26/03/22 13.13 in risposta a Jader Jed Francia.
Jader Jed Francia
Hi Carlos!
Just one question: why are you trying to do an ext when, thanks by OSGi, you can load modules simply deploying bundles to LR?
In other words: your component will be loaded directly by the container, no matter what the package is nor if it override a portal's class.

Have you tryed deploy a bundle with your implementation, changing (obviously) the package in no-ext mode?

Let me know if this path solve the issue! ;)

Cheers, JF
Inviato il 28/03/22 11.36 in risposta a Carlos A..
Carlos A.
Hi Jader,

You are right there was no need to do an ext. We did just a custom (service) bundle and it worked directly! Not also it is easier, it seems the right way to do it. Also doing it in the ext seems that it would not work if it does not override something that already exists in the original bundle. In this case the service was not implemented in the original bundle.

I owe you a beer ( or two emoticon )!

Thanks a lot!!

Carlos
Inviato il 30/03/22 12.29 in risposta a Jader Jed Francia.