Administration des groupes D2

De EjnTricks

Viewer icon.png Objectif

L'administration des groupes, et des utilisateurs, peut s'effectuer depuis un client D2. Cependant il existe une configuration propre à D2 pour restreindre l'accès a ces fonctionnalités. Même si le compte utilisé possède toutes les permissions standards Documentum, superuser ou dans le group d'administration des groupes, la mise à jour peut provoquer une erreur indiquant que le compte n'est pas habilité à effectuer les modifications à travers une fenêtre popup dont le message est Insufficient rights to perform this action..

Alors si vous vous demandez pourquoi vous ne pouvez pas modifier un groupe sous D2 alors que cela fonctionne sous Documentum Administrator, cet article vous expliquera la raison, pour une version 4.1 de D2.


Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote


Configuration-icon.png Configuration

Dans l'interface D2-Config, il existe un menu d'accès à des options sous Tools → Options....


Ceci permet d'ouvrir la fenêtre de configuration des options.


Dans le cadre de cet article, les deux options à prendre en compte sont :

  • Access group for users administration node
  • Access group for groups administration node

Même si un utilisateur possède les droits Documentum suffisants, il est nécessaire qu'il appartienne au groupe mentionné dans ces paramètres afin de gérer les groupes ou les utilisateurs. Pour cet exemple, il faut donc que l'utilisateur connecté soit membre du groupe admingroup.

Dans la grande majorité des implémentations, cette problématique ne se pose pas car la création des comptes s'effectuent avec le compte docbase owner, membre du groupe admingroup, groupe utilisé par défaut dans ces deux paramètres.


Study icon.png Analyse

Java format icon.png Code Java

L'analyse du code source décompilés avec l'outil jd-gui permet de confirmer ce fonctionnement. Les opérations des sauvegardes sont des actions au sens D2.

Dans le cas de la manipulation des groupes, la boîte de dialogue est gérée par une instance de la classe com.emc.d2fs.dctm.dialogs.property.DmGroupDialog dans laquelle se trouve la méthode validDialog.

  public XmlNode validDialog(D2fsContext context) throws Exception {
    ParameterParser parameterParser = context.getParameterParser();

    String sMode = parameterParser.getStringParameter("mode");
    if ((sMode != null) && (sMode.equals("create"))) {
      return new SaveGroup().executeAction(context);
    }
    return new D2PropertyService().saveProperties(context);
  }

C'est du code décompillé, donc nous passerons sur le mode de comparaison de la chaîne de caractères sMode. Pour rappel, une comparaison par rapport à une constante s'effectue en utilisant celle-ci comme premier argument. Cela permet de s'affranchir du test de nullité. Le code aurait pu être celui-ci.

  public XmlNode validDialog(D2fsContext context) throws Exception {
    ParameterParser parameterParser = context.getParameterParser();

    String sMode = parameterParser.getStringParameter("mode");
    if ("create".equals(sMode)) {
      return new SaveGroup().executeAction(context);
    }
    return new D2PropertyService().saveProperties(context);
  }

