Add Multiple Active Directory Groups to Portal Groups

1695
7
10-09-2015 11:23 AM
Status: Open
stevecater1
Occasional Contributor
Add the functionality to add multiple Active Directory groups to Portal Groups. Currently only one AD group may be added to a Portal group. Unlike ArcGIS Server this makes it very difficult to manage security using Portal groups. In AGS it is possible to add multiple AD groups to a folder directory or service.
7 Comments
ThomasColson

As a workaround, this will add users from multiple groups to a PTL group. Run nightly as a scheduled task. 

import subprocess
import os, sys, string, calendar, datetime, traceback, smtplib
import csv
from os import listdir
from os.path import isfile, join
from arcgis.gis import GIS
import itertools
from collections import Counter
import arcpy
from arcpy import env
# Use this script when you need to add members of multiple AD groups to one or more PTL groups
# All scripts and files should read/write to the same directory
##########################
#Begin variables
ptl ="https://portal.com/portal"
#Coded to work with headless account
# Additional parameters will need to be defined to use enterprise account or token
user = "bigfoot"
passw = "sasquatch!"
workdir = 'C:\PRODUCTION\PTL_GROUPS\IANDM'
# List of PTL Groups that will include members of the multiple AD groups lists in adgroups.txt
ptllist = ['GRSM IandM Map Editors','GRSM IandM Workspace','GRSM Vital Signs Editors','GRSM Vital Signs Workspace','GRSM Wetlands Editors','GRSM Wetlands Workspace','GRSM Sochan Workspace']
# a text file titled "adgroups.txt" containing the name of multiple AD groups (one per line, no delimiters or
# seperators) is required to be in the workdir
# Output file containing UPN of AD members from the groups listed in adgroups.txt
admembers = 'GRSM_IANDM_USERS.csv'
# Define log setting
try:
    d = datetime.datetime.now()
    log = open("C:\PYTHON_LOGS\\iandmLOG."+admembers+".txt","a")
    log.write("----------------------------" + "\n")
    log.write("----------------------------" + "\n")
    log.write("Log: " + str(d) + "\n")
    log.write("\n")
# Start process...
    starttime = datetime.datetime.now()
    log.write("Begin process:\n")
    log.write("     Process started at " + str(starttime) + "\n")
    log.write("\n")


    
    ### Start setting variables
    # Mail Server Settings
    SERVER = "mail.server"
    PORT = "25"
    FROM = "me"
    MAILDOMAIN = '@portal.com'
    # Data Steward getting the email. Needs to be their email address...without @nps.gov at the end
    userList=["the_man"]
    # get a list of usernames from the list of named tuples returned from ListUsers
    userNames = [u for u in userList]
    # take the userNames list and make email addresses by appending the appropriate suffix.
    emailList = [name +  MAILDOMAIN for name in userNames]
    TO = emailList
    # Grab date for the email
    DATE = d
