Bulk Edit UserRoles for Acumatica Users - acumatica

I need to bulk update about 2000+ users in Acumatica (ie: Guest access users). I need to assign a new role to all of these user to support a new custom integration.
I am looking for a solution using either the Rest API or some sort of Excel bulk Import/Update that assigns the roles.
I have not had much luck working with UserRoles via the Rest API. I have only been able to download the users > user roles via a generic inquiry and ODATA. So far I have not found any documentation around this. Any guidance would be appreciated!

REST Api is not really the best tool for this job. It doesn't contain an endpoint for Users (needs to be extended) and is a bit hard to use for complex logic. There isn't an Excel import mechanism either (outside import scenarios).
I would suggest to create a Customization Plugin for this task instead.
They can be created in Acumatica customization project editor with a new CODE file:
A customization plugin is a script that is executed when you publish the customization. I tested the following code which assign Portal User role to all guest users as an example.
using System;
using PX.Data;
using Customization;
using PX.SM;
using PX.EP;
using System.Linq;
namespace UpdateUserRoles
{
public class UpdateUserRoles : CustomizationPlugin
{
public override void UpdateDatabase()
{
const string roleNameToAssign = "Portal User";
AccessUsers accessUsers = PXGraph.CreateInstance<AccessUsers>();
// For each users in the system
foreach (Users user in accessUsers.UserList.Select())
{
// Modify only guest users
if (user.Guest != true)
continue;
try
{
// Set current user
accessUsers.UserList.Current = user;
// Assign role
EPLoginTypeAllowsRole role = (EPLoginTypeAllowsRole)accessUsers.AllowedRoles.Select().RowCast<EPLoginTypeAllowsRole>()
.Where(x => x.Rolename.Contains(roleNameToAssign)).FirstOrDefault();
role.Selected = true;
accessUsers.AllowedRoles.Update(role);
WriteLog("User " + user.Username + " updated.");
}
catch (Exception ex)
{
WriteLog("Error: " + ex.Message);
}
}
accessUsers.Save.Press();
}
}
}

Related

Get Azure Password Reset Authentication Phone attribute using microsoft graph library

How can we query Azure Directory and get the Authentication Phone number used for password reset. It is not the mobile number that is exposed using Microsoft graph Library. We are using MVC 5, C# VS2017.
Thank you, Tim:
Currently, we use the below code to get user properties however, the phone number used during a password reset is not there.
// Get the current user's profile.
public async Task<List<ResultsItem>> GetMe(GraphServiceClient graphClient)
{
List<ResultsItem> items = new List<ResultsItem>();
// Get the current user's profile.
User me = await graphClient.Me.Request().GetAsync();
if (me != null)
{
// Get user properties.
items.Add(new ResultsItem
{
Display = me.DisplayName,
Id = me.Id,
Properties = new Dictionary<string, object>
{
{ Resource.Prop_Upn, me.UserPrincipalName },
{ Resource.Prop_Id, me.Id }
}
});
}
return items;
}
Yes, unfortunately you currently cannot use Microsoft Graph to programmatically set the strong authentication properties, which is used for Self Service Password Reset and for Multi-Factor Authentication.
This is a frequently requested features, it is on our backlog and I hope to deliver it soon, however I cannot provide a specific timeline when it will be available.

Project Server Online CSOM - GeneralSecurityAccessDenied while reading TimePhase Assignments

