Access Denied on O365

Recently while working on a utility, I faced a very unusual error. I was trying to remove default documents from document set using managed client object model (Default Documents) and it was giving me classic “Access denied. You do not have permission to perform this action or access this resource” error, even though my account had site collection admin as well as tenant admin rights. Besides that, I was doing other operations on the same document set and it was working perfectly fine. One thing to notice was, this error was present only in root site collection and not in another site collections.

After searching a lot, I realized that this is happening due to a certain setting at tenant level and that was “Custom Script”. So if you navigate to SharePoint admin center and click on Settings, there is a section for custom script which says “Prevent users from running custom script on self-service created sites”. This is enabled by default and you need to “Allow users to run custom script on self-service created sites” for this to work. Also note that this change may take up to 24 hours to take effect.

CustomScript

I am not sure about the reason behind this behavior but hope this helps someone.

Besides access denied issue, other issues which I faced if custom script is turned off for root site collection are:

  1. I was not able to open root site collection in SP Designer and it was giving me “Forbidden” error. The complete error was “you do not have permission to open this web site in sharepoint designer
  2. Content Editor and Script Editor web parts were not available in the root site collection.

    Once this feature was turned on, both of the issues were solved.

Happy coding!

CSOM – Check If My Site Exists

There are times when we want to know whether My Site exist for a particular user. In general, My Site is created when a user accesses the My Site (i.e. the “About Me” link) for the first time. For more details on My Site architecture check this msdn link.

Using CSOM, we can fetch a particular user’s my site link. The code for that is:

using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.UserProfiles;
using (ClientContext clientContext = new ClientContext("https://tenant.sharepoint.com/"))
{
SecureString passWord = new SecureString();
foreach (char c in "your_password".ToCharArray()) passWord.AppendChar(c);
clientContext.Credentials = new SharePointOnlineCredentials("your_email_id", passWord);
//This is the login name or account name of user whose my site url is needed
string loginName = "i:0#.f|membership|email_id_of_user_whose_my_site_is_needed";
PeopleManager peopleManager = new PeopleManager(clientContext);
PersonProperties properties = peopleManager.GetPropertiesFor(loginName);
clientContext.Load(properties, p => p.PersonalUrl, p => p.UserProfileProperties);
clientContext.ExecuteQuery();
Console.WriteLine("My Site Url is: " + properties.PersonalUrl);
Console.ReadLine();
}

view raw
GetMySiteUrl.cs
hosted with ❤ by GitHub

The above code returns my site url of the user but if my site does not exist then also it returns the my site host url of that user. So, in this case the url returned is something like:

https://tenant-my.sharepoint.com/Person.aspx?accountname=accountnameofuser

This does not tell us whether my site actually exist or not unless we do some url manipulation. But there is a peroperty in the same code which returns my site relative url only if it exists and that property is PersonalSpace. This is present in UserProfileProperties of PeopleManager class.

The code to fetch that is :

using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.UserProfiles;
using (ClientContext clientContext = new ClientContext("https://tenant.sharepoint.com/"))
{
SecureString passWord = new SecureString();
foreach (char c in "your_password".ToCharArray()) passWord.AppendChar(c);
clientContext.Credentials = new SharePointOnlineCredentials("your_email_id", passWord);
//This is the login name or account name of user whose my site url is needed
string loginName = "i:0#.f|membership|email_id_of_user_whose_my_site_is_needed";
PeopleManager peopleManager = new PeopleManager(clientContext);
PersonProperties properties = peopleManager.GetPropertiesFor(loginName);
clientContext.Load(properties, p => p.PersonalUrl, p => p.UserProfileProperties);
clientContext.ExecuteQuery();
Console.WriteLine("My Site Url is: " + properties.PersonalUrl);
//code to get personal space property
foreach (var property in properties.UserProfileProperties)
{
if (property.Key.ToString().ToLower().Equals("personalspace"))
{
Console.WriteLine("Perosnal Space: " + property.Value);
}
}
Console.ReadLine();
}

