Browse Source

Initial commit of code

master
Gerry Nelson 7 years ago
committed by GitHub
parent
commit
aef1c56df2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 111
      ContributorAgreement.txt
  2. 32
      call_rest_api.py
  3. 76
      callrestapi.py
  4. 131
      createbinarybackup.py
  5. 97
      createdomain.py
  6. 93
      createfolders.py
  7. 79
      deletefolder.py
  8. 103
      deletefolderandcontent.py
  9. 293
      explainaccess.py
  10. 52
      getconfigurationproperties.py
  11. 47
      getfolderid.py
  12. 59
      getruleid.py
  13. 72
      listrules.py
  14. 59
      loginviauthinfo.py
  15. 99
      movecontent.py
  16. 521
      sharedfunctions.py
  17. 65
      showsetup.py
  18. 114
      testfolderaccess.py
  19. 139
      unittestsadm33.sh
  20. 181
      unittestsadm34.sh
  21. 108
      updatedomain.py
  22. 99
      updatepreferences.py

111
ContributorAgreement.txt

@ -0,0 +1,111 @@
Contributor Agreement
Version 1.1
Contributions to this software are accepted only when they are
properly accompanied by a Contributor Agreement. The Contributor
Agreement for this software is the Developer's Certificate of Origin
1.1 (DCO) as provided with and required for accepting contributions
to the Linux kernel.
In each contribution proposed to be included in this software, the
developer must include a "sign-off" that denotes consent to the
terms of the Developer's Certificate of Origin. The sign-off is
a line of text in the description that accompanies the change,
certifying that you have the right to provide the contribution
to be included. For changes provided in source code control (for
example, via a Git pull request) the sign-off must be included in
the commit message in source code control. For changes provided
in email or issue tracking, the sign-off must be included in the
email or the issue, and the sign-off will be incorporated into the
permanent commit message if the contribution is accepted into the
official source code.
If you can certify the below:
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
then you just add a line saying
Signed-off-by: Random J Developer <random@developer.example.org>
using your real name (sorry, no pseudonyms or anonymous contributions.)

32
call_rest_api.py

@ -0,0 +1,32 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# call_rest_api.py
#
# Includes callrestapi.py, providing backward compatibility with previous version of this tool
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
#print(os.path.dirname(os.path.realpath(__file__)))
scriptdir=os.path.dirname(os.path.realpath(__file__))
def include(filename):
if os.path.exists(filename):
execfile(filename)
include(os.path.join(scriptdir,'callrestapi.py'))

76
callrestapi.py

@ -0,0 +1,76 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# callrestapi.py
# December 2017
#
# Based on the items passed in the utility calls the rest api and return results
#
# Change History
#
# 27JAN2017 Comments added
# 29JAN2017 Added choices to validate method input
# 31JAN2017 Added contenttype parameters
# 02FEB2018 Added simple text print flag
# 01JUN2018 Renamed from call_rest_api.py to callrestapi.py
# 08JUN2018 Print json instead of pprint of easier result parsing
# 01JUN2018 Renamed from call_rest_api.py to callrestapi.py
# 08JUN2018 Print json instead of pprint of easier result parsing
# 08OCT2018 make printed json pretty
# 26OCT2018 call print function
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
from sharedfunctions import callrestapi,getinputjson,printresult
# get command line parameters
parser = argparse.ArgumentParser(description="Call the Viya REST API")
parser.add_argument("-e","--endpoint", help="Enter the REST endpoint e.g. /folders/folders ",required='True')
parser.add_argument("-m","--method", help="Enter the REST method.",default="get",required='True',choices=['get','put','post','delete'])
parser.add_argument("-i","--inputfile",help="Enter the full path to an input json file",default=None)
parser.add_argument("-a","--accepttype",help="Enter REST Content Type you want returned e.g application/vnd.sas.identity.basic+json",default="application/json")
parser.add_argument("-c","--contenttype",help="Enter REST Content Type for POST e.g application/vnd.sas.identity.basic+json",default="application/json")
parser.add_argument("-o","--output", help="Output Style", choices=['csv','json','simple'],default='json')
parser.add_argument("-t","--text", help="Display Simple Text Results.", action='store_true')
args = parser.parse_args()
reqval=args.endpoint
reqtype=args.method
reqfile=args.inputfile
reqcontent=args.contenttype
reqaccept=args.accepttype
simpletext=args.text
output_style=args.output
# keep for backward compatibility
if simpletext: output_style='simple'
# use the callrestapi function to make a call to the endpoint
# call passing json or not
if reqfile != None:
inputdata=getinputjson(reqfile)
result=callrestapi(reqval,reqtype,reqaccept,reqcontent,data=inputdata)
else:
result=callrestapi(reqval,reqtype,reqaccept,reqcontent)
#print the result
printresult(result,output_style)

131
createbinarybackup.py

@ -0,0 +1,131 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# createbinarybackup.py
# February 2018
#
# Usage:
# python createbinarybackup.py [-q] [-d]
#
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# CHANGE THIS VARIABLE IF YOUR CLI IS IN A DIFFERENT LOCATION
clidir='/opt/sas/viya/home/bin/'
debug=False
defaultBackupScheduleName="DEFAULT_BACKUP_SCHEDULE"
newScheduleName="BINARY_BACKUP_SCHEDULE"
newScheduleDesc="JobRequest to execute a binary backup"
jobDefinitionURIStem="/jobDefinitions/definitions/"
newScheduleContentType="application/vnd.sas.backup.request+json" # For a single-tenant deployment
#newScheduleContentType="application/vnd.sas.backup.deployment.request+json" # For a multi-tenant deployment
# Import Python modules
import argparse
import json
import sys
from sharedfunctions import callrestapi
# Define exception handler so that we only output trace info from errors when in debug mode
def exception_handler(exception_type, exception, traceback, debug_hook=sys.excepthook):
if debug:
debug_hook(exception_type, exception, traceback)
else:
print "%s: %s" % (exception_type.__name__, exception)
sys.excepthook = exception_handler
parser = argparse.ArgumentParser()
#parser.add_argument("-t","--principaltype", help="Enter the type of principal to test: user or group.",required='True',choices=['user','group'])
parser.add_argument("-q","--quiet", action='store_true')
parser.add_argument("-d","--debug", action='store_true')
args = parser.parse_args()
#principaltype=args.principaltype
quiet=args.quiet
debug=args.debug
# STEP 1 of 4: Get the jobDefinition of the existing DEFAULT_BACKUP_SCHEDULE
endpoint='/jobDefinitions/definitions?limit=20&filter=in(name,"'+defaultBackupScheduleName+'")'
method='get'
accept='application/json'
jobDefinition_json=callrestapi(endpoint,method,accept)
if debug:
print('jobDefinition_json:')
print(jobDefinition_json)
jobDefinitions=jobDefinition_json['items']
id_found=False
jobDefinitionId=''
for jobDefinition in jobDefinitions:
if jobDefinition['name']:
if(jobDefinition['name']==defaultBackupScheduleName):
jobDefinitionId=jobDefinition['id']
print('Id: '+jobDefinitionId)
id_found=True
if not id_found:
raise Exception('Unable to determine Id for '+defaultBackupScheduleName+'.')
# STEP 2 of 4: Create a jobExecution request
endpoint='/jobExecution/jobRequests'
method='post'
accept='application/vnd.sas.job.execution.job.request+json'
content='application/vnd.sas.job.execution.job.request+json'
inputdata={
"name": newScheduleName,
"description": newScheduleDesc,
"jobDefinitionUri": jobDefinitionURIStem+jobDefinitionId,
"arguments": {
"contentType": newScheduleContentType,
"backupType": "binary"
}
}
jobExecutionRequest_json=callrestapi(endpoint,method,accept,content,inputdata)
if debug:
print('jobExecutionRequest_json:')
print(jobExecutionRequest_json)
# STEP 3 of 4: Get the href to submit the job from the create jobExecution response
links=jobExecutionRequest_json['links']
href_found=False
submitJobHref=''
for link in links:
if link['rel']:
if(link['rel']=="submitJob"):
submitJobHref=link['href']
print('Href: '+submitJobHref)
href_found=True
if not href_found:
raise Exception('Unable to find the href for the submitJob link.')
# STEP 4 of 4: Submit the jobExecution request
endpoint=submitJobHref
method='post'
accept='application/vnd.sas.job.execution.job+json'
submitJob_json=callrestapi(endpoint,method,accept)
#if debug:
print('submitJob_json:')
print(submitJob_json)

97
createdomain.py

