Кастомизация
18
Апр
29

Отображение и скрытие полей в зависимости от роли пользователя

Иногда бывает так, что с одной записью должны работать несколько пользователей. В то же время из-за политик безопасности каждый из них должен видеть ограниченное количество данных, необходимых для их работы. Но настройки безопасности ролей не позволяют настраивать права доступа по отдельности к каждому полю. Поэтому нам придется выполнить небольшую кастомизацию в виде javascript кода. Представляю Вашему вниманю два решения — одно поддерживаемое, другое нет. Выбирать Вам…

Не поддерживаемое

В MS CRM есть много строк Javascript-кода, которые уже входят в продукт и используются им для реализации своего стандартного функционала. Один из этих файлов расположен (по умолчанию) по адресу: / _controls/RemoteCommands/RemoteCommand.js. Этот файл включается в каждую страницу CRM. Одна из функций в этом файле – RemoteCommand(sObject, sCommand, sUrlBase), которую Вы можете использовать, чтобы соединиться с CRM webservices.

Через RemoteCommand, используя Javascript, Вы можете получить все роли системы. Роли, которые назначены текущему пользователю, отмечены: checked=true. Вот код, чтобы получить этот список:

var command = new RemoteCommand("UserManager", "GetUserRoles");
command.SetParameter("userIds", "<guid>" + userId + "</guid>");
var oResult = command.Execute();

Чтобы узнать айдишник пользователя в CRM 4.0, используем объект xmlhttp для отправки запроса (методом «Post»), который будет обработан синхронно. Через метод xmlhttp.responseXML получаем xml ответ, содержащий system user id и соответствующую business unit id, связанную с пользователем.

Получив user id, мы можем через веб-службу UserManager восстановить все роли, который назначены пользователю в CRM. Ну и наконец проверяем, есть ли у текущего пользователя нужная нам роль, и в зависимости от результата скрываем/показываем определенные поля.

Теперь, если мы обыграем эти функциональные возможности в пользовательских функциях, добавим несколько операторов try… catch(e) (служит для обработки ошибок) и добавим проверку (есть ли у пользователя определенная роль), то тогда наш код будет иметь следующий вид:

//Если у пользователя есть роль Системный администратор, то скрываем поле Основной телефон вместе
if(currentUserHasRole('Системный администратор')) {
	crmForm.all.telephone1.style.display='none';
}

function currentUserHasRole(roleName) {
	userId = getUserId();
	return userHasRole(userId, roleName); 
}

//Получаем айдишник пользователя
function getUserId() {
	try {
		var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
		xmlhttp.open("POST", "/mscrmservices/2007/crmservice.asmx", false);
		xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
		xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");
		var soapBody = "<soap:Body>"+
		"<Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"+
		"<Request xsi:type='WhoAmIRequest' />"+
		"</Execute></soap:Body>";
		var soapXml = "<soap:Envelope " +
		"xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' "+
		"xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "+
		"xmlns:xsd='http://www.w3.org/2001/XMLSchema'>";
		soapXml += GenerateAuthenticationHeader();
		soapXml += soapBody;
		soapXml += "</soap:Envelope>";
		xmlhttp.send(soapXml);
		xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
		xmlDoc.async=false;  
		xmlDoc.loadXML(xmlhttp.responseXML.xml);  
		var userid = xmlDoc.getElementsByTagName("UserId")[0].childNodes[0].nodeValue;
        return userid;
	}
	catch(e) {
		return null;
	}
}

//Проверяем, есть ли у пользователя нужная роль
function userHasRole(userId, roleName) {
	var hasRole = false;
	result = getUserRoles(userId);
    if (result != null) {
		var oXml = new ActiveXObject("Microsoft.XMLDOM");
		oXml.resolveExternals = false;
		oXml.async = false;
		oXml.loadXML(result);
		roleNode = oXml.selectSingleNode("/roles/role[name='" + roleName + "']");
		if (roleNode != null) {
			if (roleNode.selectSingleNode("roleid[@checked='true']") != null) {
				hasRole = true;
			}
		}
	}
	return hasRole;
}

//Запрашиваем все роли пользователя
function getUserRoles(userId) {
	try {
		var command = new RemoteCommand("UserManager", "GetUserRoles");
		command.SetParameter("userIds", "<guid>" + userId + "</guid>");
		var oResult = command.Execute();
		if (oResult.Success) {
			return oResult.ReturnValue;
		}
	}
	catch(e) {
		return null;
	}
}

