Often we have seen that out of box securities are not enough to handle the security requirements within an organization, so we start looking for options for managing security, often we end up creating multiple groups & categories, though there is another security model that can be worked out using RBS but as per my experience RBS has its own constraints which in itself is another topic of discussion, hence leaving it to be explained as part of next post, so as said we often end up creating multiple categories in order to segregate projects, it remains good as long as there are few but often down the line it starts getting complex and hence adding manual overhead to maintain it, now there are several things options we could pursue
I won’t be talking about managing security categories & groups manually as we all know that’s the option typically used other methods but would definitely recommend plan early for all your security needs as if you are planning customized automated security implementation you may need to start discussions early in the phase of implementation you will know why i am saying so as as you read down
• Using Project Permission :: Let the project owner decide & manage the security, this is also applicable in cases where there are some people who are not on project team or project task neither overseeing a project but need permission to see the project, for eg an Audit group which is not related directly to project but as an individual department needs to audit few projects, so this could be achieved using Project Permission which an owner can manage by himself, refer to the link for more details on how to use it, Advantage of using this method is you don’t need to create additional categories or groups to manage permission every time such a request comes, disadvantage is there are predefined permissions that you can but still doesn’t makes an overhead for your administrators, but be cautioned as this method gives flexibility it also adds another level of risk and needs monitoring on a regular basis as to when the permission needs to be removed once the work is done
• Using Automated methods which essentially involves customization :: So when you think security management would be an overhead for administrators and you don’t want to call upon an administrator every time a project is created to get the project added to a specific category, you may want to get some customization done where in using code you get your projects automatically added to a specific category as soon as the project is created, for eg based on an enterprise project type and some other project level metadata you would want to add the project automatically to the category, so for this you would need to get some PSI code working and hence in this post below you will find the code base for achieving the same
Now remember above i said you should start planning / discussion early on your security automation and the reason was now so you have the code base available which can automatically add the projects to categories but to implement the code there are several possible solutions one of them being the custom workflow, so if your custom workflow is in development you may want to embed the code within your workflow and so you need it early, there isn’t any hard or fast rule why you should be doing it the first time but from my experience would recommend to get it done earlier as no of times you redeploy your workflow you lose workflow history as you need to reattach all your projects to the workflow and need to move them into appropriate stages
Hence there are 2 possible ways you could get your code deployed as below
1. Embed the code base within your custom workflow
2. Add the code base as server-side event handler (Recommended)
In my opinion i would recommend using the second approach as it removes the dependency of workflow and if in worst case anything appears to be causing trouble with the code it can be easily redeployed and would not cause any trouble to workflow, but adding to workflow has its own advantages 🙂
Now having talked too much would just post my code below, remember it’s based on WCF & i am using compiled ProjectServerServices.DLL & Project Server Library DLL as reference hence please remember to add & other referenced them before you start using it
private void CreateCategory_Click(object sender, EventArgs e)
{
pwaUrl = @”http://ServerName/pwa/”;
pwaUri = new Uri(pwaUrl);
const int MAXSIZE = 500000000;
const string svcRouter = “_vti_bin/PSI/ProjectServer.svc”;
pwaUrl = pwaUri.Scheme + Uri.SchemeDelimiter + pwaUri.Host + “:” + pwaUri.Port + pwaUri.AbsolutePath;
// Create a binding for HTTP.
BasicHttpBinding binding = null;
if (pwaUri.Scheme.Equals(Uri.UriSchemeHttps))
{
// Create binding for HTTPS.
binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
}
else
{
// Create binding for HTTP.
binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
}
binding.Name = “basicHttpConf”;
binding.SendTimeout = TimeSpan.MaxValue;
Console.WriteLine(“SendTimeout value:\n\t{0} days,\n\t{1} hours,\n\t{2} minutes,\n\t{3} seconds”,
binding.SendTimeout.Days.ToString(), binding.SendTimeout.Hours.ToString(),
binding.SendTimeout.Minutes.ToString(), binding.SendTimeout.Seconds.ToString());
binding.MaxReceivedMessageSize = MAXSIZE;
binding.ReaderQuotas.MaxNameTableCharCount = MAXSIZE;
binding.MessageEncoding = WSMessageEncoding.Text;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
// The endpoint address is the ProjectServer.svc router for all public PSI calls.
EndpointAddress address = new EndpointAddress(pwaUrl + svcRouter);
#region UID
ICredentials credentials = new NetworkCredential(“UserName”, “Password”, “Domain”);
#endregion
SvcSecurity.SecurityClient SecClient = new SvcSecurity.SecurityClient(binding,address);
SecClient.ClientCredentials.Windows.ClientCredential = (NetworkCredential)credentials;
SecClient.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
SecClient.ChannelFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
SecClient.ChannelFactory.Credentials.Windows.AllowNtlm = true;
SecClient.Endpoint.Address = new EndpointAddress(pwaUrl + “_vti_bin/PSI/ProjectServer.svc”);
using (OperationContextScope scope = new OperationContextScope(SecClient.InnerChannel))
{
// Disable Forms/ADFS authentication, to enable Windows authentication.
//WebOperationContext.Current.OutgoingRequest.Headers.Remove(“X-FORMS_BASED_AUTH_ACCEPTED”);
//WebOperationContext.Current.OutgoingRequest.Headers.Add(“X-FORMS_BASED_AUTH_ACCEPTED”, “f”);
try
{
Guid NewCustomCatID = Guid.NewGuid();
SvcSecurity.SecurityCategoriesDataSet CatDS = new SvcSecurity.SecurityCategoriesDataSet();
SvcSecurity.SecurityCategoriesDataSet.SecurityCategoriesRow NewCatRow = CatDS.SecurityCategories.NewSecurityCategoriesRow();
NewCatRow.WSEC_CAT_UID = NewCustomCatID;
NewCatRow.WSEC_CAT_NAME = “Test Category from Code “;
NewCatRow.WSEC_CAT_DESC = “This is test category 1.”;
CatDS.SecurityCategories.AddSecurityCategoriesRow(NewCatRow);
#region Optional operations which can be performed
////////////////////////Examples OF Other Functions that can be perofrmed (Optional)/////////////////////////////////////
// Add a user to New category.
SvcSecurity.SecurityCategoriesDataSet.UserRelationsRow userRelationsRow = CatDS.UserRelations.NewUserRelationsRow();
userRelationsRow.WSEC_CAT_UID = NewCustomCatID;
// Pass a resource GUID.
Guid existingResUid = new Guid(“xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”);
userRelationsRow.RES_UID = existingResUid;
CatDS.UserRelations.AddUserRelationsRow(userRelationsRow);
// Specify the permissions for the user on new Category.
SvcSecurity.SecurityCategoriesDataSet.UserPermissionsRow userPermRow = CatDS.UserPermissions.NewUserPermissionsRow();
userPermRow.WSEC_CAT_UID = NewCustomCatID;
userPermRow.RES_UID = existingResUid;
userPermRow.WSEC_ALLOW = true;
// Add an object (project or resource) to new category
SvcSecurity.SecurityCategoriesDataSet.SecurityCategoryObjectsRow category2ObjectRow = CatDS.SecurityCategoryObjects.NewSecurityCategoryObjectsRow();
category2ObjectRow.WSEC_CAT_UID = NewCustomCatID;
category2ObjectRow.WSEC_OBJ_TYPE_UID = PSLibrary.PSSecurityObjectType.Project;
category2ObjectRow.WSEC_OBJ_UID = new Guid(“xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”); // Pass Project UID to be added to the category
CatDS.SecurityCategoryObjects.AddSecurityCategoryObjectsRow(category2ObjectRow);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#endregion
SecClient.CreateCategories(CatDS);
}
catch (System.ServiceModel.FaultException fault)
{
string errAttributeName;
string errAttribute;
string errOut;
string errMess = “”.PadRight(30, ‘=’) + “\r\n”
+ “Error details: ” + “\r\n”;
PSLibrary.PSClientError error = GetPSClientError(fault, out errOut);
errMess += errOut;
PSLibrary.PSErrorInfo[] errors = error.GetAllErrors();
PSLibrary.PSErrorInfo thisError;
for (int i = 0; i < errors.Length; i++)
{
thisError = errors[i];
errMess += “\r\n”.PadRight(30, ‘=’) + “\r\nPSClientError output:\r\n”;
errMess += thisError.ErrId.ToString() + “\n”;
for (int j = 0; j < thisError.ErrorAttributes.Length; j++)
{
errAttributeName = thisError.ErrorAttributeNames()[j];
errAttribute = thisError.ErrorAttributes[j];
errMess += “\r\n\t” + errAttributeName
+ “: ” + errAttribute;
}
}
MessageBox.Show(errMess);
}
}
}
28.635308
77.224960
Read Full Post »