Note:
- This tutorial requires access to Oracle Cloud. To sign up for a free account, see Get started with Oracle Cloud Infrastructure Free Tier.
- It uses example values for Oracle Cloud Infrastructure credentials, tenancy, and compartments. When completing your lab, substitute these values with ones specific to your cloud environment.
Delete Inactive Users from OCI IAM Identity Domains Based on UserStatus and LastSuccessfulLoginDate Using Python
Introduction
Oracle Cloud Infrastructure (OCI) is a cloud platform capable of providing a range of cloud services including storage, networking and infrastructure. Oracle Cloud Infrastructure Identity and Access Management (OCI IAM) is a service that enables you to manage access to OCI resources. It provides authentication and authorization for users and groups.
Deleting inactive users based on their last successful login date in OCI IAM Identity Domains can be particularly helpful in the context of OCI’s per-user pricing model for several reasons:
-
Cost Optimization: OCI follows a per-user pricing model, which means that you are billed for each user account. By regularly identifying and deleting inactive users, you can reduce your subscription costs significantly. This cost optimization becomes even more critical as your organization scales and the number of users grows.
-
Security Enhancement: Inactive user accounts are a potential security risk. These accounts may no longer be used by legitimate users, but they can still be exploited by malicious actors if left unattended. Deleting inactive users ensures that only authorized individuals have access to your OCI resources, reducing the attack surface and enhancing overall security.
-
Resource Management: Managing a large number of inactive user accounts can be administratively burdensome. It clutters your IAM environment and makes it harder to focus on active user accounts. Automate the process of identifying and deleting inactive users frees up administrative resources and allows your team to concentrate on more strategic tasks.
-
Compliance: Compliance standards often require organizations to regularly review and manage user accounts. Deleting inactive users can help you meet compliance requirements and demonstrate a commitment to data security and governance.
To implement this process effectively, below python snippet can be used to automate the identification and deletion of inactive users based on their last successful login date and active/inactive status. By regularly running such scripts, you can maintain a lean and secure IAM environment while optimizing your costs in OCI.
Audience
IAM professionals and administrators.
Objective
Delete users based on user status and lastSuccessfulLoginDate from OCI IAM Identity Domains using the REST API with Python.
Prerequisites
-
An active OCI subscription.
-
Familiar with OCI IAM and Python.
-
Knowledge of using OCI IAM Identity Domains REST API is required.
-
An IAM user with authorization to manage Applications (Identity Domain Administrator, Security Administrator, or Application Administrator).
-
Python 3.x installed on your system.
-
urllib3
,requests
anddatetime
Python packages installed.
Task 1: Create a confidential application in OCI IAM Identity Domains
Follow the Oracle Identity Cloud Service: First REST API Call to create a confidential application and retrieve client ID and client secret, which can then be used to perform a REST API call to OCI IAM for retrieving Access Token and subsequent API endpoints.
Task 2: Set up the config.json
file
Set up the config file on your local machine. The config.json
file has information about the Identity Domain URL, Client ID and Client Secret which is used to generate the Access Token.
{
"iamurl" : "https://idcs-###########.identity.oraclecloud.com",
"client_id" : "#######################",
"client_secret" : "#######################"
}
Task 3: Get the Access Token
Once the config.json
file is in place, the first thing you need to do is to generate the Access Token, which can be used to make further REST API calls to the OCI IAM endpoints.
In the below code snippet, the function get_encoded takes Client ID and Client Secret as arguments and returns the base64-encoded
string. This encoded string is further passed as an argument to the function get_access_token
as an Authorization header, to obtain the Access Token by performing a POST request.
#get base64 encoded
def get_encoded(self,clid, clsecret):
encoded = clid + ":" + clsecret
baseencoded = base64.urlsafe_b64encode(encoded.encode('UTF-8')).decode('ascii')
return baseencoded
#get access token
def get_access_token(self,url, header):
para = "grant_type=client_credentials&scope=urn:opc:idm:__myscopes__"
response = requests.post(url, headers=header, data=para, verify=False)
jsonresp = json.loads(response.content)
access_token = jsonresp.get('access_token')
return access_token
#print access token
def printaccesstoken(self):
obj = IAM()
encodedtoken = obj.get_encoded(clientID, clientSecret)
extra = "/oauth2/v1/token"
headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'Authorization': 'Basic %s' % encodedtoken, 'Accept': '*/*'}
accesstoken = obj.get_access_token(idcsURL + extra, headers)
return accesstoken
Task 4: Handle a batch of user information (lastSuccessfulLoginDate, userStatus) to clean-up users
We have the access token, using which we can make further REST API calls to different OCI IAM Identity Domains REST endpoints. The code snippet below shows how we are making a GET request with necessary headers and parameters to the /admin/v1/Users
endpoint, in order to retrieve the total number of users and then calculate the number of iterations needed, based on the total number of results and the count per request.
-
The function obtains the current date in the YYYY-MM-DD format and stores in a variable
current_date
and then iterates through each user data. -
In each iteration, it sends a GET request with the appropriate parameters to retrieve a batch of user information (username, lastSuccessfulLoginDate and active) and then performs some conditional checks.
-
The code then based on defined conditional checks, appends the username and userid for all the matched users in
username.txt
anduserid.txt
files respectively, and marks it for deletion. Firstly, it checks the user status, and if it’s False (which means inactive), it appends the user to thetxt
files and continues to the next iteration of the loop. It then checks the user state, and if it’s None, it prints that _{user} has never accessed. Make a decision to delete it manually on console and continue to the next iteration of the loop. -
The script retrieves the last successful login date of the user, calculates the number of days since the last login, and prints it to the console. For any user who has not logged in for more than 90 days, the script proceeds to check if the user has specific admin roles associated with them. Any user that has not Logged in for 90 days and has NO admin roles gets identified and marked for deletion.
-
After processing all users, a set of user IDs to be deleted is written to a JSON file
username.json
. A bulk delete request is prepared by formatting the data in a specific JSON format. The bulk delete request is then sent to the/admin/v1/Bulk
endpoint, and a message is printed to indicate that identified users have been deleted. -
After all the iterations, the function returns to the main function.
Note: There is a code section commented out with explanations for future actions or manual decisions, such as handling users who have never accessed the system. There could be a scenario where an Admin might not have accessed OCI IAM and you might not want to delete them.
def get_successfullogindate(self): extra = "/admin/v1/Users" obj = IAM() accesstoken = obj.printaccesstoken() headers = {'Authorization': 'Bearer ' + accesstoken} resp = requests.get(idcsURL+extra, headers=headers, verify=False) jsonresp = json.loads(resp.content) totalCount = jsonresp.get("totalResults") print("Total number of users: " + str(totalCount)) current_date = datetime.now().date() startIndex = 1 count = 50 loop = int(totalCount / count) extra2 = "/admin/v1/AppRoles" for i in range(loop + 1): param = { 'attributes': "username,active,urn:ietf:params:scim:schemas:oracle:idcs:extension:userState:User:lastSuccessfulLoginDate", 'startIndex': startIndex, 'count': count} resp = requests.get(idcsURL+extra, headers=headers, verify=False, params=param) startIndex += count jsonresp = json.loads(resp.content) tempjsn = jsonresp.get("Resources") for x in tempjsn: trimjsn = {} username = trimjsn['userName'] = x.get("userName") userid = x.get("id") userStatus = x.get("active") userState = x.get("urn:ietf:params:scim:schemas:oracle:idcs:extension:userState:User") if userStatus is False: print(username + " is in inactive state. Will be deleted.") with open('username.txt', 'a') as c: c.write(username) c.write('\n') with open('userID.txt', 'a') as d: d.write(userid) d.write('\n') continue if userState is None: print(username + " has never accessed. Kindly make a decision to delete it manually on console") # with open('username.txt', 'a') as c: # c.write(username) # c.write('\n') # with open('userID.txt', 'a') as d: # d.write(userid) # d.write('\n') # ** Uncomment the lines commented above to delete the users who have never accessed ** continue lastLoginDate = x['urn:ietf:params:scim:schemas:oracle:idcs:extension:userState:User']['lastSuccessfulLoginDate'] target_dates = datetime.strptime(lastLoginDate, '%Y-%m-%dT%H:%M:%S.%fZ').date() num_days = (current_date - target_dates).days #print(username + " Last logged in " + str(num_days) + " days ago") if num_days > 30: #print("entered if condition for more than 90 days ") params2 = {"filter": f'members[value eq "{userid}"] and app.value eq "IDCSAppId" and (displayName eq "Identity Domain Administrator" or displayName eq "Security Administrator" or displayName eq "Application Administrator" or displayName eq "User Administrator" or displayName eq "User Manager" or displayName eq "Help Desk Administrator" or displayName eq "Audit Administrator")'} resp2 = requests.get(idcsURL+extra2, headers=headers, verify=False, params=params2) jsonresp3 = json.loads(resp2.content) totalCount1 = jsonresp3.get("totalResults") if totalCount1 < 1: print(username + " has not Logged in for 90 days and has NO admin role. Will be deleted.") with open('username.txt', 'a') as c: c.write(username) c.write('\n') with open('userID.txt', 'a') as d: d.write(userid) d.write('\n') try: with open('userID.txt','r') as g: lines = len(g.readlines()) except FileNotFoundError: print("No users found to be Deleted by the Script") exit() x=0 while x<lines: with open('userID.txt','r') as e: content = e.readlines() id=content[x] id=id.rstrip(id[-1]) paradelete=json.dumps( { "method": "DELETE", "path": "/Users/"+id+"?forceDelete=true" } ) with open('username.json', 'a') as j: j.writelines(paradelete) j.write(',') x=x+1 with open('username.json', 'r') as file: data = file.read() with open('formatedData.json', 'a') as file1: file1.writelines('{ \n') file1.writelines('"schemas": ["urn:ietf:params:scim:api:messages:2.0:BulkRequest"],\n') file1.writelines('"Operations": [\n') data2=data.rstrip(data[-1]) file1.write(data2) file1.write(']}') headers = {'Authorization': 'Bearer ' + accesstoken} bulkdata = json.load(open('formatedData.json')) param = {'forceDelete': True} payload = json.dumps(bulkdata) headers2 = {'Content-Type': 'application/json','Authorization': 'Bearer ' + accesstoken, 'Accept': '*/*'} extra2="/admin/v1/Bulk" #respdelete = requests.request("POST",idcsURL + extra2, headers=headers2, verify=False, params=param,data=payload) print("The identified users has been deleted ") try: os.remove('userID.txt') os.remove('username.txt') os.remove('formatedData.json') os.remove('username.json') except FileNotFoundError: exit()
Task 5: Use the script in the OCI Cloud Shell
Once the script is ready, it can be easily executed on the local machine (with Python Installed) or on any IDE that supports Python development. We are using OCI Cloud Shell to run the script and get the desired report.
-
Log in to the OCI Console, open the Cloud Shell from the top right corner of the screen and then upload the Python script and
config.json
file. -
Execute
python DeleteUser_PriorCheck_InactivePeriod60days_IsAdmin.py
.Note: The
DeleteUser_PriorCheck_InactivePeriod60days_IsAdmin.py
is the Python script developed using above code snippets.
Related Links
Acknowledgments
-
Author - Gautam Mishra (Senior Cloud Engineer)
-
Contributor - Chetan Soni (Cloud Solutions Engineer)
More Learning Resources
Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.
For product documentation, visit Oracle Help Center.
Delete Inactive Users from OCI IAM Identity Domains Based on UserStatus and LastSuccessfulLoginDate Using Python
F89514-01
November 2023
Copyright © 2023, Oracle and/or its affiliates.