@ -0,0 +1,97 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# createdomain.py
# December 2017
#
# create a viya domain
#
# Change History
#
# 27JAN2017 Comments added
# 27JAN2017 Added the ability to create connection domains
# 29JAN2017 Added choices to validate type of domain
# 29SEP2018 make group list comma seperated
#
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Create a domain
import base64
import argparse
from sharedfunctions import callrestapi
parser = argparse.ArgumentParser(description="Create a Viya Domain")
parser.add_argument("-d","--domain", help="Enter the domain name.",required=True)
parser.add_argument("-u","--user", help="User ID for the domain.",required=True)
parser.add_argument("-p","--password", help="Password for the userid.",required=False)
parser.add_argument("-g","--groups", help="A list of groups to add to the domain. Groupid comma seperated",required=True)
parser.add_argument("-c","--desc", help="Description of the domain.",required=False)
parser.add_argument("-t","--type", help="Type of the domain: password or connection (passwordless).",required=True, choices=['password','connection'])
args = parser.parse_args()
domain_name=args.domain
userid=args.user
pwval=args.password
groups=args.groups
desc=args.desc
type=args.type
# create a python list with the groups
grouplist=groups.split(",")
# encode the password
if pwval:
cred=base64.b64encode(pwval.encode("utf-8")).decode("utf-8")
# build the rest call
reqval="/credentials/domains/"+domain_name
reqtype="put"
# build the json parameters
data = {}
data['id'] = domain_name
data['description'] = desc
data['type'] = type
# create the domain
callrestapi(reqval,reqtype,data=data)
# for each group passed in add their credentials to the domain
for group_id in grouplist:
print("Adding "+ group_id + " to domain " + domain_name)
reqval="/credentials/domains/"+domain_name+"/groups/"+group_id
reqtype="put"
data = {}
data['domainId'] = domain_name
data['domainType'] = type
data['identityId'] = group_id
data['identityType'] = 'group'
data['properties']={"userId": userid}
if pwval:
data['secrets']={"password": cred}
print(data)
callrestapi(reqval,reqtype,data=data)

93
createfolders.py

@ -0,0 +1,93 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# createfolders.py
# October 2018
#
#
# Change History
#
# sasgnn 30oct2018 Initial development
#
# Format of csv file is two columns
# Column 1 is the full path to the folder
# Column 2 is a description
#
# For example:
#/RnD, Folder under root for R&D
#/RnD/reports, reports
#/RnD/analysis, analysis
#/RnD/data plans, data plans
#/temp,My temporary folder
#
# Copyright 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
import csv
import os
from sharedfunctions import callrestapi, getfolderid, file_accessible
# setup command-line arguements
parser = argparse.ArgumentParser(description="Create folders that are read from a csv file")
parser.add_argument("-f","--file", help="Full path to csv file containing folders, format of csv: 'folderpath,description ",required='True')
args = parser.parse_args()
file=args.file
reqtype="post"
check=file_accessible(file,'r')
# file can be read
if check:
with open(file, 'rt') as f:
filecontents = csv.reader(f)
for row in filecontents:
#print(row)
newfolder=row[0]
description=row[1]
if newfolder[0]!='/': newfolder="/"+newfolder
folder=os.path.basename(os.path.normpath(newfolder))
parent_folder=os.path.dirname(newfolder)
data = {}
data['name'] = folder
data['description'] = description
print ("Creating folder "+newfolder )
if parent_folder=="/": reqval='/folders/folders'
else: # parent folder create a child
parentinfo=getfolderid(parent_folder)
if parentinfo != None:
parenturi=parentinfo[1]
reqval='/folders/folders?parentFolderUri='+parenturi
else: print("Parent folder not found")
myresult=callrestapi(reqval,reqtype,data=data,stoponerror=0)
else:
print("ERROR: cannot read "+file)

79
deletefolder.py

@ -0,0 +1,79 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# deletefolder.py
# December 2017
#
# Pass in a folder path and delete the folder, its sub-folders
#
# Change History
#
# 27JAN2018 Comments added
# 03Feb2018 Added quiet mode
# 03Mar2018 Made prompt comparison case-insensitive
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Import Python modules
import argparse, sys
from sharedfunctions import getfolderid, callrestapi
# get python version
version=int(str(sys.version_info[0]))
# get input parameters
parser = argparse.ArgumentParser(description="Delete a folder and its sub-folders")
parser.add_argument("-f","--folderpath", help="Enter the path to the viya folder.",required='True')
parser.add_argument("-q","--quiet", help="Suppress the are you sure prompt.", action='store_true')
args = parser.parse_args()
print(args.folderpath)
path_to_folder=args.folderpath
quietmode=args.quiet
# call getfolderid to get the folder id
targets=getfolderid(path_to_folder)
# if folder is found
if targets[0] is not None:
uri=targets[1]
# if the user passed in the quiet key do not prompt are you sure
if quietmode:
areyousure="Y"
else:
# deal with python 2 v python 3 prompts
if version > 2:
areyousure=input("Are you sure you want to delete the folder and its contents? (Y)")
else:
areyousure=raw_input("Are you sure you want to delete the folder and its contents? (Y)")
# delete the folder recursively
if areyousure.upper() =='Y':
print("Deleting folder= "+ path_to_folder+" "+uri)
reqval=uri+"?recursive=true"
reqtype='delete'
callrestapi(reqval,reqtype)
print('Folder Deleted.')
else:
print("Good thing I asked!")

103
deletefolderandcontent.py

@ -0,0 +1,103 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# deletefolderandcontent.py
# february 2018
#
# Pass in a folder path and delete the folder, its sub-folders and content
#
# Change History
#
# 27JAN2018 Comments added
# 03Feb2018 Added quiet mode
# based on delete folder, but in addition deletes reports.
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Import Python modules
import argparse, sys
from sharedfunctions import getfolderid, callrestapi
# get python version
version=int(str(sys.version_info[0]))
# get input parameters
parser = argparse.ArgumentParser(description="Delete a folder and its sub-folders and contents")
parser.add_argument("-f","--folderpath", help="Enter the path to the viya folder.",required='True')
parser.add_argument("-q","--quiet", help="Suppress the are you sure prompt.", action='store_true')
args = parser.parse_args()
print(args.folderpath)
path_to_folder=args.folderpath
quietmode=args.quiet
# call getfolderid to get the folder id
targets=getfolderid(path_to_folder)
# if the folder is found
if targets[0] is not None:
uri=targets[1]
# if quiet do not prompt
if quietmode:
areyousure="Y"
else:
if version > 2:
areyousure=input("Are you sure you want to delete the folder and its contents? (Y)")
else:
areyousure=raw_input("Are you sure you want to delete the folder and its contents? (Y)")
if areyousure.upper() == 'Y':
#delete folder content, recursive call returns all children
reqval=uri+"/members?recursive=true"
reqtype='get'
allchildren=callrestapi(reqval,reqtype)
# get all child items
if 'items' in allchildren:
itemlist=allchildren['items']
for children in itemlist:
#if it is a report
if children['contentType']=='report':
linklist=children['links']
for linkval in linklist:
#find the delete method and call it
if linkval['rel']=='deleteResource':
reqval=(linkval['uri'])
reqtype=(linkval['method']).lower()
callrestapi(reqval,reqtype)
print("Deleting folder= "+ path_to_folder+" "+uri)
reqval=uri+"?recursive=true"
reqtype='delete'
callrestapi(reqval,reqtype)
print('Folder Deleted.')
else:
print("Good thing I asked!")

293
explainaccess.py