En tout état de cause, cela exécute la méthode executeAction sur une isntance de com.emc.d2fs.dctm.actions.property.SaveGroup.

  public XmlNode executeAction(D2fsContext context)
    throws Exception
  {
    XmlNode result = XmlFactory.getRootSuccess();

    IDfSession session = context.getSession();
    ParameterParser parameterParser = context.getParameterParser();

    ID2Options d2Options = D2Options.getInstance(session);

    if ((d2Options == null) || (!d2Options.isInNodeGroupSecurityGroup())) {
      D2fsExceptionManager.throwD2FailureException(context, "insufficientRights", null);
    }
    String sMode = parameterParser.getStringParameter("mode", "edit");

    if (sMode.equalsIgnoreCase("edit"))
    {
      boolean signoffRequired = false;
      ID2PropertyConfig propertyConfig = null;
      XmlNode xmlConfigDialog = null;
      if (context.getFirstObject() != null)
      {
        propertyConfig = D2PropertyConfig.getInstanceForObject(context.getFirstObject(), "X3");

        if (propertyConfig != null) {
          xmlConfigDialog = propertyConfig.getXmlContent();
        }
      }
      if (xmlConfigDialog != null)
      {
        signoffRequired = new DialogProcessor(context, xmlConfigDialog).isSignoffRequired("edit");
        if (signoffRequired) {
          new SignOff().process(context);
        }
      }
    }
    String sObjectType = parameterParser.getStringParameter("r_object_type", "");

    IDfGroup group = null;
    IDfSession adminSession = context.getAdminSession();
    try
    {
      if (sMode.equalsIgnoreCase("create")) {
        group = (IDfGroup)adminSession.newObject(sObjectType);
      }
      else {
        group = (IDfGroup)adminSession.getObject(context.getFirstId());
        DfAuditDump.add(group);
      }

    }
    catch (DfException localDfException)
    {
      throw new D2SilentException();
    }

    if (sMode.equals("create"))
    {
      group.setGroupName(parameterParser.getStringParameter("group_name"));
    }
    List lUsersNames = parameterParser.getListParameter("users_names");
    List lGroupsNames = parameterParser.getListParameter("groups_names");

    group.removeAllUsers();
    group.removeAllGroups();

    for (int i = 0; i < lUsersNames.size(); i++) {
      group.addUser((String)lUsersNames.get(i));
    }
    for (int i = 0; i < lGroupsNames.size(); i++) {
      group.addGroup((String)lGroupsNames.get(i));
    }
    group.setDescription(parameterParser.getStringParameter("description", ""));

    group.save();

    String sCurrentGroupAdmin = group.getGroupAdmin();

    if (sCurrentGroupAdmin == null) {
      sCurrentGroupAdmin = "";
    }
    String sGroupAdmin = parameterParser.getStringParameter("group_admin", "");

    if (!sGroupAdmin.equals(sCurrentGroupAdmin))
    {
      if (sGroupAdmin.length() == 0)
      {
        group.setString("group_admin", sGroupAdmin);
      }
      else group.setGroupAdmin(sGroupAdmin);
    }

    group.save();

    if (sMode.equals("create"))
      D2AuditConfig.apply(group, "d2_create", session.getLoginUserName(), null, null, null, null, 
        null);
    else {
      D2AuditConfig.apply(group, "d2_properties_save", session.getLoginUserName(), null, null, null, 
        null, null);
    }
    result.setAttribute("new_id", group.getObjectId().toString());

    group.fetch(null);

    return result;
  }


Il apparaît clairement l'utilisation d'une instance de com.emc.d2.api.config.ID2Options et de sa fonction isInNodeGroupSecurityGroup. Il n'existe qu'une seule implémentation de cette interface com.emc.d2.api.config.D2Options, ce qui facilite les recherches. Le code de la fonction est assez simple.

  public boolean isInNodeGroupSecurityGroup()
    throws DfException
  {
    String securityGroupName = getNodeGroupSecurityGroupName();
    boolean result;
    boolean result;
    if (securityGroupName.length() != 0)
      result = DfUserUtil.isUserInGroup(getSession(), securityGroupName);
    else {
      result = true;
    }
    return result;
  }

Cette fonction met en évidence la vérification de l'appartenance de l'utilisateur connecté à un groupe, dont le nom est retourné par la fonction getNodeGroupSecurityGroupName.

  public String getNodeGroupSecurityGroupName()
    throws DfException
  {
    String result = "";

    if (hasAttr("node_group_security_group")) {
      result = getString("node_group_security_group");
    }
    return result;
  }

En conclusion, si l'utilisateur ne fait pas partie du groupe mentionné dans l'option node_group_security_group de l'objet "option", il ne pourra pas modifier le groupe, quelque soit les permissions standards Documentum.

Dans le cadre des utilisateurs, l'analyse est exactement la même en partant de la classe com.emc.d2fs.dctm.dialogs.property.DmUserDialog, contenant la méthode validDialog.

  public XmlNode validDialog(D2fsContext context) throws Exception
  {
    ParameterParser parameterParser = context.getParameterParser();

    String sMode = parameterParser.getStringParameter("mode");
    if ((sMode != null) && (sMode.equals("create"))) {
      return new SaveUser().executeAction(context);
    }
    return new D2PropertyService().saveProperties(context);
  }