Ну осталось только вставить его в событие OnLoad в свойствах формы. Тестируем:

  • Откройте CRM, перейдите Параметры — Настройка — Настроить объекты. Дважды щелкните по объекту Бизнес-партнер. В открывшейся форме на левой панели перейдите Формы и представления, далее двойной клик по Форма.


  • Откройте Свойства формы, выделите событие При загрузке — Изменить. Поставьте галку Событие сделано активным и вставьте вышеуказанный код. Сохраняйте, закрывайте и публикуйте.



  • Зайдите в CRM под любым пользователем у которого есть роль Системный администратор и откройте любую запись Бизнес-партнера или создайте новую. Вы увидите, что поле Основной телефон не отображено. Если зайдете в CRM под пользователем, у которого нет роли Системный администратор, то это поле будет видно!

Функция try… catch
Предназначена для перехвата ошибок (исключений). Если в блоке try каком-то операторе возникает ошибка, JScript игнорирует остальные операторы и переходит в блок catch, где находится блок обработки исключений.

Учтите: так как мы используем webservices MS CRM через другой регулярный webservice, то это неподдерживаемое решение!

А теперь поддерживаемый способ

Вот код, который прекрасно работает в CRM 4.0 и полностью поддерживаемый. Функция UserHasRole(«название_роли») возвращает истину, если у текущего пользователя есть указанная роль или ложь, если такой роли нет. Функция GetCurrentUserRoles(), в соответствии с названием, возвращает все роли пользователя, используя SOAP запрос.

//Если у пользователя есть роль Системный администратор, то скрываем поле Основной телефон вместе с отображаемой подписью
if(UserHasRole("Системный администратор")) {
	crmForm.all.telephone1.parentElement.parentElement.style.display='none';
}

//Проверяем, есть ли у пользователя запрашиваемая роль
function UserHasRole(roleName) {
    // получаем текущие роли пользователя
	var oXml = GetCurrentUserRoles();
	if(oXml != null) {
		var roles = oXml.selectNodes("//BusinessEntity/q1:name");
		if(roles != null) {
			for( i = 0; i < roles.length; i++) {
				if(roles[i].text == roleName) {
                    // возвращаем true если у пользователя есть эта роль
					return true;
				}
			}
		}
	}
    // в противном случаи возвращаем false
	return false;
}