@ -0,0 +1,293 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# explainaccess.py
# November 2018
#
# Usage:
# explainaccess.py [-f folderpath | -u objectURI] [-n name_of_user_or_group -t user|group] [-p] [--header] [--direct_only] [-l permissions_list] [-c true|false] [-d]
#
# Examples:
#
# 1. Explain direct and indirect permissions on the folder /folderA/folderB, no header row. For folders, conveyed permissions are shown by default.
# ./explainaccess.py -f /folderA/folderB
#
# 2. As 1. but for a specific user named Heather
# ./explainaccess.py -f /folderA/folderB -n Heather -t user
#
# 3. As 1. with a header row
# ./explainaccess.py -f /folderA/folderB --header
#
# 4. As 1. with a header row and the folder path, which is useful if you concatenate sets of results in one file
# ./explainaccess.py -f /folderA/folderB -p --header
#
# 5. As 1. showing only rows which include a direct grant or prohibit
# ./explainaccess.py -f /folderA/folderB --direct_only
#
# 6. Explain direct and indirect permissions on a service endpoint. Note in the results that there are no conveyed permissions.
# By default they are not shown for URIs.
# ./explainaccess.py -u /SASEnvironmentManager/dashboard
#
# 7. As 6. but including a header row and the create permission, which is relevant for services but not for folders and other objects
# ./explainaccess.py -u /SASEnvironmentManager/dashboard --header -l read update delete secure add remove create
#
# 8. Explain direct and indirect permissions on a report, reducing the permissions reported to just read, update, delete and secure,
# since none of add, remove or create are applicable to a report.
# ./explainaccess.py -u /reports/reports/e2e0e601-b5a9-4601-829a-c5137f7441c6 --header -l read update delete secure
#
# 9. Explain direct and indirect permissions on a folder expressed as a URI. Keep the default permissions list, but for completeness
# we must also specify -c true to request conveyed permissions be displayed, as they are not displayed by default for URIs.
# ./explainaccess.py -u /folders/folders/9145d26a-2c0d-4523-8835-ad186bb57fa6 --header -p -c true
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
clidir='/opt/sas/viya/home/bin/'
debug=False
direct_only=False
valid_permissions=['read','update','delete','secure','add','remove','create']
default_permissions=['read','update','delete','secure','add','remove']
#direct_permission_suffix=u"\u2666" #Black diamond suit symbol - ok in stdout, seems to cause problems with other tools
direct_permission_suffix='*'
# Import Python modules
import argparse
import subprocess
import json
import sys
from sharedfunctions import getfolderid,callrestapi
# Define exception handler so that we only output trace info from errors when in debug mode
def exception_handler(exception_type, exception, traceback, debug_hook=sys.excepthook):
if debug:
debug_hook(exception_type, exception, traceback)
else:
print "%s: %s" % (exception_type.__name__, exception)
sys.excepthook = exception_handler
parser = argparse.ArgumentParser()
parser.add_argument("-f","--folderpath", help="Path to a Viya folder. You must specify either -f folderpath or -u objectURI.")
parser.add_argument("-u","--objecturi", help="Object URI. You must specify either -f folderpath or -u objectURI.")
parser.add_argument("-n","--name", help="Enter the name of the user or group to test.")
parser.add_argument("-t","--principaltype", help="Enter the type of principal to test: user or group.",choices=['user','group'])
parser.add_argument("-p","--printpath", action='store_true', help="Print the folder path in each row")
parser.add_argument("--header", action='store_true', help="Print a header row")
parser.add_argument("--direct_only", action='store_true', help="Show only explanations which include a direct grant or prohibit")
parser.add_argument("-l","--permissions_list", nargs="+", help="List of permissions, to include instead of all seven by default", default=default_permissions)
parser.add_argument("-c","--convey", help="Show conveyed permissions in results. True by default when folder path is specified. False by dfefault if Object URI is specified.",choices=['true','false'])
parser.add_argument("-d","--debug", action='store_true', help="Debug")
args = parser.parse_args()
path_to_folder=args.folderpath
objecturi=args.objecturi
name=args.name
principaltype=args.principaltype
printpath=args.printpath
header=args.header
direct_only=args.direct_only
permissions=args.permissions_list
conveyparam=args.convey
debug=args.debug
if path_to_folder and objecturi:
raise Exception('You must specify either -f and a Viya folder path, or -u and an object URI, but not both.')
if path_to_folder is None and objecturi is None:
raise Exception('You must specify either -f and a Viya folder path, or -u and an object URI. You may not specify both.')
if name and principaltype is None:
raise Exception('If you specify a principal name, you must also specify a principal type which can be user or group.')
if principaltype and name is None:
raise Exception('If you specify a principal type, you must also specify a principal name.')
for permission in permissions:
if permission not in valid_permissions:
raise Exception(permission+' is not the name of a permission. Valid permissions are: '+' '.join(map(str, valid_permissions)))
# Two ways this program can be used: for a folder, or for a URI.
if path_to_folder:
getfolderid_result_json=getfolderid(path_to_folder)
if (debug):
print(getfolderid_result_json)
if getfolderid_result_json[0] is not None:
folder_uri=getfolderid_result_json[1]
if (debug):
print("Id = "+getfolderid_result_json[0])
print("URI = "+folder_uri)
print("Path = "+getfolderid_result_json[2])
explainuri=folder_uri
resultpath=path_to_folder
#Set convey to true, unless user overrode that setting and asked for false
if(conveyparam is not None and conveyparam.lower()=='false'):
convey=False
else:
convey=True
else:
explainuri=objecturi
# This tool explains the permissions of any object.
# If the object is a folder, we expect the user to supply path_to_folder, and we find its ID
# If the object is something else, we don't have the path to the object.
# It might be possible to get the path to the object from it's ID, but I'm not sure if there is a universal way to do that.
# If the object is a report, you can call e.g.
# /opt/sas/viya/home/bin/sas-admin --output text reports show-info -id 43de1f98-d7ef-4490-bb46-cc177f995052
# And the folder is one of the results passed back. But that call uses the reports plug-in to sas-admin and
# should not be expected to return the path to other objects.
# Plus, some objects do not have a path: service endpoints, for example.
# This is a possible area for future improvement.
resultpath=objecturi
#Set convey to false, unless user overrode that setting and asked for true
if(conveyparam is not None and conveyparam.lower()=='true'):
convey=True
else:
convey=False
#Use the /authorization/decision endpoint to ask for an explanation of the rules that are relevant to principals on this URI
#See Authorization API documentation in swagger at http://swagger.na.sas.com/apis/authorization/v4/apidoc.html#op:createExplanation
endpoint='/authorization/decision'
if name and principaltype:
if(principaltype.lower()=='user'):
endpoint=endpoint+'?additionalUser='+name
else:
endpoint=endpoint+'?additionalGroup='+name
method='post'
accept='application/vnd.sas.authorization.explanations+json'
content='application/vnd.sas.selection+json'
inputdata={"resources":[explainuri]}
decisions_result_json=callrestapi(endpoint,method,accept,content,inputdata)
#print(decisions_result_json)
#print('decisions_result_json is a '+type(decisions_result_json).__name__+' object') #decisions_result_json is a dict object
e = decisions_result_json['explanations'][explainuri]
#print('e is a '+type(e).__name__+' object') #e is a list object
# Print header row if header argument was specified
if header:
if printpath:
if convey:
print('path,principal,'+','.join(map(str, permissions))+','+','.join(map('{0}(convey)'.format, permissions)))
else:
print('path,principal,'+','.join(map(str, permissions)))
else:
if convey:
print('principal,'+','.join(map(str, permissions))+','+','.join(map('{0}(convey)'.format, permissions)))
else:
print('principal,'+','.join(map(str, permissions)))
principal_found=False
#For each principle's section in the explanations section of the data returned from the REST API call...
for pi in e:
#print pi['principal']
#We are starting a new principal, so initialise some variables for this principal
outstr=''
has_a_direct_grant_or_deny=False
if printpath:
outstr=outstr+resultpath+','
# If a name and principaltype are provided as arguments, we will only output a row for that principal
if name and principaltype:
if 'name' in pi['principal']:
if (pi['principal']['name'].lower() == name.lower()):
principal_found=True
outstr=outstr+pi['principal']['name']
# Permissions on object
for permission in permissions:
# Not all objects have all the permissions
# Note that some objects do have permissions which are not meaningful for that object.
# E.g. SASAdministrators are granted Add and Remove on reports, by an OOTB rule which grants SASAdministrators all permissions (including Add and Remove) on /**.
# Meanwhile, Add and Remove are not shown in the View or Edit Authotizations dialogs for reports in EV etc.
# So, while it may be correct for the /authorization/decisions endpoint to explain that SASAdministrators are granted Add and Remove on a report,
# that does not alter the fact that in the context of a report, Add and Remove permissions are not meaningful.
if pi[permission.lower()]:
# This permission was in the expanation for this principal
outstr=outstr+','+pi[permission.lower()]['result']
if 'grantFactor' in pi[permission.lower()]:
if 'direct' in pi[permission.lower()]['grantFactor']:
if pi[permission.lower()]['grantFactor']['direct']:
has_a_direct_grant_or_deny=True
outstr=outstr+direct_permission_suffix
else:
# This permission was absent from the expanation for this principal
outstr=outstr+','
# Conveyed permissions
if convey:
for permission in permissions:
# Only a few objects have conveyed permissions at all
if 'conveyedExplanation' in pi[permission.lower()]:
# This permission was in the expanation for this principal
outstr=outstr+','+pi[permission.lower()]['conveyedExplanation']['result']
if 'grantFactor' in pi[permission.lower()]['conveyedExplanation']:
if 'direct' in pi[permission.lower()]['conveyedExplanation']['grantFactor']:
if pi[permission.lower()]['conveyedExplanation']['grantFactor']['direct']:
has_a_direct_grant_or_deny=True
outstr=outstr+direct_permission_suffix
else:
# This permission was absent from the expanation for this principal
outstr=outstr+','
if direct_only:
if has_a_direct_grant_or_deny:
print(outstr)
else:
print(outstr)
# But if no name or principaltype are provided, we output all rows
else:
if 'name' in pi['principal']:
outstr=outstr+pi['principal']['name']
else:
outstr=outstr+pi['principal']['type']
# Permissions on object
for permission in permissions:
# Not all objects have all the permissions
if pi[permission.lower()]:
# This permission was in the expanation for this principal
outstr=outstr+','+pi[permission.lower()]['result']
if 'grantFactor' in pi[permission.lower()]:
if 'direct' in pi[permission.lower()]['grantFactor']:
if pi[permission.lower()]['grantFactor']['direct']:
has_a_direct_grant_or_deny=True
outstr=outstr+direct_permission_suffix
else:
# This permission was absent from the expanation for this principal
outstr=outstr+','
# Conveyed permissions
if convey:
for permission in permissions:
# Not all objects have all the permissions
if 'conveyedExplanation' in pi[permission.lower()]:
# This permission was in the expanation for this principal
outstr=outstr+','+pi[permission.lower()]['conveyedExplanation']['result']
if 'grantFactor' in pi[permission.lower()]['conveyedExplanation']:
if 'direct' in pi[permission.lower()]['conveyedExplanation']['grantFactor']:
if pi[permission.lower()]['conveyedExplanation']['grantFactor']['direct']:
has_a_direct_grant_or_deny=True
outstr=outstr+direct_permission_suffix
else:
# This permission was absent from the expanation for this principal
outstr=outstr+','
if direct_only:
if has_a_direct_grant_or_deny:
print(outstr)
else:
print(outstr)

