How to assign alfresco activiti workflow task to intersection of two or more groups? - workflow

I'm developing alfresco activiti workflow and i want to assign a task to users who simultaneously are in two specific groups. Separating groups by comma in activiti:candidateGroups gives the union of groups. Is it even possible to resolve this problem?

First you need to setup some service task / or some execution listener before getting to your user task to assign to your set of users, and there you should be using this and this to fetch your groups and their underlying users.
var group1 = people.getGroup("GROUP_DUMMY");
var g1Users = [];
if(group1){
g1Users = g1Users.concat(people.getMembers(group1));
}
var group2 = people.getGroup("GROUP_SAMPLE");
var g2Users = [];
if(group2){
g2Users = g2Users.concat(people.getMembers(group2));
}
Once you do so, you should setup a new array to contain only users that belong to both groups, but instead of putting the user nodes, you should be putting user.properties.userName instead.
You must have a String array with each and every value representing a username !
And finally export that array to your execution like this execution.setVariable('scwf_candidates', users); setup your user task like this:
<userTask id="..." name="My Task" activiti:candidateUsers="${scwf_candidates}">
</userTask>

Related

Copy users from a series of groups based on a filter to another series of groups based on a separate filter

Thanks for taking a minute to look at this.
Scenario: Copy users from a series of groups based on a filter to another series of groups based on a separate filter. Essentially a one for one copy of group memberships.
I created some code that grabs all the groups based on the filter and can get all of the users but am having a hard time translating that into the individual pieces. For each group I want just that groups members and then add them to a group with the same name but of a different type (one is OKTA_GROUP and the other is APP_GROUP).
Any help on this would be appreciated. Getting the groups and members works but putting those into variables and passing them into the PUT is not working.
Here is what I have so far.
function get-oktaInvokeGroupMembers () {
$groups = Invoke-Method GET "/api/v1/groups?filter=type+eq+%22APP_GROUP%22&q=test"
Write-Output $groups
foreach ($group in $groups) {
$members = Get-OktaGroupMember $group.id
Write-Output $members
}
$oktagroups = Invoke-Method GET "/api/v1/groups?filter=type+eq+%22OKTA_GROUP%22&q=Test"
Write-Output $oktagroups
foreach ($okta in $oktagroups) {
Invoke-Method PUT "/api/v1/groups/$okta.id/users/$members.id"
}
}
You have to get your APP_GROUP.
Then for each group, you loop to get the members with Get-OktaGroupMember. Each member has normally an id.
You need to retrieve the OKTA_GROUP which has the same name than the group you are currently processing to get its group id.
Then finally for each member, you put the member.
Something like that will be a good starting point.
function get-oktaInvokeGroupMembers ($groupName) {
$sourceGroups = Invoke-Method GET "/api/v1/groups?filter=type+eq+%22APP_GROUP%22&q=$groupName"
foreach ($sourceGroup in $sourceGroups) {
$sourceMembers = Get-OktaGroupMember $sourceGroup.id
$targetGroups = Invoke-Method GET "/api/v1/groups?filter=type+eq+%22OKTA_GROUP%22&q=$groupName"
foreach($targetGroup in $targetGroups) {
foreach($sourceMember in $sourceMembers) {
Invoke-Method PUT "/api/v1/groups/$($targetGroup.id)/users/$($sourceMember.id)"
}
}
}
}
I've defined a parameter $groupName.
I don't know your API but difficulty is that for a specific groupName, you may get zero, one or many groups as result.
So you need to handle all these cases. I've handled multiple results nesting foreach loop but in such case for one APP_GROUP, the script will set members on all OKTA_GROUP that have the same name than APP_GROUP. There may be zero, one or many OKTA_GROUP groups returned by the API.
Another point, you will probably need to handle errors when adding a member. Calling the API with PUT when the member is already in the OKTA_GROUP may return an error.
And last, the script will only append members to a group, but it will never "synchronize" both groups. Meaning deleting members isn't handled. If you need to synchronize members this is bit more complexe.

