/// <summary> /// This method is used to update the sharing permissions of a record granted to users as Read-Write, Read-only, or grant full access. /// </summary> /// <param name="moduleAPIName">The API Name of the module to update share permissions.</param> /// <param name="recordId">The ID of the record to be obtained.</param> public static void UpdateSharePermissions(string moduleAPIName, long recordId) { //example //string moduleAPIName = "Leads"; //long recordId = 34770615177002; //Get instance of ShareRecordsOperations Class that takes recordId and moduleAPIName as parameter ShareRecordsOperations shareRecordsOperations = new ShareRecordsOperations(recordId, moduleAPIName); //Get instance of BodyWrapper Class that will contain the request body BodyWrapper request = new BodyWrapper(); //List of ShareRecord instances List <ShareRecord> shareList = new List <ShareRecord>(); //Get instance of ShareRecord Class ShareRecord share1 = new ShareRecord(); share1.ShareRelatedRecords = true; share1.Permission = "full_access"; User user = new User(); user.Id = 34770615791024; share1.User = user; shareList.Add(share1); request.Share = shareList; //Call UpdateSharePermissions method that takes BodyWrapper instance as parameter APIResponse <ActionHandler> response = shareRecordsOperations.UpdateSharePermissions(request); if (response != null) { //Get the status code from response Console.WriteLine("Status Code: " + response.StatusCode); //Check if expected response is received if (response.IsExpected) { //Get object from response ActionHandler actionHandler = response.Object; if (actionHandler is ActionWrapper) { //Get the received ActionWrapper instance ActionWrapper actionWrapper = (ActionWrapper)actionHandler; //Get the list of obtained ActionResponse instances List <ActionResponse> actionResponses = actionWrapper.Share; foreach (ActionResponse actionResponse in actionResponses) { //Check if the request is successful if (actionResponse is SuccessResponse) { //Get the received SuccessResponse instance SuccessResponse successResponse = (SuccessResponse)actionResponse; //Get the Status Console.WriteLine("Status: " + successResponse.Status.Value); //Get the Code Console.WriteLine("Code: " + successResponse.Code.Value); Console.WriteLine("Details: "); //Get the details map foreach (KeyValuePair <string, object> entry in successResponse.Details) { //Get each value in the map Console.WriteLine(entry.Key + ": " + JsonConvert.SerializeObject(entry.Value)); } //Get the Message Console.WriteLine("Message: " + successResponse.Message.Value); } //Check if the request returned an exception else if (actionResponse is APIException) { //Get the received APIException instance APIException exception = (APIException)actionResponse; //Get the Status Console.WriteLine("Status: " + exception.Status.Value); //Get the Code Console.WriteLine("Code: " + exception.Code.Value); Console.WriteLine("Details: "); //Get the details map foreach (KeyValuePair <string, object> entry in exception.Details) { //Get each value in the map Console.WriteLine(entry.Key + ": " + JsonConvert.SerializeObject(entry.Value)); } //Get the Message Console.WriteLine("Message: " + exception.Message.Value); } } } //Check if the request returned an exception else if (actionHandler is APIException) { //Get the received APIException instance APIException exception = (APIException)actionHandler; //Get the Status Console.WriteLine("Status: " + exception.Status.Value); //Get the Code Console.WriteLine("Code: " + exception.Code.Value); Console.WriteLine("Details: "); //Get the details map foreach (KeyValuePair <string, object> entry in exception.Details) { //Get each value in the map Console.WriteLine(entry.Key + ": " + JsonConvert.SerializeObject(entry.Value)); } //Get the Message Console.WriteLine("Message: " + exception.Message.Value); } } else { //If response is not as expected //Get model object from response Model responseObject = response.Model; //Get the response object's class Type type = responseObject.GetType(); //Get all declared fields of the response class Console.WriteLine("Type is: {0}", type.Name); PropertyInfo[] props = type.GetProperties(); Console.WriteLine("Properties (N = {0}):", props.Length); foreach (var prop in props) { if (prop.GetIndexParameters().Length == 0) { Console.WriteLine("{0} ({1}) : {2}", prop.Name, prop.PropertyType.Name, prop.GetValue(responseObject)); } else { Console.WriteLine("{0} ({1}) : <Indexed>", prop.Name, prop.PropertyType.Name); } } } } }
private void ParseError() { noMatchPanel.Visible = true; recordPermissionsPanel.Visible = false; errorPanel.Visible = false; retryPanel.Visible = false; resolutionsListView.Items.Clear(); scintilla1.StartStyling(0); scintilla1.SetStyling(scintilla1.TextLength, 0); if (ConnectionDetail == null) { MessageBox.Show("Please connect to an instance first", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var regexes = new[] { // SecLib::AccessCheckEx failed. Returned hr = -2147187962, ObjectID: <guid>, OwnerId: <guid>, OwnerIdType: <int> and CallingUser: <guid>. ObjectTypeCode: <int>, objectBusinessUnitId: <guid>, AccessRights: <accessrights> // Target: <objectid>,<objectidtype> // Principal: <callinguserid> // Privilege: <accessrights>,<objectidtype> // Depth: <objectid>,<callinguserid> new Regex("ObjectID: (?<objectid>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+), OwnerId: (?<ownerid>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+), OwnerIdType: (?<owneridtype>[0-9]+) and CallingUser: (?<callinguser>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+). ObjectTypeCode: (?<objectidtype>[0-9]+), objectBusinessUnitId: (?<objectbusinessunitid>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+), AccessRights: (?<accessrights>[a-z]+)", RegexOptions.IgnoreCase), // SecLib::CheckPrivilege failed. User: <guid>, PrivilegeName: <prvName>, PrivilegeId: <guid>, Required Depth: <depth>, BusinessUnitId: <guid>, MetadataCache Privileges Count: <int>, User Privileges Count: <int> // Target: <privilegename> // Principal: <userid> // Privilege: <privilegename> // Depth: <privilegedepth> new Regex("User: (?<callinguser>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+), PrivilegeName: (?<privilegename>prv[a-z0-9_]+), PrivilegeId: (?<privilegeid>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+), Required Depth: (?<privilegedepth>Basic|Local|Deep|Global)", RegexOptions.IgnoreCase), // Principal user (Id=<guid>, type=<int>, roleCount=<int>, privilegeCount=<int>, accessMode=<int>), is missing <prvName> privilege (Id=<guid>) on OTC=<int> for entity '<logicalname>'. context.Caller=<guid>. Or identityUser.SystemUserId=<guid>, identityUser.Privileges.Count=<int>, identityUser.Roles.Count=<int> is missing <prvName> privilege (Id=<guid>) on OTC=<int> for entity '<logicalname>'. // Target: <logicalname> // Principal: <userid> // Privilege: <privilegename> // Depth: Basic new Regex("Principal user \\(Id=\\s*(?<callinguser>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+)(\\s*,\\s*type=(?<usertype>[0-9]+))?(\\s*,\\s*roleCount=[0-9]+)?(\\s*,\\s*privilegeCount=[0-9]+)?(\\s*,\\s*accessMode=[0-9]+)?\\)\\s*,?\\s*is missing (?<privilegename>prv[a-z0-9_]+)\\sprivilege( \\(Id=(?<privilegeid>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+)\\))?( on OTC=(?<objectidtype>[0-9]+))?( for entity '(?<objectidtypename>[a-z0-9_]+)')?", RegexOptions.IgnoreCase), // Principal with id <guid> does not have <accessrights> right(s) for record with id <guid> of entity <entityname> // Target <objectid>,<objectidtype> // Principal: <callinguserid> // Privilege: <accessrights>,<objectidtype> // Depth: <objectid>,<callinguserid> new Regex("Principal with id (?<callinguser>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+) does not have (?<accessrights>[a-z]+) right\\(s\\) for record with id (?<objectid>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+) of entity (?<objectidtype>[a-z0-9_]+)", RegexOptions.IgnoreCase), // RoleService::VerifyCallerPrivileges failed. User: <guid>, UserBU: <guid>, PrivilegeName: <privilegename>, PrivilegeId: <guid>, Depth: <depth>, BusinessUnitId: <guid>, MissingPrivilegeCount: 16 // Target: None // Principal: <userid> // Privilege: <privilegename> // Depth: <privilegedepth> new Regex("User: (?<callinguser>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+), UserBU: ([a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+), PrivilegeName: (?<privilegename>prv[a-z0-9_]+), PrivilegeId: (?<privilegeid>[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+-[a-z0-9]+), Depth: (?<privilegedepth>Basic|Local|Deep|Global)", RegexOptions.IgnoreCase), }; foreach (var regex in regexes) { var match = regex.Match(scintilla1.Text); if (match.Success) { foreach (Group group in match.Groups) { if (Int32.TryParse(group.Name, out _)) { continue; } scintilla1.StartStyling(group.Index); scintilla1.SetStyling(group.Length, 1); } WorkAsync(new WorkAsyncInfo { Message = "Extracting Details...", Work = (bw, args) => { var result = new Results(); try { // Extract the details from the error message _principalReference = ExtractPrincipal(match, out var principalTypeDisplayName); var target = ExtractTarget(match, out var targetTypeDisplayName); var privilege = ExtractPrivilege(match, ref target, ref targetTypeDisplayName); var privilegeDepth = ExtractPrivilegeDepth(match, _principalReference, target); result.Target = target; result.PrincipalTypeDisplayName = principalTypeDisplayName; result.TargetTypeDisplayName = targetTypeDisplayName; result.Privilege = privilege; result.PrivilegeDepth = privilegeDepth; result.AccessRights = (AccessRights)privilege.GetAttributeValue <int>("accessright"); _targetReference = target as EntityReference; var privilegeRef = privilege.ToEntityReference(); privilegeRef.Name = privilege.GetAttributeValue <string>("name"); // Check if the problem should still exist try { if (_targetReference != null && result.AccessRights != AccessRights.None) { var recordAccess = (RetrievePrincipalAccessResponse)Service.Execute(new RetrievePrincipalAccessRequest { Principal = _principalReference, Target = _targetReference }); if ((recordAccess.AccessRights & result.AccessRights) == result.AccessRights) { result.HasAccess = true; } } else { var priv = (RetrieveUserPrivilegeByPrivilegeIdResponse)Service.Execute(new RetrieveUserPrivilegeByPrivilegeIdRequest { UserId = _principalReference.Id, PrivilegeId = privilege.Id }); if (priv.RolePrivileges.Any()) { result.HasAccess = true; } } } catch (FaultException <OrganizationServiceFault> ) { // In case the service doesn't support the RetrieveUserPrivilegeByPrivilegeId message } // Check permission is available at the minimum calculated depth. If not, increase the depth // to the next available value. if (privilegeDepth == PrivilegeDepth.Basic && !privilege.GetAttributeValue <bool>("canbebasic")) { privilegeDepth = PrivilegeDepth.Local; } if (privilegeDepth == PrivilegeDepth.Local && !privilege.GetAttributeValue <bool>("canbelocal")) { privilegeDepth = PrivilegeDepth.Deep; } if (privilegeDepth == PrivilegeDepth.Deep && !privilege.GetAttributeValue <bool>("canbedeep")) { privilegeDepth = PrivilegeDepth.Global; } switch (privilegeDepth) { case PrivilegeDepth.Basic: requiredDepthImage.Image = Properties.Resources.Basic; break; case PrivilegeDepth.Local: requiredDepthImage.Image = Properties.Resources.Local; break; case PrivilegeDepth.Deep: requiredDepthImage.Image = Properties.Resources.Deep; break; case PrivilegeDepth.Global: requiredDepthImage.Image = Properties.Resources.Global; break; } var whoAmI = (WhoAmIResponse)Service.Execute(new WhoAmIRequest()); if (_principalReference.LogicalName == "systemuser" && _principalReference.Id == whoAmI.UserId) { result.IsCurrentUser = true; } else { var resolutions = new List <Resolution>(); // Find roles that include the required permission and suggest to add them to the user var depthQuery = Math.Pow(2, (int)privilegeDepth); // Only suggest roles in the correct business unit var principal = Service.Retrieve(_principalReference.LogicalName, _principalReference.Id, new ColumnSet("businessunitid")); var businessUnitId = principal.GetAttributeValue <EntityReference>("businessunitid").Id; var sufficientRoleQry = new FetchExpression($@" <fetch xmlns:generator='MarkMpn.SQL4CDS'> <entity name='role'> <attribute name='name' /> <link-entity name='role' from='roleid' to='parentrootroleid'> <link-entity name='roleprivileges' to='roleid' from='roleid' alias='rp' link-type='inner'> <filter> <condition attribute='privilegeid' operator='eq' value='{privilege.Id}' /> <condition attribute='privilegedepthmask' operator='ge' value='{depthQuery}' /> </filter> </link-entity> </link-entity> <link-entity name='{_principalReference.LogicalName}roles' to='roleid' from='roleid' alias='sur' link-type='outer'> <filter> <condition attribute='{_principalReference.LogicalName}id' operator='eq' value='{_principalReference.Id}' /> </filter> </link-entity> <filter> <condition attribute='businessunitid' operator='eq' value='{businessUnitId}' /> <condition entityname='sur' attribute='roleid' operator='null' /> </filter> <order attribute='name' /> </entity> </fetch>"); var sufficientRoles = Service.RetrieveMultiple(sufficientRoleQry); foreach (var role in sufficientRoles.Entities) { var roleRef = role.ToEntityReference(); roleRef.Name = role.GetAttributeValue <string>("name"); var addRole = new AddSecurityRole { UserReference = _principalReference, RoleReference = roleRef }; resolutions.Add(addRole); } // Find roles currently assigned to the user and suggest them to be edited to include the required permission var existingRoleQry = new FetchExpression($@" <fetch xmlns:generator='MarkMpn.SQL4CDS'> <entity name='role'> <attribute name='parentrootroleid' /> <link-entity name='role' from='roleid' to='parentrootroleid'> <link-entity name='roleprivileges' to='roleid' from='roleid' alias='rp' link-type='outer'> <attribute name='privilegedepthmask' /> <filter> <condition attribute='privilegeid' operator='eq' value='{privilege.Id}' /> </filter> </link-entity> </link-entity> <link-entity name='{_principalReference.LogicalName}roles' to='roleid' from='roleid' alias='sur' link-type='inner'> <filter> <condition attribute='{_principalReference.LogicalName}id' operator='eq' value='{_principalReference.Id}' /> </filter> </link-entity> <filter type='or'> <condition entityname='rp' attribute='privilegeid' operator='null' /> <condition entityname='rp' attribute='privilegedepthmask' operator='lt' value='{depthQuery}' /> </filter> <order attribute='name' /> </entity> </fetch>"); var existingRoles = Service.RetrieveMultiple(existingRoleQry); foreach (var role in existingRoles.Entities) { var roleRef = role.GetAttributeValue <EntityReference>("parentrootroleid"); var existingDepth = role.GetAttributeValue <AliasedValue>("rp.privilegedepthmask"); var editRole = new EditSecurityRole { RoleReference = roleRef, PrivilegeReference = privilegeRef, Depth = privilegeDepth, ExistingDepth = existingDepth == null ? (PrivilegeDepth?)null : (PrivilegeDepth)Math.Log((int)existingDepth.Value, 2) }; resolutions.Add(editRole); } if (_targetReference != null) { // Suggest sharing the record with the required permission var sharing = new ShareRecord { UserReference = _principalReference, TargetReference = _targetReference, AccessRights = result.AccessRights }; resolutions.Add(sharing); } result.Resolutions = resolutions; } } catch (Exception ex) { result.Exception = ex; } args.Result = result; }, PostWorkCallBack = args => { var result = (Results)args.Result; if (result.Exception != null) { errorLabel.Text = result.Exception.Message; // If this is an ObjectDoesNotExist error, it's likely that the error is from a different instance unchecked { if (result.Exception is FaultException <OrganizationServiceFault> fault && fault.Detail.ErrorCode == (int)0x80040217) { errorLabel.Text += "\r\n\r\nAre you connected to the same instance the error message came from?"; } } noMatchPanel.Visible = false; errorPanel.Visible = true; } else { retryPanel.Visible = result.HasAccess; var userPrefix = $"The {result.PrincipalTypeDisplayName} "; var userName = _principalReference.Name; userLinkLabel.Text = userPrefix + userName; userLinkLabel.LinkArea = new LinkArea(userPrefix.Length, userName.Length); if (result.AccessRights == AccessRights.None) { missingPrivilegeLinkLabel.Text = $"does not have {result.Privilege.GetAttributeValue<string>("name")} permission"; } else { missingPrivilegeLinkLabel.Text = $"does not have {result.AccessRights.ToString().Replace("Access", "")} permission"; } if (result.Target == null) { targetLinkLabel.Visible = false; } else { var prefix = $"on the {result.TargetTypeDisplayName} "; var link = ""; if (_targetReference != null) { link = _targetReference.Name ?? "<Unnamed>"; } else { prefix += String.Join(", ", ((EntityMetadataCollection)result.Target).Select(m => m.DisplayName.UserLocalizedLabel.Label)); } targetLinkLabel.Text = prefix + link; targetLinkLabel.LinkArea = new LinkArea(prefix.Length, link.Length); targetLinkLabel.Visible = true; } requiredPrivilegeLabel.Text = $"To resolve this error, the user needs to be granted the {result.Privilege.GetAttributeValue<string>("name")} privilege to {result.PrivilegeDepth} depth"; if (result.IsCurrentUser) { errorLabel.Text = "Because you are the affected user you cannot use this tool to automatically resolve any security problems. Please ask a system administrator to run this tool on your behalf."; errorPanel.Visible = true; } else { foreach (var resolution in result.Resolutions) { var lvi = resolutionsListView.Items.Add(resolution.ToString()); lvi.ImageIndex = resolution.ImageIndex; lvi.Tag = resolution; } resolutionsListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); } noMatchPanel.Visible = false; recordPermissionsPanel.Visible = true; } } });