52
getconfigurationproperties.py

@ -0,0 +1,52 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# getconfigurationproperties.py
# December 2017
#
# pass in the coniguration definition and return the properties
#
# Change History
#
# 27JAN2017 Comments added
#
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Import Python modules
from __future__ import print_function
import argparse
import pprint
pp = pprint.PrettyPrinter(indent=4)
from sharedfunctions import callrestapi, printresult
parser = argparse.ArgumentParser(description="Return a set of configuration properties")
parser.add_argument("-c","--configuration", help="Enter the configuration definition.",required='True')
parser.add_argument("-o","--output", help="Output Style", choices=['csv','json','simple'],default='json')
args = parser.parse_args()
configurationdef=args.configuration
output_style=args.output
reqval="/configuration/configurations?definitionName="+configurationdef
configvalues=callrestapi(reqval,'get')
printresult(configvalues,output_style)

47
getfolderid.py

@ -0,0 +1,47 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# getfolder.py
# December 2017
#
# getfolderid is a wrapper which sets up the command line arguements and then calls the getfolderid function
# the function returns a folderid and uri when passed the path to the folder in Viya
#
# Change History
#
# 27JAN2017 Comments added
#
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
from sharedfunctions import getfolderid
# setup command-line arguements
parser = argparse.ArgumentParser()
parser.add_argument("-f","--folderpath", help="Enter the path to the viya folder.",required='True')
args = parser.parse_args()
path_to_folder=args.folderpath
# call the get folderid function and pass it the entered path
targets=getfolderid(path_to_folder)
#print results if any are returned
if targets[0] is not None:
print("Id = "+targets[0])
print("URI = "+targets[1])
print("Path = "+targets[2])

59
getruleid.py

@ -0,0 +1,59 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# getruleid.py
# December 2017
#
# getruleid pass in a uri and identity and return the rule id
#
# Change History
#
# 27JAN2017 Comments added
# 18JUN2018 Output JSON
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
from sharedfunctions import callrestapi, printresult
# setup command-line arguements
parser = argparse.ArgumentParser()
parser.add_argument("-u","--objecturi", help="Enter the objecturi.",required='True')
parser.add_argument("-p","--principal", help="Enter the identity name or authenticatedUsers, everyone or guest")
parser.add_argument("-o","--output", help="Output Style", choices=['csv','json','simple'],default='json')
args = parser.parse_args()
objuri=args.objecturi
ident=args.principal
output_style=args.output
if ident.lower()=='authenticatedusers': ident='authenticatedUsers'
if ident=='guest' or ident=='everyone' or ident=='authenticatedUsers':
reqval= "/authorization/rules?filter=and(eq(principalType,'"+ident+"'),eq(objectUri,'"+objuri+"'))"
else:
reqval= "/authorization/rules?filter=and(eq(principal,'"+ident+"'),eq(objectUri,'"+objuri+"'))"
reqtype='get'
result=callrestapi(reqval,reqtype)
#print("ruleid= "+result['items'][0]['id'])
# print rest call results
printresult(result,output_style)

72
listrules.py

@ -0,0 +1,72 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# listrules.py
# August 2018
#
# listrulesforidentity
#
# Change History
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
from sharedfunctions import callrestapi, printresult
# setup command-line arguements
parser = argparse.ArgumentParser(description="List rules for a principal and/or an endpoint")
parser.add_argument("-u","--uri", help="Enter a string that the objecturi contains.",default="none")
parser.add_argument("-p","--principal", help="Enter the identity name or authenticatedUsers, everyone or guest",default='none')
parser.add_argument("-o","--output", help="Output Style", choices=['csv','json','simple'],default='json')
args = parser.parse_args()
objuri=args.uri
ident=args.principal
output_style=args.output
# set the limit high so that all data is returned
limitval=10000
# build the request depending on what options were passed in
if ident.lower()=='authenticatedusers': ident='authenticatedUsers'
if ident=='none' and objuri=='none': reqval= "/authorization/rules"
elif ident=='none' and objuri != 'none': reqval= "/authorization/rules?filter=contains(objectUri,'"+objuri+"')"
elif ident!='none' and objuri == 'none':
if ident=='guest' or ident=='everyone' or ident=='authenticatedUsers':
reqval= "/authorization/rules?filter=eq(principalType,'"+ident+"')"
else:
reqval= "/authorization/rules?filter=eq(principal,'"+ident+"')"
elif ident!='none' and objuri != 'none':
if ident=='guest' or ident=='everyone' or ident=='authenticatedUsers':
reqval= "/authorization/rules?filter=and(eq(principalType,'"+ident+"'),contains(objectUri,'"+objuri+"'))"
else:
reqval= "/authorization/rules?filter=and(eq(principal,'"+ident+"'),contains(objectUri,'"+objuri+"'))"
if ident=='none' and objuri=='none': reqval=reqval+'?limit='+str(limitval)
else: reqval=reqval+'&limit='+str(limitval)
reqtype='get'
#make the rest call
result=callrestapi(reqval,reqtype)
#print the result
printresult(result,output_style)

59
loginviauthinfo.py

@ -0,0 +1,59 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# IMPORTANT: calls the sas-admin cli change the variable below if your CLI is not
# installed in the default location
#
# usage python loginviauthinfo.py
#
# Change History
#
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from __future__ import print_function
import netrc
import subprocess
import platform
import os
import argparse
# CHANGE THIS VARIABLE IF YOUR CLI IS IN A DIFFERENT LOCATION
clidir='/opt/sas/viya/home/bin/'
#clidir='c:\\admincli\\'
# get input parameters
parser = argparse.ArgumentParser(description="Authinfo File")
parser.add_argument("-f","--file", help="Enter the path to the authinfo file.",default='.authinfo')
args = parser.parse_args()
authfile=args.file
host=platform.node()
# Read from the authinfo file in your home directory
fname=os.path.join(os.path.expanduser('~'),authfile)
cur_profile=os.environ.get("SAS_CLI_PROFILE","Default")
print("Logging in with profile: ",cur_profile )
if os.path.isfile(fname):
secrets = netrc.netrc(fname)
username, account, password = secrets.authenticators( host )
command=clidir+'sas-admin --profile '+cur_profile+ ' auth login -u '+username+ ' -p '+password
subprocess.call(command, shell=True)
else:
print('ERROR: '+fname+' does not exist')

99
movecontent.py