//Выцепляем все роли пользователя
function GetCurrentUserRoles() {
	var xml = "" +
	"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
	"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
	GenerateAuthenticationHeader() +
	"<soap:Body>" +
	" <RetrieveMultiple xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
	" <query xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +
	" <q1:EntityName>role</q1:EntityName>" +
	" <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
	" <q1:Attributes>" +
	" <q1:Attribute>name</q1:Attribute>" +
	" </q1:Attributes>" +
	" </q1:ColumnSet>" +
	" <q1:Distinct>false</q1:Distinct>" +
	" <q1:LinkEntities>" +
	" <q1:LinkEntity>" +
	" <q1:LinkFromAttributeName>roleid</q1:LinkFromAttributeName>" +
	" <q1:LinkFromEntityName>role</q1:LinkFromEntityName>" +
	" <q1:LinkToEntityName>systemuserroles</q1:LinkToEntityName>" +
	" <q1:LinkToAttributeName>roleid</q1:LinkToAttributeName>" +
	" <q1:JoinOperator>Inner</q1:JoinOperator>" +
	" <q1:LinkEntities>" +
	" <q1:LinkEntity>" +
	" <q1:LinkFromAttributeName>systemuserid</q1:LinkFromAttributeName>" +
	" <q1:LinkFromEntityName>systemuserroles</q1:LinkFromEntityName>" +
	" <q1:LinkToEntityName>systemuser</q1:LinkToEntityName>" +
	" <q1:LinkToAttributeName>systemuserid</q1:LinkToAttributeName>" +
	" <q1:JoinOperator>Inner</q1:JoinOperator>" +
	" <q1:LinkCriteria>" +
	" <q1:FilterOperator>And</q1:FilterOperator>" +
	" <q1:Conditions>" +
	" <q1:Condition>" +
	" <q1:AttributeName>systemuserid</q1:AttributeName>" +
	" <q1:Operator>EqualUserId</q1:Operator>" +
	" </q1:Condition>" +
	" </q1:Conditions>" +
	" </q1:LinkCriteria>" +
	" </q1:LinkEntity>" +
	" </q1:LinkEntities>" +
	" </q1:LinkEntity>" +
	" </q1:LinkEntities>" +
	" </query>" +
	" </RetrieveMultiple>" +
	" </soap:Body>" +
	"</soap:Envelope>" +
	"";

	var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");

	xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
	xmlHttpRequest.setRequestHeader("SOAPAction"," http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
	xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
	xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
	xmlHttpRequest.send(xml);

	var resultXml = xmlHttpRequest.responseXML;
	return(resultXml);
}

Обратите внимание на одну вещь: в предыдущем примере мы скрыли только поле ввода Основного телефона, обратясь к нему напрямую по имени: crmForm.all.telephone1. Скрыв само поле, мы совсем не затронули надпись «Основной телефон», сопровождающее поле ввода. Обратится напрямую к нему по имени мы не можем, т.к. у него попросту нет имени. Чтобы скрыть весь этот маленький блок, нужно через объект, которое входит в этот блок и у которого есть имя, обратится к его родительскому контейнеру. В данном случаи это выглядит так: crmForm.all.telephone1.parentElement.parentElement. Ну а выглядеть на форме это будет так:

Комментарии (29)
  • AZubarev 18.04.2009

    Код отображается криво!!!

  • slivka_83 18.04.2009

    А можно поподробнее! В чем это выражается? У меня все нормально 🙂

  • Sergey 18.04.2009

    у меня при загрузке формы спрашивает пароль и ничего не происходит

  • slivka_83 18.04.2009

    1. А другие скрипты работают нормально?
    2. У Вас испоьзуется виндовая аутентификация?

  • Sergey 18.04.2009

    В этой статье ни один не работает. У меня виндовая доменная авторизация

  • slivka_83 18.04.2009

    Первый скрипт подправил 🙂 юзайте 🙂
    второй скрипт посмотрю попозже 🙂

  • slivka_83 18.04.2009

    Второй подправил 🙂

  • Sergey 18.04.2009

    Всё работает. Спасибо!

  • Sergey 18.04.2009

    Всё работает в Iexplorer. А вот в почтовике MS Outlook отказывается. Пишет нет доступа

  • scint 18.04.2009

    а как скрыть не поле, а раздел. В Вашем случае например раздел Адрес. Просто не знаю как к нему обратиться(

  • slivka_83 18.04.2009

    Вот так скрывается раздел:

    crmForm.all.telephone1.parentElement.parentElement.style.display='none';
    

    Только замените «telephone1» на какое-нибудь поле из раздела Адрес (или из любого другого нужного Вам раздела) 🙂

  • slivka_83 18.04.2009

    ах да… для скрытия раздела вместо имени поля надо писать «имя» + «_c». Т.е. например telephone1_с

  • lu 18.04.2009

    Я сделала все,согласно второму коду, но у меня почему-то осталась надпись «основной телефон», хотя само поле скрыто. В чем может быть проблема?

  • slivka_83 18.04.2009

    Для скрытия поля используется такая строка:

    crmForm.all.telephone1.style.display='none';
    

    Чтобы скрыть еще и надпись необходимо дополнительно выполнить такой код (сразу после первой):

    crmForm.all.telephone1_с.style.display='none';
    
  • Lu 18.04.2009

    Большое спасибо, работает!
    Но теперь как избежать смещения соседнего поля «веб-узел»?

  • slivka_83 18.04.2009

    Используйте вместо

    style.display=''; 
    style.display='none'; 
    

    следующее:

    style.visibility = "visible";
    style.visibility = "hidden"; 
    
  • axelcust 18.04.2009

    А можно ли как-нибудь выяснить, есть ли у пользователя возможность изменять данные в открытой сущности?

  • slivka_83 18.04.2009

    Ну у Вас есть преднастроенные Роли 🙂 Вы значете что могут делать эти Роли с Вашим объектом. Вы выясняете какие Роли есть у Пользователя и от этого действуете 🙂

  • Павел 18.04.2009

    А как получить ID текущего пользователя?

  • slivka_83 18.04.2009

    C помощью ‘WhoAmIRequest’ 🙂

  • Павел 18.04.2009

    Извините, но не могли бы вы более подробно описать процесс.
    Выяснить есть ли у пользователя Роль получается, но вот понять бы как узнать его ID. Как ни пытался, не выходит.

  • slivka_83 18.04.2009

    Поменяйте в первой строчке имя сервера на свое:

    var SERVER_URL = "http://win-n22hj23d1b1";
    
    var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    xmlhttp.open("POST", SERVER_URL + "/mscrmservices/2007/crmservice.asmx", false);
    xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");
    
    var soapBody = "<soap:Body>" +
    "<Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>" +
    "<Request xsi:type='WhoAmIRequest' />" +
    "</Execute></soap:Body>"; var soapXml =
    "<soap:Envelope " +
    "xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' " +
    "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
    "xmlns:xsd='http://www.w3.org/2001/XMLSchema'>";
    
    soapXml += GenerateAuthenticationHeader();
    soapXml += soapBody;
    soapXml += "</soap:Envelope>";
    
    xmlhttp.send(soapXml);
    xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
    xmlDoc.async = false;
    xmlDoc.loadXML(xmlhttp.responseXML.xml);
    
    var userid = xmlDoc.getElementsByTagName("UserId")[0].childNodes[0].nodeValue;
    var buid = xmlDoc.getElementsByTagName("BusinessUnitId")[0].childNodes[0].nodeValue;
    var orgid = xmlDoc.getElementsByTagName("OrganizationId")[0].childNodes[0].nodeValue;
    
    alert("UserId: " + userid + "\r\nBusinessUnitId: " + buid + "\r\nOrganizationId: " + orgid);
    
  • Сергей 18.04.2009

    Поддерживаемый способ работает и на CRM 2011.
    Для индикации добавил:

     if(UserHasRole("Системный администратор") == true)
    {
               alert('User has admin role');
    }
    else
    {
               alert('User does not have admin role');
    

    Роль Системный администратор определяется, а вот с таким скриптом

     if(UserHasRole("Системный администратор") == true)
    {
               alert('User has admin role');           
    }
    else
    {
               alert('User does not have admin role');
    }
    function UserHasRole(roleName)
     {
         var serverUrl = document.location.protocol + "//" + document.location.host + "/" + Xrm.Page.context.getOrgUniqueName();
    
        var oDataEndpointUrl = serverUrl + "/XRMServices/2011/OrganizationData.svc/";
         oDataEndpointUrl += "RoleSet?$top=1&amp;$filter=Name eq '" + roleName + "'";
    
        var service = GetRequestObject();
    
        if (service != null)
         {
             service.open("GET", oDataEndpointUrl, false);
             service.setRequestHeader("X-Requested-Width", "XMLHttpRequest");
             service.setRequestHeader("Accept", "application/json, text/javascript, */*");
             service.send(null);
    
            var requestResults = eval('(' + service.responseText + ')').d;
    
            if (requestResults != null &amp;&amp; requestResults.results.length == 1)
             {
                 var role = requestResults.results[0];
    
                var id = role.RoleId;
    
                var currentUserRoles = Xrm.Page.context.getUserRoles();
    
                for (var i = 0; i &lt; currentUserRoles.length; i++)
                 {
                     var userRole = currentUserRoles[i];
    
                    if (GuidsAreEqual(userRole, id))
                     {
                         return true;
                     }
                 }
             }
         }
    
        return false;
     }
    
    function GetRequestObject()
     {
         if (window.XMLHttpRequest)
         {
             return new window.XMLHttpRequest;
         }
         else
         {
             try
             {
                 return new ActiveXObject(&quot;MSXML2.XMLHTTP.3.0&quot;);
             }
             catch (ex)
             {
                 return null;
             }
         }
     }
    
    function GuidsAreEqual(guid1, guid2)
     {
         var isEqual = false;
    
        if (guid1 == null || guid2 == null)
         {
             isEqual = false;
         }
         else
         {
             isEqual = guid1.replace(/[{}]/g, &quot;&quot;).toLowerCase() == guid2.replace(/[{}]/g, &quot;&quot;).toLowerCase();
         }
    
        return isEqual;
     }
    

    Лично у меня не работает (выдает User does not have admin role), хотя скрипт популярный. Я заменил
    var serverUrl, так как у меня локальный адрес CRM сервера отличается от url.
    Может быть Вы скажете, что тут не так.

  • Сергей 18.04.2009

    Еще, json2.js и jquery.js добавлены.

  • slivka_83 18.04.2009

    А что по Вашему делает этот код (и подробно)?

  • Сергей 18.04.2009

    Скрипт проверяет роли безопасности пользователей в CRM 2011, подробнее http://blogs.infinite-x.net/2010/11/16/retreiving-user-roles-in-crm-2011/ или тут http://rajeevpentyala.wordpress.com/tag/odata-select-failed/
    Вот тут чел говорит, что скрипт у него работает http://social.microsoft.com/Forums/en/crm/thread/17453285-8649-4e19-aed1-049c07e3ac08
    Я заменил только
    var serverUrl = document.location.protocol + «//» + document.location.host + «/» + Xrm.Page.context.getOrgUniqueName();
    Но этот код работает с другими скриптами. У меня имя сервера и доменное имя для доступа к СРМ различаются, поэтому я испрользовал этот код, но может быть, еще что-то надо заменить?
    Сейчас использую UserHasRole + GetCurrentUserRoles из опубликованного здесь поддерживаемого способа, работает на ура. Использую его для скрытия вкладок и разделов вместе с Xrm.Page.ui.tabs.get

  • slivka_83 18.04.2009

    Вот это строчка возвращает роли с определенным названием. Она НЕ возвращает роли пользователя.

    oDataEndpointUrl = serverUrl + "/XRMServices/2011/OrganizationData.svc/";  
    oDataEndpointUrl += "RoleSet?$top=1&amp;$filter=Name eq '" + roleName + "'";  
    
  • Андрей 18.04.2009

    Все роли текущего пользователя в CRM 2011 можно получить в JS одной строчкой
    Xrm.Page.context.getUserRoles()

  • Annabella 18.04.2009

    Спасибо! Отличный код, подошёл как влитой!

*

code