Carl
Carl

Reputation: 61

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

  1. Usage scenarios for the CSOM: https:// msdn.microsoft.com/en-us/library/office/jj163082(v=office.15).aspx#pj15_WhatTheCSOM_UsageScenarios

  2. 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...

  1. 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)

  2. People with the same problem # 2: http:// uzzai.com/ZB43wp95/ps2013-app-how-to-read-and-update-timephased-data-with-jsom-javascript-csom.html

  3. 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

  1. Using the CSOM with the MsOnlineClaimsHelper to retrieve the FedAuth cookies for a user (and assigning them using the CookieContainer).
  2. 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
  3. Enabling the "StatusBrokerPermission" for the user
  4. Unchecking the “Only allow task updates via Tasks and Timesheets.” Option within the server settings screen (Task settings and display).
  5. 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.
  6. 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();
  7. 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).
  8. 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.
  9. Making sure the project was checked out to the current user.
  10. Using administrative delegation for a resource.
  11. Setting all available options within project permissions.
  12. Using the Project Web UI to enter the TimePhased data for other resources.
  13. 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("[email protected]", "123", admin);

                Console.WriteLine("Attempting test # 2 (login: admin, resource: edward)");
                TestActuals("[email protected]", "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??

Upvotes: 6

Views: 2235

Answers (0)

Related Questions