python 3.7 and ldap3 reading group membership

I am using Python 3.7 and ldap3. I can make a connection and retrieve a list of the groups in which I am interested. I am having trouble getting group members though.
server = Server('ldaps.ad.company.com', use_ssl=True, get_info=ALL)
with Connection(server, 'mydomain\\ldapUser', '******', auto_bind=True) as conn:
base = "OU=AccountGroups,OU=UsersAndGroups,OU=WidgetDepartment," \
+ "OU=LocalLocation,DC=ad,DC=company,DC=com"
criteria = """(
&(objectClass=group)
(
|(sAMAccountName=grp-*widgets*)
(sAMAccountName=grp-oldWidgets)
)
)"""
attributes = ['sAMAccountName', 'distinguishedName']
conn.search(base, criteria, attributes=attributes)
groups = conn.entries
At this point groups contains all the groups I want. I want to itterate over the groups to collect the members.
for group in groups:
# print(cn)
criteria = f"""
(&
(objectClass=person)
(memberof:1.2.840.113556.1.4.1941:={group.distinguishedName})
)
"""
# criteria = f"""
# (&
# (objectClass=person)
# (memberof={group.distinguishedName})
# )
# """
attributes = ['displayName', 'sAMAccountName', 'mail']
conn.search(base, criteria, attributes=attributes)
people = conn.entries
I know there are people in the groups but people is always an empty list. It doesn't matter if I do a recirsive search or not.
What am I missing?
Edit
There is a longer backstory to this question that is too long to go into. I have a theory about this particular issue though. I was running out of time and switched to a different python LDAP library -- which is working. I think the issue with this question might be that I "formated" the query over multiple lines. The new ldap lib (python-ldap) complained and I stripped out the newlines and it just worked. I have not had time to go back and test that theory with ldap3.
people is overwritten in each iteration of your loop over groups.
Maybe the search result for the last group entry in groups is just empty.
You should initialise an empty list outside of your loop and extend it with your results:
people = []
for group in groups:
...
conn.search(...)
people.extend(conn.entries)
Another note about your code snippet above. When combining objectClass definitions with attribute definitions in your search filter you may consider using the Reader class which will combine those internally.
Furthermore I would like to point out that I've created an object relational mapper where you can simply define your queries using declarative python syntax, e.g.:
from ldap3_orm import ObjectDef, Reader
from ldap3_orm.config import config
from ldap3_orm.connection import conn
PersonDef = ObjectDef("person", conn)
r = Reader(conn, PersonDef, config.base_dn, PersonDef.memberof == group.distinguishedName)
r.search()
ldap3-orm documentation can be found at http://code.bsm-felder.de/doc/ldap3-orm

Activiti, pass form field control value to script task variable (vice-versa)

I've created a workflow utilizing the script task (exam_result as integer, exam_grade as integer, remarks as string) job. In the user task, i created a form reference (with textfield) that will handle the data entry of exam result.
The exam result text field should be passed to the exam_result variable which will be evaluated thru decision table task.
What is the best practice to send data from form field control to script task variable or vice versa?
Your help is highly appreciated, thank you.
Store these values into execution variable and you should be able to access them across all the tasks.
You can use task listeners to read the form values and store them into execution variables like below.
execution.setVariable('exam_result',task.getVariable('exam_result'));
execution.setVariable('exam_grade',task.getVariable('exam_grade'));
execution.setVariable('remarks',task.getVariable('remarks'));
If you want to access them in another task listeners,
var examResult = execution.getVariable('exam_result');
var examgrade = execution.getVariable('exam_grade');
var remarks= execution.getVariable('remarks');
This is on the delegates side, you can access them like below.
int examResult = (int) executionVariables.get("exam_result");
int examGrade = (int) executionVariables.get("exam_grade");
string remarks = (string) executionVariables.get("remarks");
Hope this helps you.
Please let me know if it not clear to you.