#End variables
##########################
    try:
        os.remove(admembers)# Remove AD names list if it exists
    except OSError:
        pass
    #PS command to get members of ad group(s)
    command = '$groups = Get-Content '+workdir+'\\adgroups.txt ;foreach($Group in $Groups) {Get-ADGroupMember -Id $Group | \
    Where { $_.objectClass -eq "user" }|%{Get-ADUser $_.SamAccountName | select UserPrincipalName}  | Export-CSV '+workdir+'\\'+admembers+' \
    -NoTypeInformation -append}'
    process=subprocess.Popen(['powershell',command],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    out, err = process.communicate()
    print(process.returncode, out, err)
    #Remove any special charactes, including quotes, from AD names list
    input_file = workdir+'\\'+admembers
    chars = '$%^*"_\n' # etc notice the \n (linefeed)
    with open(input_file) as f:
        lines = [x.strip(chars) for x in f]
    with open(input_file,"w") as f:
        f.writelines("{}\n".format(x) for x in lines)
    #Initialize GIS connection to PTL
    gis = GIS(ptl, user, passw)
    #Loop through all PTL groups and do the following:
    for item in ptllist:
        #Get PTL group name
        group = gis.groups.search('title: "'+item+'"', '')
        print(group)
        #Add AD users to PTL group
        readCSV = list(csv.reader(open(workdir+'\\'+admembers)))
        for row in readCSV:
            group[0].add_users(row)
        else:
            print ('done')
        #Get list of all members in PTL group
        members = group[0].get_members()
        members_list = members['users']
        #Get list of authorized members in PTL group
        allowed_list = list(itertools.chain(*readCSV))
        #Figure out who doesn't belong
        c1 = Counter(allowed_list)
        c2 = Counter(members_list)
        diff = list((c2 - c1).elements())
        #Remove unauthorized group members
        for j in diff:
            group[0].remove_users([str(j)])
        # Write nothing to log if success.
    endtime = datetime.datetime.now()
    log.write("     Completed successfully in " 
           + str(endtime - starttime) + "\n")
    log.write("\n")
    log.close()
######################################################################
    #Delete below if not want email on success
    #Define email message if success
##    SUBJECT = "Notification of Successful Update of "+str(ptllist)
##    MSG = "Finished updating:"+str(ptllist)+" at "+ str(DATE)
##    print (MSG)
##    print (emailList)
## 
##    # Send an email notifying steward of successful archive
##    #MESSAGE = "\ From: %s To: %s Subject: %s %s" % (FROM, ", ".join(TO), SUBJECT, MSG)
##    MESSAGE = "Subject: %s\n\n%s" % (SUBJECT, MSG)
##    try:
##            try:
##                print("Connecting to Server...")
##                server = smtplib.SMTP(SERVER,PORT)
##                try:
##                    print("Login...")
##                    try:
##                        print("Sending mail...")
##                        server.sendmail(FROM, TO, MESSAGE)
##                    except Exception as e:
##                        print("Send Error Mail\n" + e.message)
##                except Exception as e:
##                    print("Error Authentication Server: check the credentials \n" + e.message)
##            except Exception as e:
##                print("Error Connecting to Server : check the URL of the server and communications port ( 25 and ' the default ) \n" + e.message)
##     
##            print("Quit.")
##            server.quit()
##     
##    except Exception as e:
##            print (e.message)
#Delete above if not want email on success
######################################################################
# Something with wrong
except:
    
 # Get the traceback object
    tb = sys.exc_info()[2]
    tbinfo = traceback.format_tb(tb)[0]
 # Concatenate information together concerning 
 # the error into a message string
    pymsg = "PYTHON ERRORS:\nTraceback info:\n" + tbinfo + "\nError Info:\n" + str(sys.exc_info()[1])
    msgs = "ArcPy ERRORS:\n" + arcpy.GetMessages(2) + "\n"
# Return python error messages for use in 
# script tool or Python Window
    arcpy.AddError(pymsg)
    arcpy.AddError(msgs)
# Print Python error messages for use in 
# Python / Python Window
    log.write("" + pymsg + "\n")
    log.write("" + msgs + "")
    log.close()
    # Define email message if something went wrong
    SUBJECT = "Notification of Un-Successful Update of "+str(ptllist)
    MSG = "This horrible thing happend:"+ str(DATE)+ "; " +pymsg + "; " + msgs
    print (MSG)
    print (emailList)
 
    # Send an email notifying steward of successful archive
    #MESSAGE = "\ From: %s To: %s Subject: %s %s" % (FROM, ", ".join(TO), SUBJECT, MSG)
    MESSAGE = "Subject: %s\n\n%s" % (SUBJECT, MSG)
    try:
            try:
                print("Connecting to Server...")
                server = smtplib.SMTP(SERVER,PORT)
                try:
                    print("Login...")
                    try:
                        print("Sending mail...")
                        server.sendmail(FROM, TO, MESSAGE)
                    except Exception as e:
                        print("Send Error Mail\n" + e.message)
                except Exception as e:
                    print("Error Authentication Server: check the credentials \n" + e.message)
            except Exception as e:
                print("Error Connecting to Server : check the URL of the server and communications port ( 25 and ' the default ) \n" + e.message)
     
            print("Quit.")
            server.quit()
     
    except Exception as e:
            print (e.message)

    

    

jill_es
Status changed to: Implemented

This functionality is currently present in the software.

by Anonymous User

@jill_es thanks, what version was it added at?

jill_es

@Anonymous User, there's been support for nested enterprise groups for many releases now.  It's present in all supported versions of Enterprise (10.7+).  Our product documentation goes into more detail regarding this: https://enterprise.arcgis.com/en/portal/10.7/administer/windows/create-groups.htm#ESRI_SECTION1_5E3FFFAA1B7E443FBB1E483E070B1979.  Is this what you were referring to or were you wanting to use separate AD groups to create one group in your Enterprise organization?

BillFox

No, this should allow multiple Non-Nested AD groups be added to portal groups

jill_es
Status changed to: Open

@BillFox, thanks for that additional information.  We'll keep this in mind moving forward!

by Anonymous User

We have the same use case as @BillFox, it needs to allow for multiple non-nested AD groups.

We commonly have projects which span multiple parts of the business e.g. environment, engineering and property teams which all need access. They each have an AD group which we would like to be able to add to a Portal group, rather than having to add every user individually. If a user drops out of an AD group or a new user is added to an AD group the Portal group should reflect this and be updated.

There are similar ideas requesting the same functionality:

Groups from multipe active directory groups
Invite extra users to active directory group based groups