I have a SharePoint 2013 auto-hosted app. The app is really simple. It has a list with a remote event receiver on ItemUpdating and ItemUpdated. What I have been trying to do is to assign unique permissions to the item added by the user. I have added this logic in the ItemUpdated event because I run some other business logic in the ItemUpdating event. The code I used is:
public void ProcessOneWayEvent(SPRemoteEventProperties properties) {
if (properties.EventType == SPRemoteEventType.ItemUpdated) {
using (ClientContext clientContext = TokenHelper.CreateRemoteEventReceiverClientContext(properties)) {
if (clientContext != null) {
clientContext.Load(clientContext.Web);
clientContext.ExecuteQuery();
var documentList = ProtocolUtils.GetListById(clientContext, properties.ItemEventProperties.ListId);
var item = ProtocolUtils.GetItemById(clientContext, documentList, properties.ItemEventProperties.ListItemId);
var itemRoleAssignments = item.RoleAssignments;
// Get user info and the groups he belogns to.
var currentUser = ProtocolUtils.GetCurrentUser(clientContext, properties.ItemEventProperties.CurrentUserId);
GroupCollection userGroups = ProtocolUtils.GetCurrentUserGroups(clientContext, currentUser);
var contributorRDF = clientContext.Web.RoleDefinitions.GetByType(RoleType.Contributor);
var administratorRDF = clientContext.Web.RoleDefinitions.GetByType(RoleType.Administrator);
var AdminsGroup = clientContext.Web.SiteGroups.GetByName(Constants.Groups.Admins);
clientContext.ExecuteQuery();
item.BreakRoleInheritance(false, true);
// Set Admins group as the item's ADMINISTRATOR!
RoleDefinitionBindingCollection adminsCollRDB = new RoleDefinitionBindingCollection(clientContext);
adminsCollRDB.Add(administratorRDF);
item.RoleAssignments.Add(AdminsGroup, adminsCollRDB);
//lets add now the group ("departments") the user belongs to as contributor
RoleDefinitionBindingCollection contributorsCollRDB = new RoleDefinitionBindingCollection(clientContext);
contributorsCollRDB.Add(contributorRDF);
foreach (var group in userGroups) {
if (IsGroupValidForRoleAssignment(group.LoginName)) {
item.RoleAssignments.Add(group, contributorsCollRDB);
}
}
clientContext.ExecuteQuery();
}
}
}
}
Now when I add an item to the list using my site collection administrator everything works just fine. When using a simple user with just the basic permissions the query fails with the message “Access denied. You do not have permission to perform this action or access this resource”. Obviously there is a permission issue here.
I started assigning more and more permissions to my app to make it work. Right now the app pretty much has all the major permissions. And more importantly it has the AllowAppOnlyPolicy.
<AppPermissionRequests AllowAppOnlyPolicy="true" >
<AppPermissionRequest Scope="http://sharepoint/content/sitecollection" Right="FullControl" />
<AppPermissionRequest Scope="http://sharepoint/content/sitecollection/web" Right="FullControl" />
<AppPermissionRequest Scope="http://sharepoint/content/tenant" Right="FullControl" />
So from my understanding my app can now only be installed by a tenant admin and the app calls will run under the app’s context and NOT the user’s that is using the app. Nevertheless this is not working. The query is failing for every simple member. After playing with the permissions for many hours I found out that the above code works if I give to my members the permission “Manage Permissions - Create and change permission levels on the Web site and assign permissions to users and groups.” This permission when give also automatically gives the permission “Enumerate Permissions - Enumerate permissions on the Web site, list, folder, document, or list item.”. But I do not want to use this workaround. My users should not have these permissions as long as my app can use the permissions I granted when I installed it.
So the problem is why the app does not run under the AllowAppOnlyPolicy context? I cannot use RunWithElevatedPrivileges so I need my app to do things the app user does not have permission to do.
Am I missing something?
Thank you
EDIT
Based on the answer the little snippet I needed to make this thing work was this:
SharePointContextToken contextToken = null;
Uri sharepointUrl = new Uri(properties.ItemEventProperties.WebUrl);
string appOnlyAccessToken = string.Empty;
try {
contextToken = TokenHelper.ReadAndValidateContextToken(properties.ContextToken, OperationContext.Current.IncomingMessageHeaders.To.Host);
appOnlyAccessToken = TokenHelper.GetAppOnlyAccessToken(contextToken.TargetPrincipalName, sharepointUrl.Authority, contextToken.Realm).AccessToken;
}
catch (Exception ex) {
//Log something
}
using (ClientContext clientContext = TokenHelper.GetClientContextWithAccessToken(sharepointUrl.ToString(), appOnlyAccessToken)) {
//rest of the code
}