Can I start a Service Now workflow via an external SOAP call?

I would like to make a call into the ServiceNow SOAP webservice to start an instance of a specific web service.
I can find the WSDL for functions like incident.do but seem to be missing the step needed to find the proper table/endpoint for workflows to start.
If you want to start a Workflow via SOAP I think the only way to do this is to create a Scripted Web-Service or a Custom Processor.
In there you will have to define a script which starts your Workflow.
var w = new Workflow();
var context = w.startFlow(id, current, current.operation(), getVars());
In this wiki article you can find API Methods for Workflows.
The tricky bit is getting the variables into the Workflow.
While this sounds easy, in fact it isn't.
If your workflow runs on the table sc_req_item (which is likely if you are dealing with Request Fulfillment), you first need to set the Property (sys_properties) glide.workflow.enable_input_variables to true, because otherwise, you will not be able to add normal Input variables to your workflow.
Then, add the Input variables to the workflow. Note that you have some nifty datatypes available there. Note for example the "Data Structure" type.
All Input variables are treated like custome columns (in fact they are columns of a workflw-specific table). That is why the names start with u_.
Lets say, you define an input variable called u_dynamic_vars (Datatype "Data Structure").
Here is how to call the workflow:
var wf_name = "Name of your workflow";
// Instantiate JSON machinery
var parser = new JSON();
//Declare an instance of workflow.js
var wf = new Workflow ();
//Get the workflow id
var wfId = wf.getWorkflowFromName (wf_name) ;
//Start workflow, passing along object containing name/value pairs mapping to inputs expected by the workflow
var vars = { } ;
// Prepare the JSON Datastructure
var obj ={"name":"George",
"lastname":"Washington"};
// Encode the data
vars.u_dynamic_vars = parser.encode(obj);
vars.u_new_email = "inject#new.com";
// Get a specific RITM
var gr = GlideRecord("sc_req_item");
gr.get("18d8e9740f4013002f504c6be1050e48");
gs.print(gr.number);
// Start the Workflow with a "current" record
wf.startFlow(wfId , gr , "update" , vars ) ;
// You may also pass null, then current is null.
wf.startFlow(wfId , null , "update" , vars ) ;
In the workflow, you then unpack the data like so:
// Let's unpack it. For some reason, intantiating the parse won't work here...
payload = JSON.parse(workflow.variables.u_dynamic_vars);
gs.print("payload.first_name:" + payload.name);
Also note that a workflow does not necessarily need to run on a table.
To achieve this, choose "global" as table name when defining the workflow.

Tastypie relationship fields on obj_get_list

I have a very standard user/group many to many relationship with the following simplified definition:
class GroupResource(ModelResource):
users = fields.ToManyField(UserResource, 'group_set')
class Meta:
cache = SimpleCache()
queryset = Group.objects.all()
resource_name = "hr/group"
When I get a list groups it returns the group along with the list of uris of users connected to the group. However, our production system is starting to get quite large and a group can have hundreds of users in it. It is becoming painstakingly slow to return a list of groups, since each group also returns with it a list of users connected to that group.
Is there a way to exclude the linked users only for obj_get_list, but include the users when viewing the group instance? Or is there another solution to this problem that will fit better?
You need to specify a callable as use_in argument to fields.ToManyField which returns False if current request corresponds to get_list end point.
Example code
def is_not_group_get_list_end_point(bundle):
# TODO: use dynamically generated path here.
if bundle.request.get_full_path() == '/api/v1/hr/group/':
return False
return True
class GroupResource(ModelResource):
users = fields.ToManyField(UserResource, 'group_set',
use_in=is_not_group_get_list_end_point)
class Meta:
cache = SimpleCache()
queryset = Group.objects.all()
resource_name = "hr/group"