I'm trying to wrap my head around object-level access control permissions with Realm Cloud (using query-based sync). What I want, is that one object (say, a Task in my example below), can be viewed by a set of multiple users.
So, I create the task, create a role for it with the name "task:\(task.taskId)", add the current user to that role, create a new permission for that role, and finally add it to the task.
let task = Task()
task.name = "A task!"
try? realm.write {
realm.add(task)
let taskRole = realm.create(PermissionRole.self, value: ["task:\(task.taskId)"])
let permissionUser = realm.object(ofType: PermissionUser.self, forPrimaryKey: SyncUser.current!.identity!)!
taskRole.users.append(permissionUser)
let permission = task.permissions.findOrCreate(forRole: taskRole)
permission.canRead = true
permission.canUpdate = true
permission.canDelete = true
}
The idea being that at a later point another user can be added to the same role, with having access to the Task, and nobody else.
Almost everything is working, except that the user is not actually being added to the role. When I look in Realm Studio, I can see that the task is created, the role is created, the permission is created and it's added to the task, but the role has no users at all.
What am I doing wrong? And are there better examples or docs? I find the official docs to be very sparse.
I'm wondering about how you have permissions set up. Are you sure you have permission to read that PermissionUser, and modify that PermissionRole?
Related
Having following issue:
I denormalised my data structure in my Realtime Firebase Database. As a consequence of that I need to update the username in all other "posts" (in this case recipes) as well as in recipes with different authors. To do that, every time a user creates a comment on a recipe this user also creates a reference to this recipe in his user object.
Now when you change your username, it reads all those references and changes the username on this path like following (Swift):
ref.child("users/profile").child(uid ?? "ERROR").child("comments/DE").observeSingleEvent(of: .value, with: { (recipes) in
// recipe dict is a dictionary of all the references to recipes a user has commented
let recipeDict = recipes.value as! [String:Any]
// now every reference is being used to change the username at given place to the new username with variable "username"
var updateObjects: [String:String] = [:]
for i in recipeDict.keys {
updateObjects["recipes/DE/\(i)/comments/\(uid ?? "ERROR")/username"] = username
}
// Now the changes stored in the "updateObjects" variable will be applied
ref.updateChildValues(updateObjects)
})
Now this works, but the problem with this approach is, that say a recipe gets deleted, the reference doesn't get deleted (would be complicated since security rules don't allow to update another users profile so therefore not their references as well).
So that's not a huge deal for me, but since the 'updateChildValues' function creates the missing objects it is. What ends up happening is, that at the place where the deleted recipe would be, a "new" empty recipe structure without any other nodes than the comment gets created. This crashes my app and is not what I want. How can I stop this from happening?
Can you call a Multi-path update without writing data, when there is no existing structure (recipe was deleted)? Or do I need to remove all comment references and somehow give other users access to the comment references (which is tedious...)?
You could write security rules to reject a write operation to a non-existing comment if the write is just a username. For example, say that another property for each comment is the text, you could do:
"comments": {
"$commentid": {
"$uid": {
"username": {
".write": "data.parent.parent.child('text').exists()"
}
}
}
}
The problem with this is that a multi-path update performs one big check for the security rules across all these updates. So if one node was deleted, the updating of all names will fail. If that is what you want, I'd go for that.
I am getting an error when trying to add AppRoleAssignment for a user:
{"odata.error":{"code":"Request_BadRequest","message":{"lang":"en","value":"One or more properties are invalid."},"date":"2017-10-21T14:49:42","requestId":"3aacf13e-5620-40da-8fd0-fb2d4130f171","values":null}}
When i use an actual ApproleId, everything works fine. However, when i set
AppRoleAssignment.Id = new Guid(); i get the above error;
This does not make sense, because the documentation says that this is allowed
by setting zero GUID and the same has been stressed in other posts on SO.
What am i missing here?
Full code:
AppRoleAssignment appRoleAssignment = new AppRoleAssignment()
{
Id = new Guid(),
ResourceId = Guid.Parse(servicePrincipal.ObjectId),
PrincipalId = Guid.Parse(user.ObjectId),
PrincipalType = "User"
};
user.AppRoleAssignments.Add(appRoleAssignment);
await user.UpdateAsync();
Based on my experience, there are two scenarios we may get this issue.
First, if there is customize roles in the service principal you want to assign the default role.
Second, if we have already assigned the default role to that person before.
Please check whether you are in one of these two scenarios and let me know if you still have the problem about this issue.
A user logs in to the server, current role of the user in a course context is editing teacher. I want to change the role to student, and test if attempt capability exists or not for the user in course context?
To change the role of user to student for a given context, I manually update the role_assignment table's role column from editing teacher role to student. Changes done directly show no effect unless the user logs out and logs in again.
To test the capability of the user I use $USER global variable.
Even after manually changing the role of the user to student $USER is not updated: $USER shows the changes only after user logs out and logs in again.
I think issue is related to browser cache, session.
How can I update $USER directly as soon as I change role?
Code used to change role of user:
$index = $context->depth-1;
$array = explode("/",$context->path);
$context_id = $array[$index];
// get logged in user id
$userId = $USER->id;
$user = $DB->get_record('role_assignments', array('contextid'=>$array[$index], 'userid'=> $userId));
$user->roleid = 5;
$roles = get_user_roles($context, $userId);
$user->timemodified = time();
$DB->update_record('role_assignments', $user, $bulk=false);
I think the code you want looks something a bit more like this:
$coursecontext = $context->get_course_context();
$teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher']);
$studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student']);
role_assign($studentroleid, $USER->id, $coursecontext->id);
role_unassign($teacherroleid, $USER->id, $coursecontext->id);
I wasn't quite sure what you were trying to do by exploding the context path, so I assumed you were trying to get the course context (which I've done above). If you just wanted the id of the current context, then $context->id would have done.
It's not a good idea to hard code roleids, 5 is the student role in most clean installs, but looking it up is more reliable.
Using the proper Moodle functions to assign/unassign roles is much more reliable than manually messing around with database entries (if you want, you can take a look in lib/accesslib.php and see exactly what those functions are doing internally).
I'm working with user properties in Sakai and I wish that through some Tool I'm developing a Teacher can alter some properties according to some criteria. I've selected the function user.upd.any in the Realm Role configuration but I keep getting the following error when I try to change the User:
org.sakaiproject.user.api.UserPermissionException user=d1dbdfee-d247-44e4-b5c2-d3d787c829ca function=user.upd.any resource=/user/115cf182-17b0-4f2c-a8fc-34fcbe98bac7
at org.sakaiproject.user.impl.BaseUserDirectoryService.unlock(BaseUserDirectoryService.java:270)
at org.sakaiproject.user.impl.BaseUserDirectoryService.editUser(BaseUserDirectoryService.java:1085)
This is the code that generates the stacktrace:
UserEdit ue = userDirectoryService.editUser(userId);
ue.getProperties().addProperty(name, value);
userDirectoryService.commitEdit(ue);
The code should have a if(userDirectoryService.allowUpdateUser(userId)){...}, but I will put this just when this permission issue have been troubleshooted (if I put this now, nothing happens: no permission)
Any advice?
Thanks
TL;DR version:
In CQ workflows, is there a difference between what's available to the OR Split compared to the Process Step?
Is it possible to access the /history/ nodes of a workflow instance from within an OR Split?
How?!
The whole story:
I'm working on a workflow in CQ5 / AEM5.6.
In this workflow I have a custom dialog, which stores a couple of properties on the workflow instance.
The path to the property I'm having trouble with is: /workflow/instances/[this instance]/history/[workItem id]/workItem/metaData and I've called the property "reject-or-approve".
The dialog sets the property fine (via a dropdown that lets you set it to "reject" or "approve"), and I can access other properties on this node via a process step (in ecma script) using:
var actionReason;
var history = workflowSession.getHistory(workItem.getWorkflow());
// loop backwards through workItems
// and as soon as we find a Action Reason that is not empty
// store that as 'actionReason' and break.
for (var index = history.size() - 1; index >= 0; index--) {
var previous = history.get(index);
var tempActionReason = previous.getWorkItem().getMetaDataMap().get('action-message');
if ((tempActionReason != '')&&(tempActionReason != null)) {
actionReason = tempActionReason;
break;
}
}
The process step is not the problem though. Where I'm having trouble is when I try to do the same thing from inside an OR Split.
When I try the same workflowSession.getHistory(workItem.getWorkflow()) in an OR Split, it throws an error saying workItem is not defined.
I've tried storing this property on the payload instead (i.e. storing it under the page's jcr:content), and in that case the property does seem to be available to the OR Split, but my problems with that are:
This reject-or-approve property is only relevant to the current workflow instance, so storing it on the page's jcr:content doesn't really make sense. jcr:content properties will persist after the workflow is closed, and will be accessible to future workflow instances. I could work around this (i.e. don't let workflows do anything based on the property unless I'm sure this instance has written to the property already), but this doesn't feel right and is probably error-prone.
For some reason, when running through the custom dialog in my workflow, only the Admin user group seems to be able to write to the jcr:content property. When I use the dialog as any other user group (which I need to do for this workflow design), the dialog looks as though it's working, but never actually writes to the jcr:content property.
So for a couple of different reasons I'd rather keep this property local to the workflow instance instead of storing it on the page's jcr:content -- however, if anyone can think of a reason why my dialog isn't setting the property on the jcr:content when I use any group other than admin, that would give me a workaround even if it's not exactly the solution I'm looking for.
Thanks in advance if anyone can help! I know this is kind of obscure, but I've been stuck on it for ages.
a couple of days ago i ran into the same issue. The issue here is that you don't have the workItem object, because you don't really have an existing workItem. Imagine the following: you are going through the workflow, you got a couple of workItems, with means, either process step, either inbox item. When you are in an or split, you don't have existing workItems, you can ensure by visiting the /workItems node of the workflow instance. Your workaround seems to be the only way to go through this "issue".
I've solved it. It's not all that elegant looking, but it seems to be a pretty solid solution.
Here's some background:
Dialogs seem to reliably let you store properties either on:
the payload's jcr:content node (which wasn't practical for me, because the payload is locked during the workflow, and doesn't let non-admins write to its jcr:content)
the workItem/metaData for the current workflow step
However, Split steps don't have access to workItem. I found a fairly un-helpful confirmation of that here: http://blogs.adobe.com/dmcmahon/2013/03/26/cq5-failure-running-script-etcworkflowscriptscaworkitem-ecma-referenceerror-workitem-is-not-defined/
So basically the issue was, the Dialog step could store the property, but the OR Split couldn't access it.
My workaround was to add a Process step straight after the Dialog in my workflow. Process steps do have access to workItem, so they can read the property set by the Dialog. I never particularly wanted to store this data on the payload's jcr:content, so I looked for another location. It turns out the workflow metaData (at the top level of the workflow instance node, rather than workItem/metaData, which is inside the /history sub-node) is accessible to both the Process step and the OR Split. So, my Process step now reads the workItem's approveReject property (set by the Dialog), and then writes it to the workflow's metaData node. Then, the OR Split reads the property from its new location, and does its magic.
The way you access the workflow metaData from the Process step and the OR Split is not consistent, but you can get there from both.
Here's some code: (complete with comments. You're welcome)
In the dialog where you choose to approve or reject, the name of the field is set to rejectApprove. There's no ./ or anything before it. This tells it to store the property on the workItem/metaData node for the current workflow step under /history/.
Straight after the dialog, a Process step runs this:
var rejectApprove;
var history = workflowSession.getHistory(workItem.getWorkflow());
// loop backwards through workItems
// and as soon as we find a rejectApprove that is not empty
// store that as 'rejectApprove' and break.
for (var index = history.size() - 1; index >= 0; index--) {
var previous = history.get(index);
var tempRejectApprove = previous.getWorkItem().getMetaDataMap().get('rejectApprove');
if ((tempRejectApprove != '')&&(tempRejectApprove != null)) {
rejectApprove = tempRejectApprove;
break;
}
}
// steps up from the workflow step into the workflow metaData,
// and stores the rejectApprove property there
// (where it can be accessed by an OR Split)
workItem.getWorkflowData().getMetaData().put('rejectApprove', rejectApprove);
Then after the Process step, the OR Split has the following in its tabs:
function check() {
var match = 'approve';
if (workflowData.getMetaData().get('rejectApprove') == match) {
return true;
} else {
return false;
}
}
Note: use this for the tab for the "approve" path, then copy it and replace var match = 'approve' with var match = 'reject'
So the key here is that from a Process step:
workItem.getWorkflowData().getMetaData().put('rejectApprove', rejectApprove);
writes to the same property that:
workflowData.getMetaData().get('rejectApprove') reads from when you execute it in an OR Split.
To suit our business requirements, there's more to the workflow I've implemented than just this, but the method above seems to be a pretty reliable way to get values that are entered in a dialog, and access them from within an OR Split.
It seems pretty silly that the OR Split can't access the workItem directly, and I'd be interested to know if there's a less roundabout way of doing this, but for now this has solved my problem.
I really hope someone else has this same problem, and finds this useful, because it took me waaay to long to figure out, to only apply it once!