This is my first SO question so please let me know if this question is not very clear or if I am missing anything.
FYI SO prevented me from attaching links, so sorry for all the bad formatting.
Overview
I'm trying to read (and write) the "Actual work" for a resource in Project Server Online by using the CSOM library available by Microsoft. Reading and writing the assignments and Actual work is working perfectly, as long as I am reading the assignments for the currently authenticated user. If I attempt to read this for another resource, I receive a GeneralSecurityAccessDenied error.
I've done this in the past using Impersonation, which is supposed to be called transparently in the background if the user has the StatusBrokerPermission, but it doesn't seem to be working for me. Impersonation has been removed in 2013+, so that's no longer an option.
Problem summary
The CSOM is supposed to transparently enable statusing extensions to allow status updates to be made for resources other than the currently authenticated user (as long as the user has the status broker permission). This works fine for adding new assignments, but does not work when trying to update actual TimePhased hours via the TimePhased assignments. The assignments cannot be queried, and thus, we cannot call SubmitAllStatusUpdates to submit the hours.
Research
Usage scenarios for the CSOM: https:// msdn.microsoft.com/en-us/library/office/jj163082(v=office.15).aspx#pj15_WhatTheCSOM_UsageScenarios
Impersonation Deprecated: https:// msdn.microsoft.com/en-us/library/office/ee767690(v=office.15).aspx#pj15_WhatsNew_Deprecated)
Picture: Supposed to read on behalf of another user...
People with the same problem # 1: https:// social.technet.microsoft.com/Forums/projectserver/en-US/dccdb543-18a1-4a0e-a948-5d861305516e/how-to-get-resource-assignments-summary-view-data-project-server-online-2013?forum=projectonline)
People with the same problem # 2: http:// uzzai.com/ZB43wp95/ps2013-app-how-to-read-and-update-timephased-data-with-jsom-javascript-csom.html
People with the same problem # 4: https:// social.technet.microsoft.com/Forums/Sharepoint/en-US/be27d497-e959-44b6-97cb-8f19fe0278fe/csom-how-to-set-timephase-data-on-an-assignment?forum=project2010custprog
Other things I've tried
Using the CSOM with the MsOnlineClaimsHelper to retrieve the FedAuth cookies for a user (and assigning them using the CookieContainer).
Using the REST/OData API.
a) https:// URL.sharepoint.com/sites/pwa/_api/ProjectServer/EnterpriseResources('c39ba8f1-00fe-e311-8894-00155da45f0e')/Assignments/GetTimePhaseByUrl(start='2014-12-09',end='2014-12-09')/assignments
Enabling the "StatusBrokerPermission" for the user
Unchecking the “Only allow task updates via Tasks and Timesheets.” Option within the server settings screen (Task settings and display).
Creating a SharePoint-hosted app and using JSOM code equivalent to the CSOM code above.
a) The code we wrote was JavaScript being executed from within SharePoint app, so we did not need to provide authentication. The user who was logged in had the StatusBrokerPermission.
Using a Provider-hosted SharePoint app and using the CSOM code above. We tried using all authentication methods for CSOM above, with an additional test:
a) using Fiddler to view the FedAuth cookies being set by the SharePoint app authentication, and overriding the WebRequest to manually insert the FedAuth/rtFA cookies: webRequestEventArgs.WebRequestExecutor.WebRequest.CookieContainer = getStaticCookieContainer();
Using timesheets to submit time phased data.
a) We can only create a timesheet for the currently-authenticated user, and cannot populate timesheet lines with projects / assignments not available to him (or a GeneralItemDoesNotExist error is thrown).
Manually issuing a “SubmitAllStatusUpdates” CSOM request using fiddler, as a different user.
a) The purpose of this test was to determine if we can write time phased data, even if we can’t read it.
Making sure the project was checked out to the current user.
Using administrative delegation for a resource.
Setting all available options within project permissions.
Using the Project Web UI to enter the TimePhased data for other resources.
Using SharePoint permission mode instead of Project Permission Mode.
The code
See failing code screenshot here
using System;
using System.Security;
using Microsoft.ProjectServer.Client;
using Microsoft.SharePoint.Client;
namespace ProjectOnlineActuals
{
static class Program
{
const string projectSite = "https://URL.sharepoint.com/sites/pwa/";
private const string edward = "c39ba8f1-00fe-e311-8894-00155da45f0e";
private const string admin = "8b1bcfa4-1b7f-e411-af75-00155da4630b";
static void Main(string[] args)
{
TestActuals();
}
private static void TestActuals()
{
Console.WriteLine("Attempting test # 1 (login: admin, resource: admin)");
TestActuals("admin#URL.onmicrosoft.com", "123", admin);
Console.WriteLine("Attempting test # 2 (login: admin, resource: edward)");
TestActuals("adminy#hmssoftware.onmicrosoft.com", "123", edward);
Console.ReadLine();
}
private static void TestActuals(string username, string password, string resourceID)
{
try
{
using (ProjectContext context = new ProjectContext(projectSite))
{
DateTime startDate = DateTime.Now.Date;
DateTime endDate = DateTime.Now.Date;
Login(context, username, password);
context.Load(context.Web); // Query for Web
context.ExecuteQuery(); // Execute
Guid gResourceId = new Guid(resourceID);
EnterpriseResource enterpriseResource = context.EnterpriseResources.GetByGuid(gResourceId);
context.Load(enterpriseResource, p => p.Name, p => p.Assignments, p => p.Email);
Console.Write("Loading resource...");
context.ExecuteQuery();
Console.WriteLine("done! {0}".FormatWith(enterpriseResource.Name));
Console.Write("Adding new resource assignment to collection...");
enterpriseResource.Assignments.Add(new StatusAssignmentCreationInformation
{
Comment = "testing comment - 2016-02-17",
ProjectId = new Guid("27bf182c-2339-e411-8e76-78e3b5af0525"),
Task = new StatusTaskCreationInformation
{
Start = DateTime.Now,
Finish = DateTime.Now.AddDays(2),
Name = "testing - 2016-02-17",
}
});
Console.WriteLine("done!");
Console.Write("Trying to save new resource assignment...");
enterpriseResource.Assignments.Update();
context.ExecuteQuery();
Console.WriteLine("done!");
Console.Write("Loading TimePhase...");
TimePhase timePhase = enterpriseResource.Assignments.GetTimePhase(startDate.Date, endDate.Date);
context.ExecuteQuery();
Console.WriteLine("done!");
Console.Write("Loading TimePhase assignments...");
context.Load(timePhase.Assignments);
context.ExecuteQuery();
Console.WriteLine("done! Found {0} assignments.".FormatWith(timePhase.Assignments.Count));
Console.WriteLine("Updating TimePhase assignments...");
foreach (var assignment in timePhase.Assignments)
{
Console.WriteLine("Updating assignment: {0}. ActualWork: {1}".FormatWith(assignment.Name, assignment.ActualWork));
assignment.ActualWork = "9h";
assignment.RegularWork = "3h";
assignment.RemainingWork = "0h";
}
timePhase.Assignments.SubmitAllStatusUpdates("Status update comment test 2016-02-17");
context.ExecuteQuery();
Console.WriteLine("done!");
Console.WriteLine("Success (retrieved & updated {0} time phase assignments)!".FormatWith(timePhase.Assignments.Count));
}
}
catch (Exception ex)
{
if (ex.ToString().Contains("GeneralSecurityAccessDenied"))
Console.WriteLine("ERROR! - GeneralSecurityAccessDenied");
else
throw;
}
finally
{
Console.WriteLine();
Console.WriteLine();
}
}
private static void Login(ProjectContext projContext, string username, string password)
{
var securePassword = new SecureString();
foreach (char c in password)
securePassword.AppendChar(c);
projContext.Credentials = new SharePointOnlineCredentials(username, securePassword);
}
static string FormatWith(this string str, params object[] args)
{
return String.Format(str, args);
}
}
}
Can anyone help??

