Benutzerberechtigungen übertragen?

von Heiko Robert
Kategorien: alfresco
cover-image

Alfresco bietet leider keine API, um Berechtigungen von einem Benutzer auf einen anderen zu übertragen. Es gibt Strategien, diesen Fall zu vermeiden, aber was, wenn man es dennoch benötigt? In diesem Beitrag zeigen wir Ihnen, wie es trotzdem funktionieren kann und wie man diese Situation vermeidet.

Username und Benutzer-Berechtigungen

Es ist immer eine schlechte Idee, Benutzern direkt Berechtigungen für Ordner oder Dateien zu erteilen, es sei denn, Sie meinen wirklich die einzelne Person, der Sie Rechte geben (z.B. bei privaten Freigaben). Meist meint man jedoch eine Rolle wie beispielsweise "Mitarbeiter des Sekretariats" oder "Projektleiter für Project X". Auch wenn es auf den ersten Blick umständlich erscheint, ist es meist besser, man legt im AD oder in Alfresco eine entsprechende Gruppe an und vergibt die Rechte dieser Gruppe. Dies hat den Vorteil, dass man diese Berechtigung leicht übertragen kann, indem man einfach die Mitglieder dieser Gruppe ändert. Hat man jedoch bereits zahlreiche Berechtigungen einzelnen Usern in Alfresco auf Ordner oder gar Dateien erteilt, kommt das blaue Wunder, sollte man einmal einen Personalwechsel durchführen wollen oder sollte sich einmal die Rolle eines Users im Unternehmen ändern.

Das Gleiche gilt, wenn Sie Benutzernamen in Alfresco ändern müssen, z. B. weil Sie eine SSO-Lösung integrieren wollen, die die Konsolidierung verschiedener Benutzernamen erfordert. Da Benutzernamen in Alfresco nicht geändert werden können, werden stattdessen neue Benutzer angelegt, die dann aber keine Berechtigungen haben. Dieser Sonderfall kann durch die Verwendung unseres Alfresco-Moduls [Smart Logins] (/was-wir-tun/produkte/alfresco/module/smartlogins) vermieden werden, das den Benutzernamen vom Login trennen kann, damit sich die Benutzer über alle Zugriffswege mit einem änderbaren Login in Alfresco anmelden können, indem ein dynamischer Lookup gegen ein konfigurierbares LDAP-Attribut durchgeführt wird.

Was nun, wenn man trotzdem in die Situation gerät, Berechtigungen auf einen anderen User zu übertragen? Nachfolgend ein Proo-of-Concept mit JavaScript:

Übertragen direkter Berechtigungen

Leider bietet Alfresco keine API zur Abfrage aller direkten Berechtigungen eines Benutzers (ACLs) im System. Ein Workaround könnte darin bestehen, über alle Knoten in Alfresco per Routine zu iterieren, um zu prüfen, welche Berechtigungen gesetzt sind. Dieser Ansatz wird hier jedoch verworfen, da er in einem realen System mit potenziell vielen Millionen Knoten viel zu lange dauern würde und außerdem nicht ohne Batchverarbeitung implementiert werden kann.

Viel effektiver ist es, die zugrundeliegende Datenbank direkt abzufragen, um alle ACL-Einträge zu ermitteln, in denen Berechtigungen eines bestimmten Users gesetzt sind.

Grundsätzlich sieht für den User testuser das SQL wie folgt aus:

select ace.id as ace_id, n.id as node_id, n.acl_id, a.authority, concat(s.protocol,'://', s.identifier,'/', n.uuid) as node_ref, nqn.local_name as node_type, p.name
from alf_access_control_entry ace
join alf_authority a on (ace.authority_id = a.id and a.authority = 'testuser')
join alf_acl_member acm on (ace.id = acm.ace_id and acm.pos=0)
join alf_node n on (acm.acl_id=n.acl_id)
join alf_permission p on (ace.permission_id=p.id and p.type_qname_id in (select id from alf_qname where local_name in ('cmobject','site')))
join alf_store s on (n.store_id=s.id)
join alf_qname nqn on (n.type_qname_id=nqn.id)

Anmerkung: Wir sind hier nur an Berechtigungen vom Typ cmobject und site ineressiert. Aus Performance-Gründen wird hier darauf verzichtet, zusätzlich auf den Namespace per zusätzlichen join einzuschränken.

Die Abfrage gibt eine Liste aller Berechtigungen für den Benutzer "testuser" zusammen mit dem betroffenen Knoten zurück. Mit dieser Liste ist es einfach, per Skript über die Knoten zu iterieren, um einem anderen Benutzer die gleichen Berechtigungen zu erteilen.

Wie kommt man nun aber aus JavaScript an diese Liste, wenn man über die JavaScript-API von Alfresco gar keinen direkten Zugriff auf die Datenbank hat? Wir bedienen uns hierfür einer Erweiterung, die von Jens Goldhammer veröffentlicht wurde: alfresco-jscript-extensions. Die Erweiterung enthält u. a. ein root object database, mit dem man auch aus JavaScript heraus SQL ausführen kann.