view raw
checkmysite.cs
hosted with ❤ by GitHub

This will return blank if my site does not exist else will return /personal/email_of_user

P.S. The code above could also be used in apps, just give Read permission to User Profile.

Happy Coding!!

Provider Hosted App – Upload Large Files using CSOM

We all at some time struggled with uploading large files using CSOM in O365. Using managed client object model, we can upload a maximum of 2MB file size. If the file is of larger size, we usually had two options:

  1. File.SaveBinaryDirect
  2. Using REST which supports upto 2GB

The first option can not be used in Sharepoint Online because SaveBinaryDirect does not work with claims authentication (check this link).

Update: We can now use SaveBinaryDirect in SP Online by using SharePointOnlineCredentials class which could be used for authentication of context but I am yet not able to make it work in provider hosted add-in. It works in normal console app by providing credentials though.

Does that mean we are left with REST only? Actually NO. We have one more way by which we can upload files greater than 2MB in O365 using CSOM in provider hosted app. But let’s first see the standard CSOM way which allows us 2MB file upload.

var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext, SPHostUrl);
using (var clientContext = spContext.CreateUserClientContextForSPAppWeb())
{
if (clientContext != null)
{
//code for file size < 2MB
//file is the input which we uploaded. It can be taken from Request.Files
FileCreationInformation newFile = new FileCreationInformation();
newFile.Content = System.IO.File.ReadAllBytes(file.FileName);
newFile.Url = Path.GetFileName(file.FileName);
List docs = clientContext.Web.Lists.GetByTitle("List_Name");
Microsoft.SharePoint.Client.File uploadFile = docs.RootFolder.Files.Add(newFile);
clientContext.Load(uploadFile);
clientContext.ExecuteQuery();
}
}

view raw
uploadfile.cs
hosted with ❤ by GitHub

Now the method to upload larger files is exactly the same. We only have to use FileCreationInformation.ContentStream property instead of FileCreationInformation.Content property. The whole code is present below:

var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext, SPHostUrl);
using (var clientContext = spContext.CreateUserClientContextForSPAppWeb())
{
if (clientContext != null)
{
//file is uploaded file. Could be read from Request.Files
using (FileStream fs = new FileStream(file.FileName, FileMode.Open))
{
//code for file size > 2MB. tested upto 50MB
FileCreationInformation newFile = new FileCreationInformation();
newFile.ContentStream = fs;
newFile.Url = Path.GetFileName(file.FileName);
newFile.Overwrite = true;
List docs = clientContext.Web.Lists.GetByTitle("List_Name");
Microsoft.SharePoint.Client.File uploadFile = docs.RootFolder.Files.Add(newFile);
clientContext.Load(uploadFile);
clientContext.ExecuteQuery();
}
}
}

view raw
uploadfilelarge.cs
hosted with ❤ by GitHub

In some cases, you might get a FileNotFoundException while using above code. Below is another way to use the code

var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext, SPHostUrl);
using (var clientContext = spContext.CreateUserClientContextForSPAppWeb())
{
if (clientContext != null)
{
FileCreationInformation newFile = new FileCreationInformation();
//here file is the selected file. In this case of type HttpPostedFileBase
newFile.ContentStream = file.InputStream;
newFile.Url = Path.GetFileName(file.FileName);
newFile.Overwrite = true;
List docs = clientContext.Web.Lists.GetByTitle("List_Name");
Microsoft.SharePoint.Client.File uploadFile = docs.RootFolder.Files.Add(newFile);
clientContext.Load(uploadFile);
clientContext.ExecuteQuery();
}
}

view raw
uploadfilestream.cs
hosted with ❤ by GitHub

Note that I replaced FileStream with the input stream present in the uploaded file itself.

P.S. I have tested the above code for files upto 50 MB. It depends largely on internet speed. Once for 50MB file size it gave me time out error (which could be fixed by setting RequestTimeout property of client context)

Hope this helps.

Reference:

  1. O365 AMS