La classe com.emc.d2fs.dctm.actions.property.SaveUser fonctionne exactement de la même façon que SaveGroup.

  public XmlNode executeAction(D2fsContext context)
    throws Exception
  {
    XmlNode result = XmlFactory.getRootSuccess();

    IDfSession session = context.getSession();
    ParameterParser parameterParser = context.getParameterParser();

    ID2Options d2Options = D2Options.getInstance(session);

    if ((d2Options == null) || (!d2Options.isInNodeUserSecurityGroup())) {
      D2fsExceptionManager.throwD2FailureException(context, "insufficientRights", null);
    }
    String sMode = parameterParser.getStringParameter("mode", "edit");

    if (sMode.equalsIgnoreCase("edit"))
    {
      boolean signoffRequired = false;
      ID2PropertyConfig propertyConfig = null;
      XmlNode xmlConfigDialog = null;
      if (context.getFirstObject() != null)
      {
        propertyConfig = D2PropertyConfig.getInstanceForObject(context.getFirstObject(), "X3");

        if (propertyConfig != null) {
          xmlConfigDialog = propertyConfig.getXmlContent();
        }
      }
      if (xmlConfigDialog != null)
      {
        signoffRequired = new DialogProcessor(context, xmlConfigDialog).isSignoffRequired("edit");
        if (signoffRequired) {
          new SignOff().process(context);
        }
      }
    }
    String sObjectType = parameterParser.getStringParameter("r_object_type", "");

    IDfUser currentUser = session.getUser(null);
    boolean isSuperUser = (currentUser.isSuperUser()) || (currentUser.isSystemAdmin());

    IDfUser user = null;
    IDfSession adminSession = context.getAdminSession();
    try
    {
      if (sMode.equalsIgnoreCase("create")) {
        user = (IDfUser)adminSession.newObject(sObjectType);
      }
      else {
        user = (IDfUser)adminSession.getObject(context.getFirstId());
        DfAuditDump.add(user);
      }

    }
    catch (DfException localDfException)
    {
      throw new D2SilentException();
    }

    List listAttrName = parameterParser.getListParameter("list");

    for (String attrName : listAttrName)
    {
      if (!user.hasAttr(attrName))
        continue;
      if (attrName.startsWith("r_"))
        continue;
      if (DfTypeUtil.isAttrRepeating(user, attrName))
      {
        user.removeAll(attrName);

        List listValues = parameterParser.getListParameter(attrName);

        int dataType = user.getAttrDataType(attrName);

        for (String strValue : listValues)
        {
          switch (dataType)
          {
          case 4:
            IDfTime time = DfUtilEx.getDfTime(strValue, context.getSessionLocale());
            user.appendTime(attrName, time);

            break;
          case 0:
            boolean bool = Boolean.valueOf(strValue).booleanValue();
            user.appendBoolean(attrName, bool);

            break;
          case 1:
          case 2:
          case 3:
          default:
            IDfValue value = new DfValue(strValue);
            user.appendValue(attrName, value);
          }

        }

      }
      else
      {
        String strValue = parameterParser.getStringParameter(attrName, "");

        if ((attrName.equals("user_name")) && 
          (sMode.equals("create"))) {
          user.setUserName(parameterParser.getStringParameter("user_name"));
        } else if (attrName.equals("user_password"))
        {
          String password = parameterParser.getDecryptParameter("user_password", 
            "");

          if (!password.equals("****************"))
            user.setString("user_password", password);
        }
        else if (attrName.equals("default_folder")) {
          user.setDefaultFolder(
            parameterParser.getStringParameter(
            "default_folder", 
            ""), true);
        } else if (attrName.equals("user_os_name"))
        {
          user.setUserOSName(
            parameterParser.getStringParameter("user_os_name", ""), 
            parameterParser.getStringParameter("user_os_domain", 
            ""));
          user.setString("user_login_name", 
            parameterParser.getStringParameter("user_os_name"));
        }
        else if (attrName.equals("user_privileges"))
        {
          int privilege = parameterParser.getIntParameter("user_privileges", 0);

          if (isSuperUser)
            user.setUserPrivileges(privilege);
          else if (privilege > 0)
            D2fsExceptionManager.throwD2FailureException(context, "insufficientRights", null);
        }
        else if (attrName.equals("user_xprivileges"))
        {
          int xprivilege = parameterParser.getIntParameter("user_xprivileges", 0);

          if ((isSuperUser) || (xprivilege == 0) || (xprivilege == 32))
            user.setUserXPrivileges(xprivilege);
          else
            D2fsExceptionManager.throwD2FailureException(context, "insufficientRights", null);
        }
        else
        {
          switch (user.getAttrDataType(attrName))
          {
          case 4:
            IDfTime time = DfUtilEx.getDfTime(strValue, context.getSessionLocale());
            user.setTime(attrName, time);

            break;
          case 0:
            boolean bool = Boolean.valueOf(strValue).booleanValue();
            user.setBoolean(attrName, bool);

            break;
          case 1:
          case 2:
          case 3:
          default:
            Object value = new DfValue(strValue);
            user.setValue(attrName, (IDfValue)value);
          }

        }

      }

    }

    user.save();

    result.setAttribute("new_id", user.getObjectId().toString());
    user.fetch(null);

    if (listAttrName.contains("group_membership"))
    {
      List selectedGroups = parameterParser.getListParameter("group_membership");

      String groupName = parameterParser.getStringParameter("user_group_name", null);
      if ((!StringUtil.isNullOrEmpty(groupName)) && (!selectedGroups.contains(groupName))) {
        selectedGroups.add(groupName);
      }
      List oldSelectedGroup = DfUserUtil.getGroupNamesMemberOf(user, 
        "not group_name like 'dm%' and not group_name like 'd2%' and not group_name like 'grp\\_wf\\_%' escape '\\'");

      List groupToRemove = ListUtils.subtract(oldSelectedGroup, selectedGroups);

      for (int i = 0; i < groupToRemove.size(); i++)
      {
        IDfGroup group = adminSession.getGroup((String)groupToRemove.get(i));

        String groupAdmin = group.getGroupAdmin();

        if ((groupAdmin.length() == 0) || (DfUserUtil.isUserInGroup(session, groupAdmin)))
        {
          group.removeUser(user.getUserName());
          group.save();
        }
        else {
          D2fsExceptionManager.throwD2FailureException(context, "insufficientGroupPermit", new String[] { group.getGroupName() });
        }
      }
      List groupToAdd = ListUtils.subtract(selectedGroups, oldSelectedGroup);

      for (int i = 0; i < groupToAdd.size(); i++)
      {
        IDfGroup group = adminSession.getGroup((String)groupToAdd.get(i));

        String groupAdmin = group.getGroupAdmin();

        if ((groupAdmin.length() == 0) || (DfUserUtil.isUserInGroup(session, groupAdmin)))
        {
          group.addUser(user.getUserName());
          group.save();
        }
        else {
          D2fsExceptionManager.throwD2FailureException(context, "insufficientGroupPermit", new String[] { group.getGroupName() });
        }
      }
    }
    if (sMode.equals("create"))
      D2AuditConfig.apply(user, "d2_create", session.getLoginUserName(), null, null, null, null, 
        null);
    else {
      D2AuditConfig.apply(user, "d2_properties_save", session.getLoginUserName(), null, null, null, 
        null, null);
    }
    return (XmlNode)result;
  }

