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??
Related
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();
}
}
}
Tested BY(19)
Under tested by there are 19 test cases.
If the id of the user story is given how to get these test cases.
If (Link link is Related) returns all the links.
How to get just the test cases.
Any help would be appreciated. I just started working with tfs using c#
You could use TFS REST API to get the information you need, the Microsoft.VSTS.Common.TestedBy-Forward relation is the TestedBy link type. The API is as below:
GET https://{account}.visualstudio.com/{project}/_apis/wit/workitems/{id}?$expand=Relations&api-version=5.0-preview.3
The following screenshot is the response of the REST API:
Here is a sample with client library to get test case ids from the User Story for your reference:
using System;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
namespace GetWorkItemFullyExpanded
{
class Program
{
static void Main(string[] args)
{
GetWorkItemFullyExpanded();
}
public static WorkItem GetWorkItemFullyExpanded()
{
int id = xx;
var myCredentials = new VssClientCredentials();
var connection = new VssConnection(new Uri(#"https://xxxx.visualstudio.com"), myCredentials);
WorkItemTrackingHttpClient workItemTrackingClient = connection.GetClient<WorkItemTrackingHttpClient>();
WorkItem workitem = workItemTrackingClient.GetWorkItemAsync(id, expand: WorkItemExpand.Relations).Result;
Console.WriteLine(workitem.Id);
Console.WriteLine("Relations: ");
foreach (var relation in workitem.Relations)
{
if (relation.Rel == "Microsoft.VSTS.Common.TestedBy-Forward")
Console.WriteLine(relation.Url);
}
return workitem;
}
}
}
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.
I want to Get the Active Directory names in sharepoint in a list.
To got Know that SharePoint 2013 Has some Hidden URL which Shows Current Active Directory User I wan to get it into List.
http://{YourSharepointUrl}/_catalogs/users/simple.aspx
Now I want to have list of all the names Displayed on my sharepoint
I am using the code:
private static void GetAllSiteUsers()
{
// Starting with ClientContext, the constructor requires a URL to the server running SharePoint.
var sharepointContext = new ClientContext("http://yoursharepointurl/");
}
Now I am Getting error it says about assembly reference doesn't exist.So I checked on google and added up this ddl and add the using Microsoft.SharePoint.Client; reference also.Still Not working.
Please Let me Know what needed to be done Guys
Purpose Of Making Program:To have all the AD Users and Make a work Group so that I can Assign them some right in such a way when assigned grp open something some other URL in iframe shows. and if some one else than other URL in iframe is shown to him.
Thanks IN Advance Guys.
You are in Server side (Farms solution), so don't use : ClientContext, this is for Client application, not server.
You just have to get the : User Information List
You could try somthing like :
using(SPSite site = new SPSite("URLsiteCollection")){
using(SPWeb web = site.rootWeb){
SPList userList = web.SiteUserInfoList;
SPListItemCollection allUsers = userList.Items;
foreach(SPListItem userItem in allUsers){
string userEmail = Convert.Tostring(userItem["EMail"]);
string userName = userItem.Title;
....
}
}
}
To get all information about active directory user or group you can use
PrincipalContext pContext = new PrincipalContext (ContextType.Domain, YOUR_DOMAIN);
//For User
UserPrincipal userPrincipal = new UserPrincipal (pContext);
PrincipalSearcher userSearch = new PrincipalSearcher (userPrincipal);
//For Group
GroupPrincipal grpPrincipal = new GroupPrincipal (pContext);
PrincipalSearcher grpSearch = new PrincipalSearcher (grpPrincipal);
foreach (UserPrincipal result in userSearch.FindAll())
{
if (result.SamAccountName!= null)
// Your code
}
foreach (GroupPrincipal result in grpSearch.FindAll())
{
if (result != null)
{
// Your code
}
Assembly
System.DirectoryServices.AccountManagement
Namespace
using System.DirectoryServices.AccountManagement;
I am going crazy since two days solving an issue. The problem is;
I am making a console APP which is talking to SharePoint Online using global admin account (One which was specified as admin while making a new subscription). What I am trying to achieve is, I want to add a custom action using CSOM to each site collection and subsite of office 365. That code works fine except on the root site collection which is pre-created by office 365 while signing up (i.e. https://xyz.sharepoint.com)
For any tenant for root site collection, it gives me below error;
{
"SchemaVersion":"15.0.0.0","LibraryVersion":"16.0.3912.1201","ErrorInfo":{
"ErrorMessage":"Access denied. You do not have permission to perform
this action or access this
resource.","ErrorValue":null,"TraceCorrelationId":"2a47fd9c-c07b-1000-cfb7-cdffbe3ab83a","ErrorCode":-2147024891,"ErrorTypeName":"System.UnauthorizedAccessException"
},"TraceCorrelationId":"2a47fd9c-c07b-1000-cfb7-cdffbe3ab83a" }
Now the user is global admin. I also added again that user as site collection admin.
The same piece of code works fine on other site collections (search site collection, any newly made site collection...).
here is a code;
using (ClientContext spcollContext = new ClientContext(web.Url))
{
SecureString passWord = new SecureString();
foreach (char c in strAdminPassword.ToCharArray()) passWord.AppendChar(c);
SharePointOnlineCredentials creds = new SharePointOnlineCredentials(strAdminUser, passWord);
spcollContext.Credentials = creds;
Web currentweb = spcollContext.Web;
spcollContext.Load(currentweb);
spcollContext.ExecuteQuery();
// authCookie = creds.GetAuthenticationCookie(new Uri(web.Url));
var existingActions2 = currentweb.UserCustomActions;
spcollContext.Load(existingActions2);
spcollContext.ExecuteQuery();
var actions2 = existingActions2.ToArray();
foreach (var action in actions2)
{
if (action.Description == "CustomScriptCodeForEachsite" &&
action.Location == "ScriptLink")
{
action.DeleteObject();
spcollContext.ExecuteQuery();
}
}
var newAction2 = existingActions2.Add();
newAction2.Description = "CustomScriptCodeForEachsite";
newAction2.Location = "ScriptLink";
newAction2.ScriptBlock = scriptBlock;
newAction2.Update();
spcollContext.Load(currentweb, s => s.UserCustomActions);
spcollContext.ExecuteQuery(); // GETTING ERROR ON THIS LINE.
}
Note: Above error is Fiddler traces.
Most probably this behavior is caused by Custom Script feature, basically
the issue occurs when the Custom Script feature is turned off
How to verify?
You could verify the site permissions using the following console app:
using (var ctx = GetContext(webUri, userName, password))
{
var rootWeb = ctx.Site.RootWeb;
ctx.Load(rootWeb, w => w.EffectiveBasePermissions);
ctx.ExecuteQuery();
var permissions = rootWeb.EffectiveBasePermissions;
foreach (var permission in Enum.GetValues(typeof(PermissionKind)).Cast<PermissionKind>())
{
var permissionName = Enum.GetName(typeof(PermissionKind), permission);
var hasPermission = permissions.Has(permission);
Console.WriteLine("Permission: {0}, HasPermission: {1}", permissionName, hasPermission);
}
}
where
public static ClientContext GetContext(Uri webUri, string userName, string password)
{
var securePassword = new SecureString();
foreach (var ch in password) securePassword.AppendChar(ch);
return new ClientContext(webUri) {Credentials = new SharePointOnlineCredentials(userName, securePassword)};
}
When SP.PermissionKind.AddAndCustomizePages is set to False, the Access denied error occurs while adding user custom action.
Solution
According to Turn scripting capabilities on or off:
For self-service created sites, custom scripting is disabled by
default
Solution: enable Allow users to run custom scripts on self-service created sites
To enable or disable scripting from the SharePoint admin center
Sign in to Office 365 with your work or school account.
Go to the SharePoint admin center.
Select Settings.
Under Custom Script choose:
Prevent users from running custom script on personal sites or Allow
users to run custom script on personal sites.
Prevent users from running custom script on user created sites or
Allow users to run custom script on self-service created sites.
Select OK. It takes about 24 hours for the change to take
effect.
Since any change to the scripting setting made through the SharePoint Online admin center may take up to 24 hours to take effect, you could enable scripting on a particular site collection immediately via CSOM API (SharePoint Online Client Components SDK) as demonstrated below:
public static void DisableDenyAddAndCustomizePages(ClientContext ctx, string siteUrl)
{
var tenant = new Tenant(ctx);
var siteProperties = tenant.GetSitePropertiesByUrl(siteUrl, true);
ctx.Load(siteProperties);
ctx.ExecuteQuery();
siteProperties.DenyAddAndCustomizePages = DenyAddAndCustomizePagesStatus.Disabled;
var result = siteProperties.Update();
ctx.Load(result);
ctx.ExecuteQuery();
while (!result.IsComplete)
{
Thread.Sleep(result.PollingInterval);
ctx.Load(result);
ctx.ExecuteQuery();
}
}
Usage
using (var ctx = GetContext(webUri, userName, password))
{
using (var tenantAdminCtx = GetContext(tenantAdminUri, userName, password))
{
DisableDenyAddAndCustomizePages(tenantAdminCtx,webUri.ToString());
}
RegisterJQueryLibrary(ctx);
}
where
public static void RegisterJQueryLibrary(ClientContext context)
{
var actions = context.Site.UserCustomActions;
var action = actions.Add();
action.Location = "ScriptLink";
action.ScriptSrc = "~SiteCollection/Style Library/Scripts/jQuery/jquery.min.js";
action.Sequence = 1482;
action.Update();
context.ExecuteQuery();
}
If you don't have time for CSOM as described by Vadim, the page also links to a powershell script you can use:
Set-SPOsite <SiteURL> -DenyAddAndCustomizePages 0
But note that SiteUrl needs to be the admin url. If your tenant is https://mysite.sharepoint.com, the url you use is https://mysite-admin.sharepoint.com"
In our case, we were in the midst of a deployment when this hit and could not wait 24 hours (or even one hour!) to continue. Everything had been fine in our testing site collections, but when we deployed to the tenant root, we hit the error described above and this script fixed it. Apparently the feature is turned off by default on the tenant root.
Current site is not a tenant administration site
Turn scripting capabilities on or off
My first response would be that you shouldn't add a CustomAction on the fly through code. That said, I'm sure you have a good reason to need to do so.
Try to set the AllowUnsafeUpdates flag on SPWeb to true as soon as you reference currentWeb. Make sure to also set it back to false after you call the final ExecuteQuery()
By default, AllowUnsafeUpdates is false. It is used to block cross-site scripting attacks.
https://msdn.microsoft.com/en-us/library/Microsoft.SharePoint.SPWeb_properties.aspx