Orchard CMS ask for an invite token to link to backend data during registration using workflows only

I am using Orchard CMS v1.9 and want to display a custom registration page to accept the usual username/password/email and an additional token (invite token). The token will be used to match the user to some to custom data on the server.
I have walked through this blog Customizing User Registation With Dynamic Forms And Workflows. But in addition to what is achieved in this blog I want to force a registering user to enter a token. The token is used to lookup data on the server and create a link to the userpart.
Adding the token to the form is not the issue - its the querying and linking the entered token to the backend data and storing it in the userpart that im finding awkward.
Is this possible using just workflows - or do i need a custom module? I did not see a custom action that allowed me to match the token and link.
Is there a custom module already available that does something
similar?
Disclaimer: This approach is currently based on Orchard 1.10 but was initially developed on the 1.9.x branch. It does not rely on Dynamic Forms and Workflows, but I think you could achieve something similar with those modules.
Okay so I ended up building an example module with our approach to extended users / activation system. I stripped out a lot of code, but also let some juicy parts, which aren't directly related to your answer, in it.
First you should check out the UsersController it has the activate actions you are searching for. You may need to extend the orchard LogOn-View and include some GET & POST Actions accordingly.
[AllowAnonymous]
[HttpGet]
public ActionResult Activate(string activationCode)
{
// validation stuff....
var viewModel = new CustomUserActivate
{
// This is the activationCode you're looking for
ActivationCode = userFromActivationCode.ActivationCode,
UserName = userFromActivationCode.User.UserName,
WelcomeText = userFromActivationCode.WelcomeText,
Email = userFromActivationCode.User.Email
};
return this.View(viewModel);
}
[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Activate(CustomUserActivate input)
{
if ( input == null )
{
this.ModelState.AddModelError("_form", this.T("The argument cannot be null").Text);
}
CustomUserPart customUserPart = null;
if ( this.ModelState.IsValid )
{
customUserPart = this.myService.GetCustomUserByActivationCode(input.ActivationCode);
if ( customUserPart == null || customUserPart.User == null || customUserPart.User.UserName != input.UserName )
{
this.notifier.Add(NotifyType.Error, this.T("The activation failed"));
}
if ( string.IsNullOrEmpty(input.Email) )
{
this.ModelState.AddModelError("Email", this.T("You must specify an email address.").Text);
}
else if ( input.Email.Length >= 255 )
{
this.ModelState.AddModelError("Email", this.T("The email address you provided is too long.").Text);
}
else if ( !Regex.IsMatch(input.Email, UserPart.EmailPattern, RegexOptions.IgnoreCase) )
{
// http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx
this.ModelState.AddModelError("Email", this.T("You must specify a valid email address.").Text);
}
else if ( !this.myService.VerifyEmailUnicity(customUserPart.User.Id, input.Email) )
{
this.ModelState.AddModelError("Email", this.T("This email address is already in use.").Text);
}
}
if ( !this.ModelState.IsValid )
{
return this.View(input);
}
Debug.Assert(customUserPart != null, "customUserPart != null");
var user = customUserPart.User;
var userParams = new CreateUserParams(user.UserName, input.Password, input.Email, passwordQuestion: null, passwordAnswer: null, isApproved: true);
this.myService.ActivateCustomUser(customUserPart.Id, userParams);
this.notifier.Add(NotifyType.Information, this.T("Your account was activated. You can now log in."));
return this.RedirectToAction("LogOn", "Account", new { area = "Orchard.Users" });
}
The interesting stuff happens in MyService.cs.
We designed the activation system so that you can still leverage all the features of the Orchard.User Module like Email-Verifcation.
For this we've implemented some CustomSettings, where you can decide if your user get's completely activated when an ActivationCode is used or if you trigger the normal Orchard mechanism.
I guess it's best to checkout the module and step through the code in Visual Studio.
Here a two screenshots of our activation views.
Step 1 - Enter your activation code
Step 2 - Fill in the remaining fields
Profit!
All the additional source is to make use of the CustomUser / ActivationCode in Workflows, Events, Tokens, etc. But I leave this for you to discover.
If you want more detailed descriptions of the source on GitHub let me know.
Hope this helps!

Control Report Permission Based on Parameters in Reporting Services

Assume we have a report called SalesSummary for a large department. This department has many smaller teams for each product. People should be able to see information about their own product, not other teams' products. We also have one domain group for each of these teams.
Copying SalesSummary report for each team and setting the permission is not the best option since we have many products. I was thinking to use a code similar to below on RS, but it doesn't work. Apparently, System.Security.Principal.WindowsPrincipal is disabled by default on RS.
Public Function isPermitted() As Boolean
Dim Principal As New System.Security.Principal.WindowsPrincipal(System.Security.Principal.WindowsIdentity.GetCurrent())
If (Principal.IsInRole("group_prod")) Then
Return true
Else
Return false
End If
End Function
I also thought I can send the userID from RS to SQL server, and inside my SP I can use a code similar to below to query active directory. This also doesn't work due to security restriction.
SELECT
*
FROM OPENQUERY(ADSI,'SELECT cn, ADsPath FROM ''LDAP://DC=Fabricam,DC=com'' WHERE objectCategory=''group''')
Is there any easier way to achieve this goal?
Thanks for the help!
The first option you suggested (using embedded code to identify the executing user) will not be reliable. SSRS code is not necessarily executed as the user accessing the report, and may not have access to that users credentials, such as when running a subscription.
Your second approach will work, but requires the appropriate permissions for your SQL server service account to query Active Directory.
Another approach is to maintain a copy of the group membership or user permissions in a SQL table. This table can be updated by hand or with an automated process. Then you can easily incorporate this into both available parameters and core data queries.
So I ended up with this code:
PrincipalContext domain = new PrincipalContext(ContextType.Domain, "AD");
UserPrincipal user = UserPrincipal.FindByIdentity(domain, identityName);
//// if found - grab its groups
if (user != null)
{
PrincipalSearchResult<Principal> _groups = null;
int tries = 0;
//We have this while because GetGroups sometimes fails! Specially if you don't
// mention the domain in PrincipalContext
while (true)
{
try
{
_groups = user.GetGroups();
break;
}
catch (Exception ex)
{
logger.Debug("get groups failed", ex);
if (tries > 5) throw;
tries++;
}
}
// iterate over all groups, just gets groups related to this app
foreach (Principal p in _groups)
{
// make sure to add only group principals
if (p is GroupPrincipal)
{
if (p.Name.StartsWith(GROUP_IDENTIFIER))
{
this.groups.Add((GroupPrincipal)p);
this.groupNames.Add(p.Name);
}
}
}
}
Now, that you have a list of related group you can check the list to authorize the user!

Creating a custom Document Library in SharePoint

I have a document library in my SharePoint page and there are 10 documents in it.
If User A is logged in I want him to only see 5 of those documents in that document library.
How can I create some custom document library for this to work?
I have MOSS installed.
Thanks in advance!
You could configure different permissions on each document in the document library. Just select the "Manage Permissions" option on each item and break the permission inheritance from the document library level. Just note that having too many documents with item level permissions can create a maintenance nightmare for you. Another option could be to create two document libraries with different permissions.
Write an ItemEventReceiver that breaks the permissions based on a field in the library, i.e. a column that holds the different roles .
We have done this by creating a list that holds all roles coupled to sharepoint groups.
i.e.
Administrator -> Owners of website (SPGroup), Company Administrators (SPGroup)
Managers -> Managers (SPGroup)
then in our content type we have a lookup column to this list.
Here's the code for the ItemEventReceiver:
public override void ItemUpdated(SPItemEventProperties properties)
{
lock (_lock)
{
try
{
using (SPSite site = new SPSite(properties.SiteId,
properties.ListItem.ParentList.ParentWeb.Site.SystemAccount.UserToken))
using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl))
{
web.AllowUnsafeUpdates = true;
var item = web.Lists[properties.ListId].GetItemById(properties.ListItemId);
var roles = item["Roles"] as SPFieldLookupValueCollection;
var rolesList = web.Site.RootWeb.Lists["Company Roles"];
var groupsToAdd = new List<SPFieldUserValue>();
if (item.HasUniqueRoleAssignments)
{
item.ResetRoleInheritance();
item = item.ParentList.GetItemById(item.ID);
}
if (roles != null && roles.Count > 0)
{
// Iterate over the roles and see if there is a group associated
foreach (var role in roles)
{
var roleItem = rolesList.GetItemById(rol.LookupId);
if (roleItem != null)
{
// This is the SPgroup field in the rolesList
var groups = roleItem["Groups"] as SPFieldUserValueCollection;
if (groups != null)
{
groupsToAdd.AddRange(from g in groups
where g.User == null
select g);
}
}
}
if (groupsToAdd.Count > 0)
{
item.BreakRoleInheritance(false);
foreach (var value in groupsToAdd)
{
var group = web.Groups[value.LookupValue];
var assignment = web.RoleAssignments.GetAssignmentByPrincipal(group);
item.RoleAssignments.Add(assignment);
}
}
}
DisableEventFiring();
item.SystemUpdate(false);
EnableEventFiring();
}
}
catch (Exception ex)
{
//LOG ERROR
}
}
}
If the coding doesn't work for you, and you'd rather not set permissions on each file, then there is a third option. We use folders with permissions set on them.
e.g.
Create a folder called "Managers", break permissions, and set rights to only the managers.
Create another folder called "Employee 1", break permissions, and set Contribute rights to the Employee and the Employe's manager.
Place the files in the appropriate folders and it will inherit rights from the folder.
This way, managers can see the manager files, and all files of their employees. Users can only see their own files.
Similar logic can be done for Headquarters, Region 1, Region 2, etc ... and creating different Groups for each region and then assigning the group to the folder's permissions.
Note, there's always concern in using this design on maintaining all the permissions and on performance, but we've been doing similar things for 750+ user populations and thousand of docs and it's been working fine for us so far.

Resources