Les droits d'accès sont fournis par la fonction isInNodeUserSecurityGroup de l'intance ID2Options, implémentation com.emc.d2.api.config.D2Options.

  public boolean isInNodeUserSecurityGroup()
    throws DfException
  {
    String securityGroupName = getNodeUserSecurityGroupName();
    boolean result;
    boolean result;
    if (securityGroupName.length() != 0)
      result = DfUserUtil.isUserInGroup(getSession(), securityGroupName);
    else {
      result = true;
    }
    return result;
  }

Cette fonction met en évidence la vérification de l'appartenance de l'utilisateur connecté à un groupe, dont le nom est retourné par la fonction getNodeGroupSecurityGroupName.

  public String getNodeUserSecurityGroupName()
    throws DfException
  {
    String result = "";

    if (hasAttr("node_user_security_group")) {
      result = getString("node_user_security_group");
    }
    return result;
  }

A noter, une dernière remarque sur le code source décompilé. Il semblerait qu'il y ait de la duplication pourtant facilement factorisable. La seule différence entre getNodeGroupSecurityGroupName et getNodeUserSecurityGroupName concerne le nom de la propriété. Pareil pour les fonctions isInNodeGroupSecurityGroup et isInNodeUserSecurityGroup, la différence concerne uniquement le nom du groupe vérifié.

Icon-database-process.png Analyse objets en docbase