Eine entpsrechende JavaScript function kann dann so aussehen:

function copyAuthorityPermissions(fromAuthority, toAuthority){
    var aceSqlQuery = `select ace.id as ace_id, a.authority, n.id as node_id, concat(s.protocol,'://', s.identifier,'/', n.uuid) as node_ref, nqn.local_name as node_type, p.name as permission
    from alf_access_control_entry ace
    join alf_authority a on (ace.authority_id = a.id and a.authority in (?))
    join alf_acl_member acm on (ace.id = acm.ace_id and acm.pos=0)
    join alf_node n on (acm.acl_id=n.acl_id)
    join alf_permission p on (ace.permission_id=p.id and p.type_qname_id in (select id from alf_qname where local_name in ('cmobject','site')))
    join alf_store s on (n.store_id=s.id)
    join alf_qname nqn on (n.type_qname_id=nqn.id)`

    var aceEntries = database.query("dataSource", aceSqlQuery, fromAuthority);
    // permission, node_ref, node_type , acl_id, authority, node_id 

    for each (var aceEntry in aceEntries){
        var node = utils.getNodeFromString(aceEntry.node_ref );
        if (node.exists()){
            logger.log("authority: " + aceEntry.authority + " permission: " + aceEntry.permission + " path: " + node.displayPath + '/' + node.name);
            logger.log("nodeRef: " + aceEntry.node_ref);
            node.setPermission(aceEntry.permission, toAuthority)
        }
    }
}

Übertragen des bisherigen Userhome

Angenomen, der neue User soll das bisherige Userhome erben, kann man mit der folgenden function im User-Profil den Ordner eines anderen Users setzen und den Besitz erlangen, sodass es zum eigenen Userhome wird:

function switchUserHome(oldUserName,newUserName){
    var oldUserNode = people.getPerson(oldUserName);
    var newUserNode = people.getPerson(newUserName);

    if (oldUserNode){
        if (newUserNode){
            var newUserHomeFolder = newUserNode.properties["cm:homeFolder"];
            var oldUserHomeFolder = oldUserNode.properties["cm:homeFolder"];
            if (newUserHomeFolder.exists() && newUserHomeFolder.name != oldUserHomeFolder.name){
                if (newUserHomeFolder.name == newUserName){
                    newUserHomeFolder.name = newUserHomeFolder.name + '-old'
                    newUserHomeFolder.save();
                }
                newUserNode.properties["cm:homeFolder"] = oldUserHomeFolder;
                newUserNode.save();
                newUserHomeFolder.remove();
                if (oldUserHomeFolder.name != newUserName){
                    logger.log("renaming user folder " + oldUserHomeFolder.name + " to " + newUserName);
                    oldUserHomeFolder.name = newUserName;
                    oldUserHomeFolder.properties["cm:owner"] = newUserName;
                    oldUserHomeFolder.save();
                }
            } else {
                logger.error("user home for user " + newUserName + " does not exist or is already moved!");
            }
        } else {
            logger.error("user "+newUserName+" not found!")
        }
    } else {
        logger.error("user "+oldUserName+" not found!")
    }
}

Hinweis: In diesem Fall geht switchUserHome davon aus, dass das Userhome von newUser leer ist und gelöscht werden soll. Um beide Homes zusammenzuführen, müsste man noch die Kinder aus dem Userhome von newUser mit dem von oldUser synchronisieren.

Kopieren von Gruppen-Mitgliedschaften

Da User auch Berechtigungen durch Gruppen-Mitgliedschaften (wie beispielsweise in Sites) haben, kann man über die folgende function die Gruppen-Mitgliedschaftn eines Users kopieren:

function copyUserGroupMemberships(fromUserName,toUserName){

    var fromUser = people.getPerson(fromUserName);
    var toUser = people.getPerson(toUserName);

    if (fromUser && toUser){
        var memberships = fromUser.parentAssociations["cm:member"];

        for each (var groupAssoc in memberships){
            try {
                people.addAuthority(groupAssoc,toUser);
            } catch(ex ) {
                // unfortunatele the people.addAuthority method does not check if the authority is not already a member
                // so we need to catch the exception in case the user is already a member
                logger.error("ABORT: Exception occurred: "+ex);
            }
        }    
    }
}

All in One

Das sollte es jetzt gewesen sein. Um die Berechtigungsänderung nun leicht mit einem einfachen Aufruf durchführen zu können, kann man die obigen Funktionen nun noch in eine eigene Funktion packen und per Einzeiler ausführen:

function switchUser(oldUserName,newUserName){
    switchUserHome(oldUserName,newUserName);
    copyUserGroupMemberships(oldUserName,newUserName);
    copyAuthorityPermissions(oldUserName,newUserName);
}

switchUser("old.user","new.user");

/* 
people.deletePerson("old.user")
*/

Wenn Sie Feedback haben: Lassen Sie es uns wissen!

Picture credits title picture: aitoff (Andrew Martin) licensed under the pixabay license (free for commercial use)