@ -0,0 +1,99 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# movecontent.py
# December 2017
#
# Moves content from one folder to another
#
# Change History
#
# 27JAN2018 Comments added
# 03Feb2018 Added quiet mode
# 03Mar2018 Made prompt comparison case-insensitive
#
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Import Python modules
import argparse, sys
from sharedfunctions import getfolderid, callrestapi
# get python version
version=int(str(sys.version_info[0]))
parser = argparse.ArgumentParser(description="Move content from a source to a target folder")
parser.add_argument("-s","--sourcefolder", help="Enter the path to the source folder.",required='True')
parser.add_argument("-t","--targetfolder", help="Enter the path to the source folder.",required='True')
parser.add_argument("-q","--quiet", help="Suppress the are you sure prompt.", action='store_true')
args = parser.parse_args()
source=args.sourcefolder
target=args.targetfolder
quietmode=args.quiet
sourceinfo=getfolderid(source)
targetinfo=getfolderid(target)
if sourceinfo[0] is not None:
id=sourceinfo[0]
if quietmode:
areyousure="Y"
else:
if version > 2:
areyousure=input("Are you sure you want to move content from "+source+" to "+target+"? (Y)")
else:
areyousure=raw_input("Are you sure you want to move content from "+source+" to "+target+"? (Y)")
if areyousure.upper() == 'Y':
# get all the content in folder
reqtype='get'
reqval='/folders/folders/'+id+"/members"
members=callrestapi(reqval,reqtype)
# create a list of items
items=members["items"]
for item in items:
# delete from folder
reqtype="delete"
reqval='/folders/folders/'+id+"/members/"+item["id"]
rc=callrestapi(reqval,reqtype)
#build dictionary of item
thisitem={"uri":item["uri"],"name":item["name"],"type":item["type"],"contentType":item["contentType"]}
#add to new folder
reqtype="post"
reqval="/folders/folders/"+targetinfo[0]+"/members"
rc=callrestapi(reqval,reqtype,data=thisitem)
print("NOTE: content moved between folder "+source+" and "+target)
else:
print("Good thing I asked!")

521
sharedfunctions.py

@ -0,0 +1,521 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# sharedfunctions.py
# December 2017
#
# A set of shared functions used by the piviyatool which makes REST calls to supplement the VIYA CLI
#
# callrestapi is the core function it will accept a method endpoint and optionally a python dictionary as input
# getfolderid returns a folder id if it is passed the path to the viya folder
# getebaseuri returns the base url for the service from the default profile
# getauthtoken returns the authentication token created by the CLI call sas-admin auth login
# getinputjson converts the input json to a python dictionary
#
# Change History
#
# 27JAN2018 Comments added
# 29JAN2018 Added simpleresults function
# 31JAN2018 Added the ability to pass contenttype to call_rest_api (now callrestapi)
# 31JAN2018 Improved error handling of call_rest_api (now callrestapi)
# 31JAN2018 Deal with situation where json is not returned
# 31JAN2018 Fix a bug when neither json or text is returned
# 02FEB2018 Fix a bug when text is returned
# 12MAR2018 Made simple result print generic
# 20MAR2018 Added some comments
# 20MAR2018 Handle errors when profile and authentication token do not exist
# 20May2018 Fixed bug in authentication check
# 01jun2018 Deal with empty profile error
# 23oct2018 Added print result function
# 23oct2018 Added print csv
# 28oct2018 Added stop on error to be able to override stopping processing when an error occurs
# 20nov2018 Updated so that multiple profiles can be used
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
# Import Python modules
from __future__ import print_function
import requests
import sys
import json
import pprint
import os
pp = pprint.PrettyPrinter(indent=4)
# validate rest api is not used at this time
# not used
def validaterestapi(baseurl, reqval, reqtype, data={}):
global result
print("The request is a "+reqtype+" request: ",baseurl+reqval)
json_data=json.dumps(data, ensure_ascii=False)
print("Data for Request:")
print(json_data)
if (reqtype !="get" or reqtype !="post" or reqtype!="delete" or reqtype!="put"):
print("NOTE: Invalid method")
return;
# callrestapi
# this is the main function called many other programs and by the callrestapi program to make the REST calls
# change history
# 01dec2017 initial development
# 28oct2018 Added stop on error to be able to override stopping processing when an error occurs
def callrestapi(reqval, reqtype, acceptType='application/json', contentType='application/json',data={},stoponerror=1):
# get the url from the default profile
baseurl=getbaseurl()
# get the auth token
oaval=getauthtoken(baseurl)
# build the authorization header
head= {'Content-type':contentType,'Accept':acceptType}
head.update({"Authorization" : oaval})
# maybe this can be removed
global result
# sereliaze the data string for the request to json format
json_data=json.dumps(data, ensure_ascii=False)
# call the rest api using the parameters passed in and the requests python library
if reqtype=="get":
ret = requests.get(baseurl+reqval,headers=head,data=json_data)
elif reqtype=="post":
ret = requests.post(baseurl+reqval,headers=head,data=json_data)
elif reqtype=="delete":
ret = requests.delete(baseurl+reqval,headers=head,data=json_data)
elif reqtype=="put":
ret = requests.put(baseurl+reqval,headers=head,data=json_data)
else:
result=None
print("NOTE: Invalid method")
sys.exit()
# response error if status code between these numbers
if (400 <= ret.status_code <=599):
print(ret.text)
result=None
if stoponerror: sys.exit()
# return the result
else:
# is it json
try:
result=ret.json()
except:
# is it text
try:
result=ret.text
except:
result=None
print("NOTE: No result to print")
return result;
# getfolderid
# when a Viya content path is passed in return the id, path and uri
# change history
# 01dec2017 initial development
def getfolderid(path):
# build the request parameters
reqval="/folders/folders/@item?path="+path
reqtype='get'
callrestapi(reqval,reqtype)
if result==None:
print("NOTE: Folder'"+path+"' not found.")
targetid=None
targetname=None
targeturi=None
else:
targetid=result['id']
targetname=result['name']
targeturi="/folders/folders/"+targetid
return [targetid,targeturi,targetname]
# getbaseurl
# from the default profile return the baseurl of the Viya server
# change history
# 01dec2017 initial development
# 01jun2018 Deal with empty profile error
# 20nov2018 Use the SAS_CLI_PROFILE env variable
def getbaseurl():
# check that profile file is available and can be read
# note the path to the profile is hard-coded right now
endpointfile=os.path.join(os.path.expanduser('~'),'.sas','config.json')
access_file=file_accessible(endpointfile,'r')
#profile does not exist
if access_file==False:
print("ERROR: Cannot read CLI profile at:",endpointfile,". Recreate profile with sas-admin profile init.")
sys.exit()
#profile is empty file
if os.stat(endpointfile).st_size==0:
print("ERROR: Cannot read CLI profile empty file at:",endpointfile,". Recreate profile with sas-admin profile init.")
sys.exit()
# get json from profile
with open(endpointfile) as json_file:
data = json.load(json_file)
# get the profile environment variable to use it
# if it is not set default to the default profile
cur_profile=os.environ.get("SAS_CLI_PROFILE","Default")
#print("URL: ",cur_profile )
# check that information is in profile
if cur_profile in data:
baseurl=data[cur_profile]['sas-endpoint']
else:
baseurl=None
print("ERROR: profile "+cur_profile+" does not exist. Recreate profile with sas-admin profile init.")
sys.exit()
return baseurl
# getauthtoken
# from the stored auth file get the authentication token for the request header
# change history
# 01dec2017 initial development
# return oaval=None when no authtoken retrieved
# 20nov2018 Use the SAS_CLI_PROFILE env variable
def getauthtoken(baseurl):
#get authentication information for the header
credential_file=os.path.join(os.path.expanduser('~'),'.sas','credentials.json')
# check that credential file is available and can be read
access_file=file_accessible(credential_file,'r')
if access_file==False:
oaval=None
print("ERROR: Cannot read authentication credentials at: ", credential_file)
print("ERROR: Try refreshing your token with sas-admin auth login")
sys.exit()
with open(credential_file) as json_file:
data = json.load(json_file)
type(data)
# the sas-admin profile init creates an empty credential file
# check that credential is in file, if it is add it to the header, if not exit
# get the profile environment variable to use it
# if it is not set default to the default profile
cur_profile=os.environ.get("SAS_CLI_PROFILE","Default")
#print("LOGON: ", cur_profile )
if cur_profile in data:
oauthToken=data[cur_profile]['access-token']
oauthTokenType="bearer"
oaval=oauthTokenType + ' ' + oauthToken
head= {'Content-type':'application/json','Accept':'application/json' }
head.update({"Authorization" : oaval})
# test a connection to rest api if it fails exit
r = requests.get(baseurl,headers=head)
if (400 <= r.status_code <=599):
oaval=None
print(r.text)
print("ERROR: cannot connect to "+baseurl+" is your token expired?")
print("ERROR: Try refreshing your token with sas-admin auth login")
sys.exit()
else:
oaval=None
print("ERROR: access token not in file: ", credential_file)
print("ERROR: Try refreshing your token with sas-admin auth login")
sys.exit()
return oaval
# getinputjson
# load the returned json to a python dictionary
# change history
# 01dec2017 initial development
def getinputjson(input_file):
with open(input_file) as json_file:
inputdata = json.load(json_file)
return inputdata
# simpleresults
# take the complex json and create a simple print of the results
# change history
# 01dec2017 initial development
def simpleresults(resultdata):
# print a simplification of the json results
# list of items returned by rest call
if 'items' in resultdata:
total_items=resultdata['count']
returned_items=len(resultdata['items'])
if total_items == 0: print("Note: No items returned.")
for i in range(0,returned_items):
print ("=====Item ",i,"=======")
pairs=resultdata['items'][i]
for key,val in pairs.items():
if key != 'links':
print(key,end="")
print(" = ", val)
print("Result Summary: Total items available: ",total_items ,"Total items returned: ", returned_items)
elif 'id' in resultdata: #one item returned by rest call
for key,val in resultdata.items():
if key != 'links':
print(key,end="")
print(" = ", val)
else:
print("NOTE: No JSON Results Found")
# tableresults
# take the complex json and create a simple table of the results
# change history
# 01aug2018 initial development
def csvresults(resultdata):
#print(resultdata)
if 'items' in resultdata:
total_items=resultdata['count']
returned_items=len(resultdata['items'])
if total_items == 0: print("Note: No items returned.")
for i in range(0,returned_items):
pairs=resultdata['items'][i]
#test=pairs.get('description')
#if test==None: pairs['description']='None'
for key,val in pairs.items():
if i==0: print(key,',',end="")
print("\n",end="")
for key,val in pairs.items():
if key != 'links':
print('"',val,'",',end="")
print("\n",end="")
elif 'id' in resultdata: #one item returned by rest call
for key,val in resultdata.items():
if key != 'links':
print(key,',',end="")
print("\n",end="")
for key,val in resultdata.items():
if key != 'links':
print('"',val,'",',end="")
print("\n",end="")
else:
print("NOTE: No JSON Results Found")
# file_accessible
# Check if a file exists and is accessible.
# change history
# 01dec2017 initial development
def file_accessible(filepath, mode):
try:
f = open(filepath, mode)
f.close()
except IOError as e:
return False
return True
# printresult
# prints the results in the style requested
# change history
# 28oct2018 initial development
def printresult(result,output_style):
# print rest call results
if type(result) is dict:
if output_style=='simple':
simpleresults(result)
elif output_style=='csv':
csvresults(result)
else:
print(json.dumps(result,indent=2))
else: print(result)
# getprofileinfo
# prints the token expiration, endpoint and current user
# change history
# 20nov2018 initial development
def getprofileinfo(myprofile):
#get authentication information for the header
credential_file=os.path.join(os.path.expanduser('~'),'.sas','credentials.json')
# check that credential file is available and can be read
access_file=file_accessible(credential_file,'r')
if access_file==False:
print("ERROR: Cannot read authentication credentials at: ", credential_file)
print("ERROR: Try refreshing your token with sas-admin auth login")
sys.exit()
with open(credential_file) as json_file:
data = json.load(json_file)
type(data)
# the sas-admin profile init creates an empty credential file
# check that credential is in file, if it is add it to the header, if not exit
# get the profile environment variable to use it
# if it is not set default to the default profile
if myprofile in data:
expiry=data[myprofile]['expiry']
print("Note your authentication token expires at: "+expiry)
else:
print("ERROR: access token not in file: ", credential_file)
print("ERROR: Try refreshing your token with sas-admin auth login")
sys.exit()
# note the path to the profile is hard-coded right now
endpointfile=os.path.join(os.path.expanduser('~'),'.sas','config.json')
access_file=file_accessible(endpointfile,'r')
#profile does not exist
if access_file==False:
print("ERROR: Cannot read CLI profile at:",endpointfile,". Recreate profile with sas-admin profile init.")
sys.exit()
#profile is empty file
if os.stat(endpointfile).st_size==0:
print("ERROR: Cannot read CLI profile empty file at:",endpointfile,". Recreate profile with sas-admin profile init.")
sys.exit()
# get json from profile
with open(endpointfile) as json_file:
data = json.load(json_file)
# check that information is in profile
if myprofile in data:
baseurl=data[myprofile]['sas-endpoint']
print("Endpoint is: "+baseurl)
else:
print("ERROR: profile "+myprofile+" does not exist. Recreate profile with sas-admin profile init.")
# build the request parameters
reqval="/identities/users/@currentUser"
reqtype='get'
result=callrestapi(reqval,reqtype)
if result==None:
print("NOTE: Not logged in.")
else:
print("Logged on as id: "+ result['id'])
print("Logged on as name: "+result['name'])

