diff --git a/gws/1.0.0/Dockerfile b/gws/1.0.0/Dockerfile new file mode 100644 index 00000000..364e1531 --- /dev/null +++ b/gws/1.0.0/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/gws/1.0.0/README.md b/gws/1.0.0/README.md new file mode 100644 index 00000000..5c8eb2e4 --- /dev/null +++ b/gws/1.0.0/README.md @@ -0,0 +1,40 @@ +## Google Workspace +An app for interacting with Google Workspace or GWS. +## Requirements +1) Enable the Admin SDK API from GCP console. + - Login to Google cloud (Make sure you are using the same administrator acount that you're using for Google Workspace) and In the navigation menu on the left-hand side, click on “APIs & Services” > “Library”. + - In the API Library, use the search bar to find the "Admin SDK". Click on it to open the API page. + - Click the “Enable” button to activate the Admin SDK API for your project. + 2) Create a Service account. + - Go to the navigation menu, and select “IAM & Admin” > “Service Accounts”. + - Click on “Create Service Account” at the top of the page. + - Enter a service account name and description, then click “Create”. + - You can skip the permission part here as we will be adding persmissions from GWS console later on. + - In the service account details page, click on “Keys”. + - Click on “Add Key” and select “Create new key”. + - Choose “JSON” as the key type and click “Create”. This will download the JSON key file which contains the “client_id”. Note down this client ID. + + 3) Subject (Email address associated with the service account) + - Note down the email address associated with the service account you just created it'll be used in the authentication in Shuffle. + 4) Adding permissions to the service account from GWS console. + - Signin to the Google Workspace admin console. + - In the Admin console, locate the sidebar and navigate to Security > API controls. This area allows you to manage third-party and internal application access to your Google Workspace data. + - Under the Domain-wide delegation section, click on “Manage Domain Wide Delegation” to view and configure client access. + - If the service account client ID is not listed, you will add it; if it is already listed but you need to update permissions, click on the service account’s client ID. To add a new client ID: + - Click on Add new. + - Enter the Client ID of the service account you noted earlier when creating the service account in GCP. + - In the OAuth Scopes field, enter the scopes required for your service account to function correctly. OAuth Scopes specify the permissions that your application requests. + - Depending on the actions you want to use below are the OAuth scopes required. + +| Action | OAuth Scope | +|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| Reset User Password | `https://www.googleapis.com/auth/admin.directory.user` | +| Suspend User | `https://www.googleapis.com/auth/admin.directory.user` | +| Get User Devices |`https://www.googleapis.com/auth/admin.directory.device.mobile` | +| Reactivate User | `https://www.googleapis.com/auth/admin.directory.user` + +## Authentication +1) Upload the Service account JSON file in to the Shuffle files and copy the file id. +2) Now, Inside the GWS app authentication in Shuffle; use the file id you just copied and in subject use the email address asscoitate with your service account. + + diff --git a/gws/1.0.0/api.yaml b/gws/1.0.0/api.yaml new file mode 100644 index 00000000..a879728b --- /dev/null +++ b/gws/1.0.0/api.yaml @@ -0,0 +1,106 @@ +app_version: 1.0.0 +name: Google Workspace +description: Manage Google Workspace with Shuffle +contact_info: + name: "dhaval055" + url: https://github.com/dhaval055 +tags: + - Assets +categories: + - Assets +authentication: + required: true + parameters: + - name: service_account_file_id + description: Upload a service account file to Shuffle and use the file ID here. + example: "file_id" + required: true + schema: + type: string + - name: subject + description: User email associated with service account. + example: "admin@org.com" + required: true + schema: + type: string +actions: + - name: reset_user_password + description: Change GWS user password. + parameters: + - name: user_email + description: User email you want to reset password of. + required: true + multiline: false + example: 'testuser@testorg.com' + schema: + type: string + - name: new_password + description: Password you want to set. If you do not provide this value then a random password will be generated. + required: false + multiline: false + example: "*******" + schema: + type: string + returns: + schema: + type: string + - name: get_user_devices + description: Get GWS user devices. + parameters: + - name: user_email + description: User email you want to reset password of. + required: true + multiline: false + example: 'testuser@testorg.com' + schema: + type: string + - name: customer_id + description: Customer ID of the account. Can be found in admin console under account -> account settings. + required: true + multiline: false + example: "C02dnh9vw" + schema: + type: string + returns: + schema: + type: string + - name: suspend_user + description: Suspend GWS user. + parameters: + - name: user_email + description: User email you want to reset password of. + required: true + multiline: false + example: 'testuser@testorg.com' + schema: + type: string + returns: + schema: + type: string + - name: reactivate_user + description: Reactivate the suspended GWS user. + parameters: + - name: service_account_file_id + description: Upload a service account file to Shuffle and use the file ID here. + example: "file_id" + required: true + schema: + type: string + - name: subject + description: User email associated with service account. + required: true + multiline: false + example: 'adminuser@testorg.com' + schema: + type: string + - name: user_email + description: User email you want to reset password of. + required: true + multiline: false + example: 'testuser@testorg.com' + schema: + type: string + returns: + schema: + type: string +large_image: data:image/png;base64,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 diff --git a/gws/1.0.0/requirements.txt b/gws/1.0.0/requirements.txt new file mode 100644 index 00000000..102c8fc3 --- /dev/null +++ b/gws/1.0.0/requirements.txt @@ -0,0 +1,6 @@ +requests==2.25.1 +google-auth==1.28.0 +google-auth-oauthlib==0.4.3 +google-auth-httplib2==0.0.4 +google-api-python-client==2.0.2 + diff --git a/gws/1.0.0/src/app.py b/gws/1.0.0/src/app.py new file mode 100644 index 00000000..211055c3 --- /dev/null +++ b/gws/1.0.0/src/app.py @@ -0,0 +1,130 @@ +import socket +import asyncio +import time +import random +import json +import requests +import secrets +import string + +from walkoff_app_sdk.app_base import AppBase + +from google.oauth2 import service_account +from googleapiclient.discovery import build + + +class Gws(AppBase): + __version__ = "1.0.0" + app_name = "Google Workspace" + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + def reset_user_password(self, service_account_file_id, subject ,user_email,new_password): + service_account_file = self.get_file(service_account_file_id) + service_account_info = service_account_file['data'].decode() + + def generate_secure_password(length=12): + characters = string.ascii_letters + string.digits + string.punctuation + secure_password = ''.join(secrets.choice(characters) for i in range(length)) + return secure_password + + if new_password == "": + print("Generating new password") + new_password = generate_secure_password() + + try: + service_account_info = json.loads(service_account_info) + except Exception as e: + print(f"Error loading service account file: {e}") + return {"success": False, "message": f"Error loading service account file: {e}"} + + SCOPES = ['https://www.googleapis.com/auth/admin.directory.user'] + + creds = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES,subject=subject) + service = build('admin', 'directory_v1', credentials=creds) + + try: + result = service.users().update(userKey=user_email, body={'password': new_password}).execute() + return {"success": True, "message": f"Password for {user_email} reset successfully.", "new_password": new_password} + except Exception as e: + return {"success": False, "message": f"Error resetting password: {e}"} + + def get_user_devices(self, service_account_file_id, subject ,user_email, customer_id): + service_account_file = self.get_file(service_account_file_id) + service_account_info = service_account_file['data'].decode() + + try: + service_account_info = json.loads(service_account_info) + except Exception as e: + print(f"Error loading service account file: {e}") + return {"success": False, "message": f"Error loading service account file: {e}"} + + SCOPES = ['https://www.googleapis.com/auth/admin.directory.device.mobile'] + + creds = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES,subject=subject) + service = build('admin', 'directory_v1', credentials=creds) + + query = f'email:{user_email}' + + try: + results = service.mobiledevices().list(customerId=customer_id, query=query).execute() + devices = results.get('mobiledevices', []) + except Exception as e: + return {"success": False, "message": f"Error getting devices: {e}"} + + return {"success": True, "message": f"Devices for {user_email} retrieved successfully.", "devices": devices} + + def suspend_user(self, service_account_file_id, subject ,user_email): + service_account_file = self.get_file(service_account_file_id) + service_account_info = service_account_file['data'].decode() + + try: + service_account_info = json.loads(service_account_info) + except Exception as e: + print(f"Error loading service account file: {e}") + return {"success": False, "message": f"Error loading service account file: {e}"} + + SCOPES = ['https://www.googleapis.com/auth/admin.directory.user'] + + creds = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES,subject=subject) + service = build('admin', 'directory_v1', credentials=creds) + + try: + result = service.users().update(userKey=user_email,body={'suspended': True}).execute() + except Exception as e: + return {"success": False, "message": f"Error suspending user: {e}"} + + return {"success": True, "message": f"{user_email} suspended successfully."} + + def reactivate_user(self, service_account_file_id, subject ,user_email): + service_account_file = self.get_file(service_account_file_id) + service_account_info = service_account_file['data'].decode() + + try: + service_account_info = json.loads(service_account_info) + except Exception as e: + print(f"Error loading service account file: {e}") + return {"success": False, "message": f"Error loading service account file: {e}"} + + SCOPES = ['https://www.googleapis.com/auth/admin.directory.user'] + + creds = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES,subject=subject) + service = build('admin', 'directory_v1', credentials=creds) + + try: + result = service.users().update(userKey=user_email,body={'suspended': False}).execute() + except Exception as e: + return {"success": False, "message": f"Error reactivating user: {e}"} + + return {"success": True, "message": f"{user_email} reactivated successfully."} + + +if __name__ == "__main__": + Gws.run()