In order to track the problem down, I started adding custom logging to our Synchronizers, starting with logging every method call that was made. What quickly became apparent was that our GetMediaData method on our custom synchronizer was never getting called in production, but it was being called in QA. Using this clue, I used ILSpy to crack open the assembly for MediaFramework to figure out what processor was making this call.
In the MediaSyncItemImport.CreateItem processor, in the Process method, you can see that GetMediaData is called, but only after a successful permissions check for Context.User against the Account Settings item.
Now we are getting somewhere - we are clearly failing a permissions check. But why?
The next round of logging I added was to see which user was running my import job, since the documentation for MediaFramework contains no mention of what user it runs as, or even how to configure that user.
Since I was seeing calls to GetRootItem in production, I added a line of code there to simulate the authentication check that MediaFramework was calling, as well as to log the results. Here is the code for my GetRootItem method:
public override Item GetRootItem(object entity, Item accountItem)
{
Log.Info(string.Format("FolderSynchronizer GetRootItem Called. Looking for root item match: {0} from accountItem {1}", Scene7Constants.RootFolderFolder, accountItem.ID), this);
Item rootItem = accountItem.Children.FirstOrDefault(i => i.Name == Scene7Constants.RootFolderFolder);
Log.Info(string.Format("FolderSynchronizer GetRootItem Result: {0}", (rootItem != null ? rootItem.Name : "null")), this);
bool canCreate = AuthorizationManager.IsAllowed(rootItem, AccessRight.ItemCreate, Context.User);
Log.Info(string.Format("Permissions check for Item:{0}, AccessRight:{1} - {2} for user {3}", rootItem.Uri, AccessRight.ItemCreate, canCreate, Context.User.Name), this);
return rootItem;
}
When running this in dev, we saw that the user was "sitecore\admin", but in production it was "sitecore\anonymous". That explains the permissions problem. But how should we fix this?
You could grant permissions to the anonymous user in production, but that requires editing the raw values on the item because the security UI allow you to assign permissions to this user. In addition, this felt like a sloppy workaround.
To find a better solution to our problem, I wanted to know why we were running as anonymous in production but not QA. This ended up being a simple thing to determine - our security team disabled the admin account and it needed to stay disabled. In fact, we were supposed to have disabled it in QA!
Knowing this, I decided to dig a little further and figure out how we might control the user so we could have an account that had the correct permissions for MediaFramework, but still respected a least privileges configuration.
The answer again came from ILSpy. Specifically, knowing that Agents in Sitecore run as anonymous, there had to be some code in MediaFramework that was allowing it to run as admin.
The answer comes from Sitecore.MediaFramework.Schedulers.ScopeSchedulerBase. This class has two constructors, one of which takes a "database" parameter. But that constructor invokes another and passes it a userName. Here you can see the first constructor:
A little more digging shows that ScopeSchedulerBase.Execute() calls Sitecore.Security.Accounts.UserSwitcher to set the active user to the specified user.
Now that we know how the agent runs as the admin user, we just need to tell MediaFramework to use the other constructor for the scheduler.
The answer for this is not found in the MediaFramework integration guide, but it's as simple as passing another parameter to your agent in the config file. Just below the param that sets the database, you will add a new parameter that specifies the userName. Here is what your config file might look like now.
<scheduling>
<agent name="MediaFramework_Scene7_Import" interval="00:01:00" type="Sitecore.Media.Scene7.Schedulers.Scene7ImportScheduler, Sitecore.Media.Scene7">
<param desc="database">master</param>
<param desc="userName">sitecore\scene7importer</param>
<scopeConfigurations hint="raw:AddConfiguration">
<add ref="mediaFramework/scopeExecuteConfigurations /*[@name='Scene7_Import_Content'][1]"/>
</scopeConfigurations>
</agent>
</scheduling>