65
showsetup.py

@ -0,0 +1,65 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# showsetup.py
#
# output some system settings to help with debugging issues
#
# October 2018
#
#
# Change History
#
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import sys
import requests
import os
from sharedfunctions import getprofileinfo
# software versions
print("Python Version is: "+str(sys.version_info[0])+'.'+str(sys.version_info[1]))
print("Requests Version is: "+requests.__version__)
# profile
cur_profile=os.environ.get("SAS_CLI_PROFILE","NOTSET")
if cur_profile=="NOTSET":
print("SAS_CLI_PROFILE environment variable not set, using Default profile")
cur_profile='Default'
else:
print("SAS_CLI_PROFILE environment variable set to profile "+ cur_profile)
ssl_file=os.environ.get("SSL_CERT_FILE","NOTSET")
if ssl_file=="NOTSET":
print("SSL_CERT_FILE environment variable not set.")
else:
print("SSL_CERT_FILE environment variable set to profile "+ ssl_file)
r_ssl_file=os.environ.get("REQUESTS_CA_BUNDLE","NOTSET")
if r_ssl_file=="NOTSET":
print("REQUESTS_CA_BUNDLE environment variable not set.")
else:
print("REQUESTS_CA_BUNDLE environment variable set to profile "+ r_ssl_file)
getprofileinfo(cur_profile)

114
testfolderaccess.py

@ -0,0 +1,114 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# testfolderaccess.py
# January 2018
#
# Usage:
# python testfolderaccess.py -f folderpath -n name_of_user_or_group -t user|group -s grant|prohibit -m permission [-q] [-d]
#
#
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# CHANGE THIS VARIABLE IF YOUR CLI IS IN A DIFFERENT LOCATION
clidir='/opt/sas/viya/home/bin/'
debug=False
# Import Python modules
import argparse
import subprocess
import json
import sys
from sharedfunctions import getfolderid,callrestapi
# Define exception handler so that we only output trace info from errors when in debug mode
def exception_handler(exception_type, exception, traceback, debug_hook=sys.excepthook):
if debug:
debug_hook(exception_type, exception, traceback)
else:
print "%s: %s" % (exception_type.__name__, exception)
sys.excepthook = exception_handler
parser = argparse.ArgumentParser()
parser.add_argument("-f","--folderpath", help="Enter the path to the viya folder.",required='True')
parser.add_argument("-n","--name", help="Enter the name of the user or group to test.",required='True')
parser.add_argument("-t","--principaltype", help="Enter the type of principal to test: user or group.",required='True',choices=['user','group'])
parser.add_argument("-s","--setting", help="Enter grant or prohibit as the expected setting for the permission being tested.",required='True', choices=['grant','prohibit'])
parser.add_argument("-m","--permission", help="Enter the permission to test.",required='True', choices=["read","update","delete","add","secure","remove"])
parser.add_argument("-q","--quiet", action='store_true')
parser.add_argument("-d","--debug", action='store_true')
args = parser.parse_args()
path_to_folder=args.folderpath
name=args.name
principaltype=args.principaltype
setting=args.setting
permission=args.permission
quiet=args.quiet
debug=args.debug
getfolderid_result_json=getfolderid(path_to_folder)
if (debug):
print(getfolderid_result_json)
if getfolderid_result_json[0] is not None:
folder_uri=getfolderid_result_json[1]
if (debug):
print("Id = "+getfolderid_result_json[0])
print("URI = "+folder_uri)
print("Path = "+getfolderid_result_json[2])
endpoint='/authorization/decision'
if(principaltype.lower()=='user'):
endpoint=endpoint+'?additionalUser='+name
else:
endpoint=endpoint+'?additionalGroup='+name
method='post'
accept='application/vnd.sas.authorization.explanations+json'
content='application/vnd.sas.selection+json'
inputdata={"resources":[folder_uri]}
decisions_result_json=callrestapi(endpoint,method,accept,content,inputdata)
#print(decisions_result_json)
#print('decisions_result_json is a '+type(decisions_result_json).__name__+' object') #decisions_result_json is a dict object
e = decisions_result_json['explanations'][folder_uri]
#print('e is a '+type(e).__name__+' object') #e is a list object
principal_found=False
for pi in e:
#print pi['principal']
# Test whether principal has a name: authenticatedusers and guest do not have a name key
if 'name' in pi['principal']:
if (pi['principal']['name'].lower() == name.lower()):
#print(pi['principal']['name']+':'+pi[permission.lower()]['result'])
principal_found=True
if (pi[permission.lower()]['result'] == setting.lower()):
if not quiet:
print('TEST PASSED: the effective '+permission.lower()+' permission for '+pi['principal']['name']+' on folder '+path_to_folder+' is '+pi[permission.lower()]['result'])
else:
raise Exception('TEST FAILED: the effective '+permission.lower()+' permission for '+pi['principal']['name']+' on folder '+path_to_folder+' is '+pi[permission.lower()]['result']+', not '+setting.lower())
if not principal_found:
raise Exception('No direct or inherited authorization rules found for \''+name+'\' on folder '+path_to_folder+'. Please check that you spelled the principal name correctly, and specified the correct principal type - user or group.')