Jusqu'ici l'analyse a été réalisée uniquement sur le code Java. Mais qu'en est il des objets dans la docbase. L'étude de la fonction getInstance dans la classe D2Options va donner la solution.

  public static ID2Options getInstance(IDfSession session)
    throws DfException
  {
    ID2Options result = null;

    if (DfTypeUtil.isInstalled(session, "d2_options"))
    {
      IDfId objectId = session.getIdByQualification("d2_options");

      if (objectId.isNull())
      {
        result = (ID2Options)session.newObject("d2_options");

        IDfPersistentObject object = (IDfPersistentObject)result;

        Locale locale = DfSessionUtil.getLocale(session);
        ResourceBundle resource = ResourceBundle.getBundle(D2Options.class.getName(), locale, D2Options.class.getClassLoader());

        Enumeration enumeration = resource.getKeys();

        while (enumeration.hasMoreElements())
        {
          String attrName = (String)enumeration.nextElement();
          String value = resource.getString(attrName);

          if (object.hasAttr(attrName))
          {
            if (DfTypeUtil.isAttrRepeating(object, attrName))
            {
              if (value.length() != 0)
              {
                List listValues = StringUtil.split(value, ',');
                Iterator iterator = listValues.iterator();

                while (iterator.hasNext())
                {
                  String tmpValue = (String)iterator.next();

                  object.appendValue(attrName, new DfValue(tmpValue));
                }
              }
            }
            else {
              object.setValue(attrName, new DfValue(value));
            }
          }
        }
        object.save();
      }
      else {
        result = (ID2Options)session.getObject(objectId);
      }
    }
    return result;
  }

En conclusion, si le type d2_options est installé, il est recherché une instance de celui-ci dans la docbase pour être retournée. Il ne peut donc en exister qu'un seul objet pour que le fonctionnement soit assuré.

Il est aisé de dumper l'objet depuis la docbase donnant un résultat similaire à celui-ci.

API> retrieve,c,d2_options
...
0000000380000af1
API> dump,c,l
...
USER ATTRIBUTES

  config_security_group           : admingroup
  client_security_group           :
  node_admin_security_group       : admingroup
  forbidden_copy                []: <none>
  client_urls                   []: <none>
  enable_compare                  : F
  attribute_list_display_mode     : 0
  node_user_security_group        : admingroup
  node_group_security_group       : admingroup
  node_group_user_parent          : node_admin
  node_group_display_all          : T
  dfc_validator                   : T
  in_create_config              []: <none>

SYSTEM ATTRIBUTES

  r_object_id                     : 0000000380000af1

APPLICATION ATTRIBUTES


INTERNAL ATTRIBUTES

  i_is_replica                    : F
  i_vstamp                        : 2

Les attributs récupérés dans les fonctions getNodeGroupSecurityGroupName et getNodeUserSecurityGroupName se retrouvent donc bien sur l'objet de type d2_options.

Examples-icon.png Annexe

Revenons sur la fonction getInstance, une instance de d2_options est créée si aucune n'est trouvée. Mais comment sont positionnées les valeurs par défaut ? Encore une fois, la réponse se trouve dans le code. En revenant au contenu de la méthode.

  public static ID2Options getInstance(IDfSession session)
    throws DfException
  {
    ID2Options result = null;

    if (DfTypeUtil.isInstalled(session, "d2_options"))
    {
      IDfId objectId = session.getIdByQualification("d2_options");

      if (objectId.isNull())
      {
        result = (ID2Options)session.newObject("d2_options");

        IDfPersistentObject object = (IDfPersistentObject)result;

        Locale locale = DfSessionUtil.getLocale(session);
        ResourceBundle resource = ResourceBundle.getBundle(D2Options.class.getName(), locale, D2Options.class.getClassLoader());

        Enumeration enumeration = resource.getKeys();

        while (enumeration.hasMoreElements())
        {
          String attrName = (String)enumeration.nextElement();
          String value = resource.getString(attrName);

          if (object.hasAttr(attrName))
          {
            if (DfTypeUtil.isAttrRepeating(object, attrName))
            {
              if (value.length() != 0)
              {
                List listValues = StringUtil.split(value, ',');
                Iterator iterator = listValues.iterator();

                while (iterator.hasNext())
                {
                  String tmpValue = (String)iterator.next();

                  object.appendValue(attrName, new DfValue(tmpValue));
                }
              }
            }
            else {
              object.setValue(attrName, new DfValue(value));
            }
          }
        }
        object.save();
      }
      else {
        result = (ID2Options)session.getObject(objectId);
      }
    }
    return result;
  }

Le fichier de propriétés, D2Options.properties, est donc chargé. Celui-ci se trouve dans le jar D2-API-4.1.0.jar dans le package com.emc.d2.api.config, spécifié lors du chargement de la ressource. Son contenu permet de spécifier quatre valeurs de l'option.

config_security_group=admingroup
node_admin_security_group=admingroup
node_user_security_group=admingroup
node_group_security_group=admingroup