139
unittestsadm33.sh

@ -0,0 +1,139 @@
#!/usr/bin/sh
#
# unittestsadm33.sh
# June 2018
#
# Calls each of the pyviyatools at least once, as a simple unit/integration test
#
# Some tests are provided with example folder paths which are not likely to
# exist in your deployment. However, most tests are not dependent on any
# custom content in the deployment, and will run well on any deployment.
#
# Some tests intentionally do things which do not work, e.g. delete a folder
# which does not exist. The error message returned by the tool called is
# considered sufficient to demonstrate that it has in fact been called, and is
# working as intended. If you like, you could create content for these tests
# to act on, e.g. create a folder called "/this_folder_does_not_exist", and
# allow one of the tests below delete it.
#
# The following tests create new content, and do not clean up after themselves:
# 1. "Create a domain using createdomain"
# - creates or replaces domain named 'test', does not create multiple
# copies
# 2. "Create a binary backup job"
# - creates a new scheduled job named 'BINARY_BACKUP_JOB' each time it
# runs, will create multiple copies
# You may wish to clean up after them manually, especially in a
# real customer environment. Study the tests and/or run them individually
# to learn more about what they create, so that you can find and delete it
# yourself. In a dev, PoC, playpen or classroom environment, the cleanup
# might be optional, as the created objects will not interfere with other
# content or work.
#
# Change History
#
# 01Jun2018 Initial version after refactoring tools
# 18oct2018 updated gerrulid test because -o changed to -u
#
#
# Copyright 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
echo "Return all the rest calls that can be made to the folders endpoint"
./callrestapi.py -e /folders -m get
echo
echo "Return the json for all the folders/folders"
./callrestapi.py -e /folders/folders -m get
echo
echo "Return simple text for all the folders/folders"
./callrestapi.py -e /folders/folders -m get -o simple
echo
echo "Rest calls often limit the results returned the text output will tell you returned and total available items"
echo "in this call set a limit above the total items to see everything"
./callrestapi.py -e /folders/folders?limit=500 -m get -o simple
echo
echo "Return the json for all the identities"
./callrestapi.py -e /identities/identities -m get
echo
echo "Return the json for all the identities output to a file"
./callrestapi.py -e /identities/identities -m get > /tmp/identities.json
echo
echo "Contents of /tmp/identities.json:"
cat /tmp/identities.json
echo "End of contents of /tmp/identities.json"
echo
echo "Deleting /tmp/identities.json"
rm /tmp/identities.json
echo "Demonstrating that /tmp/identities.json has been deleted - list it, ls should say no such file or directory:"
ls -al /tmp/identities.json
echo
echo "Refresh the identities cache"
./callrestapi.py -e /identities/userCount -m get
./callrestapi.py -e /identities/cache/refreshes -m post
echo
echo "Pass the folder path and return the folder id and uri"
./getfolderid.py -f /gelcontent
echo
echo "Delete a folder based on its path - we don't want to delete a real folder, so try (and fail) to delete one which does not exist"
./deletefolder.py -f /this_folder_does_not_exist
echo
echo "Delete a folder and its content - we don't want to delete a real folder, so try (and fail) to delete one which does not exist"
./deletefolderandcontent.py -f /this_folder_does_not_exist
echo
echo "Return a set of configuration properties"
./getconfigurationproperties.py -c sas.identities.providers.ldap.user
echo
echo "Create a domain using createdomain"
./createdomain.py -t password -d test -u sasadm -p lnxsas -g "SASAdministrators,HRs,Sales"
echo
echo "Create a binary backup job"
./createbinarybackup.py
echo
echo "Get a rule ID"
#Get /Public folder ID
./getfolderid.py --folderpath /Public > /tmp/folderid.txt
id=$(grep "Id " /tmp/folderid.txt | tr -s ' ' | cut -f3 -d " ")
echo "The Public folder ID is" $id
./getruleid.py -u /folders/folders/$id/** -p authenticatedUsers
echo
echo "Move all content from one folder to another folder (or in this case, the same folder)"
./movecontent.py -s /gelcontent/GELCorp/Shared/Reports -t /gelcontent/GELCorp/Shared/Reports -q
echo
echo "Test folder access"
./testfolderaccess.py -f '/gelcontent/GELCorp' -n gelcorp -t group -m read -s grant
echo
echo "Display all sasadministrator rules"
./listrules.py --p SASadministrators -o simple
echo
echo "Display all rules that contain SASVisual in the URI"
./listrules.py -u SASVisual -o simple
echo

181
unittestsadm34.sh

@ -0,0 +1,181 @@
#!/usr/bin/sh
#
# unittestsadm33.sh
# December 2018
#
# Calls each of the pyviyatools at least once, as a simple unit/integration test
#
# Some tests are provided with example folder paths which are not likely to
# exist in your deployment. However, most tests are not dependent on any
# custom content in the deployment, and will run well on any deployment.
#
# Some tests intentionally do things which do not work, e.g. delete a folder
# which does not exist. The error message returned by the tool called is
# considered sufficient to demonstrate that it has in fact been called, and is
# working as intended. If you like, you could create content for these tests
# to act on, e.g. create a folder called "/this_folder_does_not_exist", and
# allow one of the tests below delete it.
#
# The following tests create new content, and do not clean up after themselves:
# 1. "Create a domain using createdomain"
# - creates or replaces domain named 'test', does not create multiple
# copies
# 2. "Create a binary backup job"
# - creates a new scheduled job named 'BINARY_BACKUP_JOB' each time it
# runs, will create multiple copies
# You may wish to clean up after them manually, especially in a
# real customer environment. Study the tests and/or run them individually
# to learn more about what they create, so that you can find and delete it
# yourself. In a dev, PoC, playpen or classroom environment, the cleanup
# might be optional, as the created objects will not interfere with other
# content or work.
#
# Change History
#
# 01Jun2018 Initial version after refactoring tools
# 18Oct2018 updated gerrulid test because -o changed to -u
# 03Dec2018 Added tests for explainaccess.py
#
#
# Copyright 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
echo "Return all the rest calls that can be made to the folders endpoint"
./callrestapi.py -e /folders -m get
echo
echo "Return the json for all the folders/folders"
./callrestapi.py -e /folders/folders -m get
echo
echo "Return simple text for all the folders/folders"
./callrestapi.py -e /folders/folders -m get -o simple
echo
echo "Rest calls often limit the results returned the text output will tell you returned and total available items"
echo "in this call set a limit above the total items to see everything"
./callrestapi.py -e /folders/folders?limit=500 -m get -o simple
echo
echo "Return the json for all the identities"
./callrestapi.py -e /identities/identities -m get
echo
echo "Return the json for all the identities output to a file"
./callrestapi.py -e /identities/identities -m get > /tmp/identities.json
echo
echo "Contents of /tmp/identities.json:"
cat /tmp/identities.json
echo "End of contents of /tmp/identities.json"
echo
echo "Deleting /tmp/identities.json"
rm /tmp/identities.json
echo "Demonstrating that /tmp/identities.json has been deleted - list it, ls should say no such file or directory:"
ls -al /tmp/identities.json
echo
echo "Refresh the identities cache"
./callrestapi.py -e /identities/userCount -m get
./callrestapi.py -e /identities/cache/refreshes -m post
echo
echo "Pass the folder path and return the folder id and uri"
./getfolderid.py -f /gelcontent
echo
echo "Delete a folder based on its path - we don't want to delete a real folder, so try (and fail) to delete one which does not exist"
./deletefolder.py -f /this_folder_does_not_exist
echo
echo "Delete a folder and its content - we don't want to delete a real folder, so try (and fail) to delete one which does not exist"
./deletefolderandcontent.py -f /this_folder_does_not_exist
echo
echo "Return a set of configuration properties"
./getconfigurationproperties.py -c sas.identities.providers.ldap.user
echo
echo "Create a domain using createdomain"
./createdomain.py -t password -d test -u sasadm -p lnxsas -g "SASAdministrators,HR,Sales"
echo
# Commented out for Viya 3.4 version: this tool is only intended for use with Viya 3.3
#echo "Create a binary backup job"
#./createbinarybackup.py
#echo
echo "Get a rule ID"
#Get /Public folder ID
./getfolderid.py --folderpath /Public > /tmp/folderid.txt
id=$(grep "Id " /tmp/folderid.txt | tr -s ' ' | cut -f3 -d " ")
echo "The Public folder ID is" $id
./getruleid.py -u /folders/folders/$id/** -p authenticatedUsers
echo
echo "Move all content from one folder to another folder (or in this case, the same folder)"
./movecontent.py -s /gelcontent/GELCorp/Shared/Reports -t /gelcontent/GELCorp/Shared/Reports -q
echo
echo "Test folder access"
./testfolderaccess.py -f '/gelcontent/GELCorp' -n gelcorp -t group -m read -s grant
echo
echo "Display all sasadministrator rules"
./listrules.py --p SASadministrators -o simple
echo
echo "Display all rules that contain SASVisual in the URI"
./listrules.py -u SASVisual -o simple
echo
echo "Create folders from a CSV file"
./createfolders.py -h
echo
echo Update the theme for the user sasadm
./updatepreferences.py -t user -tn sasadm -pi OpenUI.Theme.Default -pv sas_hcb
echo
echo "Explain permissions for the folder /gelcontent/GELCorp, with no header row."
./explainaccess.py -f /gelcontent/GELCorp
echo
echo "Explain permissions for the folder /gelcontent/GELCorp, with no header row, for Heather."
./explainaccess.py -f /gelcontent/GELCorp -n Heather -t user
echo
echo "Explain permissions for the folder /gelcontent/GELCorp, with a header row, and include the folder path."
./explainaccess.py -f /gelcontent/GELCorp --header -p
echo
echo "Explain permissions for the folder /gelcontent/GELCorp, showing only rows with at least one direct permission."
./explainaccess.py -f /gelcontent/GELCorp --direct_only
echo
echo "Explain permissions for several application capabilities. Notice that there are no conveyed permissions in the results"
./explainaccess.py -u /SASEnvironmentManager/dashboard --header -p -l read update delete secure add remove create
./explainaccess.py -u /SASDataStudio/** -p -l read update delete secure add remove create
./explainaccess.py -u /SASDataExplorer/** -p -l read update delete secure add remove create
./explainaccess.py -u /SASVisualAnalytics_capabilities/buildAnalyticalModel -p -l read update delete secure add remove create
echo
echo "Explain the permissions for a folder expressed as a URI. This works with any kind of object, not just folders"
echo "Uses the default permissions list. Specify -c true to include conveyed permissions, as they are not included by default when you use the -u URI parameter."
#Get /gelcontent/GELCorp folder ID
./getfolderid.py --folderpath /gelcontent/GELCorp > /tmp/folderid.txt
id=$(grep "Id " /tmp/folderid.txt | tr -s ' ' | cut -f3 -d " ")
./explainaccess.py -u /folders/folders/$id --header -p -c true
echo

108
updatedomain.py

@ -0,0 +1,108 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# updatedomain.py
# November 2018
#
# update a viya domain to add credentials from a csv
# the domain must exist
#
# Change History
#
# 27JAN2017 Comments added
# 03DEC2018 Strip spaces from the end of users and groups
#
# csv file format
# no header row
# column1 is userid
# column2 is password
# column3 is identity
# column4 is identity type (user or group)
# For example:
# myuserid,mypass,Sales,group
# acct1,pw1,Admin,user
# etc
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
# Update a domain
import csv
import base64
import argparse
from sharedfunctions import callrestapi, file_accessible
parser = argparse.ArgumentParser(description="Update a Viya Domain to add credentials from a csv file.")
parser.add_argument("-d","--domain", help="Existing Domain.",required=True)
parser.add_argument("-f","--file", help="A csv file containing groups and userids.",required=True)
args = parser.parse_args()
domain_name=args.domain
file=args.file
# check that domain exists
reqval="/credentials/domains/"+domain_name
reqtype="get"
#if domain does not exist call restapi will exit and no additional code is run
domainexist=callrestapi(reqval,reqtype)
type=domainexist['type']
# read the csv file to create json
check=file_accessible(file,'r')
# file can be read
if check:
with open(file, 'rt') as f:
filecontents = csv.reader(f)
for row in filecontents:
#print(row)
userid=row[0]
pwval=row[1]
ident=row[2].rstrip()
identtype=row[3].rstrip()
if pwval: cred=base64.b64encode(pwval.encode("utf-8")).decode("utf-8")
if identtype=="group": end_ident="groups"
if identtype=="user": end_ident="users"
reqval="/credentials/domains/"+domain_name+"/"+end_ident+"/"+ident
reqtype="put"
data = {}
data['domainId'] = domain_name
data['domainType'] = type
data['identityId'] = ident
data['identityType'] = identtype
data['properties']={"userId": userid}
if pwval:
data['secrets']={"password": cred}
#print(reqval)
#print(data)
# make the rest call
callrestapi(reqval,reqtype,data=data)
else:
print("ERROR: cannot read "+file)

99
updatepreferences.py

@ -0,0 +1,99 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# updatepreferences.py
# October 2018
#
# Update user preferences for a single user of a group of users
#
# Change History
#
# 30OCT2018 first version
#
# Copyright © 2018, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
####################################################################
#### COMMAND LINE EXAMPLE ####
####################################################################
#### ./updatepreferences.py - ####
#### -t user ####
#### -tn myUser ####
#### -pi VA.geo.drivedistance.unit ####
#### -pv kilometers ####
####################################################################
#### POSSIBLE VALUES ####
####################################################################
#### sas.welcome.suppress = true/false ####
#### sas.drive.show.pinned = true/false ####
#### VA.geo.drivedistance.unit = kilometers/miles ####
#### OpenUI.Theme.Default = sas_corporate/sas_inspire/sas_hcb ####
####################################################################
import argparse
from sharedfunctions import callrestapi
parser = argparse.ArgumentParser(description="Update user preferences for a user or a group of users")
parser.add_argument("-t", "--target", help="Type the target of the update: user or group", required=True, choices=['user', 'group'])
parser.add_argument("-tn", "--targetname", help="ID of the user or group to which the update applies.", required=True)
parser.add_argument("-pi", "--preferenceid", help="ID of the preference to be updated", required=True)
parser.add_argument("-pv", "--preferencevalue", help="Value to be set for the preference", required=True)
args = parser.parse_args()
target = args.target
targetName = args.targetname
preferenceID = args.preferenceid
preferenceValue = args.preferencevalue
json= {"application": "SAS Visual Analytics", "version": 1,"id": preferenceID ,"value": preferenceValue}
# Function to update preference of a specific user
if target == 'user' :
userID=targetName
reqtype='get'
reqval="/identities/users/"+userID
userexist=callrestapi(reqval,reqtype)
reqtype="put"
reqval="/preferences/preferences/"+ userID +"/" + preferenceID
result=callrestapi(reqval,reqtype,data=json)
print("Updating Preference "+reqval+" = "+preferenceValue)
else: # Execute actual code to update the preference for a user or a group
reqtype='get'
reqval='/identities/groups/'+ targetName +'/members?limit=1000'
resultdata=callrestapi(reqval,reqtype)
reqtype="put"
if 'items' in resultdata:
returned_items=len(resultdata['items'])
for i in range(0,returned_items):
id=resultdata['items'][i]['id']
type=resultdata['items'][i]['type']
if type=="user":
reqval="/preferences/preferences/"+ id +"/" + preferenceID
result=callrestapi(reqval, reqtype,data=json,stoponerror=0)
print(result)
print("Updating Preference "+reqval+" = "+preferenceValue)
else: print("Cannot set preferences for a group "+id )
Loading…
Cancel
Save