Add support for service account
Adding support service accounts Signed-off-by: lzzy12 <jhashivam2020@gmail.com> Added scripts and docs for generating service accounts Signed-off-by: lzzy12 <jhashivam2020@gmail.com> gen_sa_accounts: Save credentials with indexed file name Signed-off-by: lzzy12 <jhashivam2020@gmail.com> gdriveTools: Avoid using oauth2 library for service accounts oauth2 library is deprecated Signed-off-by: lzzy12 <jhashivam2020@gmail.com>
This commit is contained in:
parent
997e572b69
commit
7ad7d9388a
|
|
@ -10,3 +10,4 @@ data*
|
||||||
*.pickle
|
*.pickle
|
||||||
authorized_chats.txt
|
authorized_chats.txt
|
||||||
log.txt
|
log.txt
|
||||||
|
accounts/*
|
||||||
31
README.md
31
README.md
|
|
@ -87,3 +87,34 @@ sudo docker build . -t mirror-bot
|
||||||
```
|
```
|
||||||
sudo docker run mirror-bot
|
sudo docker run mirror-bot
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Using service accounts for uploading to avoid user rate limit
|
||||||
|
|
||||||
|
Many thanks to [AutoRClone](https://github.com/xyou365/AutoRclone) for the scripts
|
||||||
|
### Generating service accounts
|
||||||
|
Step 1. Generate service accounts [What is service account](https://cloud.google.com/iam/docs/service-accounts) [How to use service account in rclone](https://rclone.org/drive/#service-account-support).
|
||||||
|
---------------------------------
|
||||||
|
Let us create only the service accounts that we need.
|
||||||
|
**Warning:** abuse of this feature is not the aim of autorclone and we do **NOT** recommend that you make a lot of projects, just one project and 100 sa allow you plenty of use, its also possible that overabuse might get your projects banned by google.
|
||||||
|
|
||||||
|
```
|
||||||
|
Note: 1 service account can copy around 750gb a day, 1 project makes 100 service accounts so thats 75tb a day, for most users this should easily suffice.
|
||||||
|
```
|
||||||
|
|
||||||
|
`python3 gen_sa_accounts.py --quick-setup 1 --new-only`
|
||||||
|
|
||||||
|
A folder named accounts will be created which will contain keys for the service accounts created
|
||||||
|
```
|
||||||
|
We highly recommend to zip this folder and store it somewhere safe, so that you do not have to create a new project everytime you want to deploy the bot
|
||||||
|
```
|
||||||
|
### Adding service accounts to Google Groups:
|
||||||
|
We use Google Groups to manager our service accounts considering the
|
||||||
|
[Official limits to the members of Team Drive](https://support.google.com/a/answer/7338880?hl=en) (Limit for individuals and groups directly added as members: 600).
|
||||||
|
|
||||||
|
1. Turn on the Directory API following [official steps](https://developers.google.com/admin-sdk/directory/v1/quickstart/python) (save the generated json file to folder `credentials`).
|
||||||
|
|
||||||
|
2. Create group for your organization [in the Admin console](https://support.google.com/a/answer/33343?hl=en). After create a group, you will have an address for example`sa@yourdomain.com`.
|
||||||
|
|
||||||
|
3. Run `python3 add_to_google_group.py -g sa@yourdomain.com`
|
||||||
|
|
||||||
|
4. Now, add Google Groups (**Step 2**) to manager your service accounts, add the group address `sa@yourdomain.com` or `sa@googlegroups.com` to the Team drive or folder
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
# auto rclone
|
||||||
|
# Add service accounts to groups for your organization
|
||||||
|
#
|
||||||
|
# Author Telegram https://t.me/CodyDoby
|
||||||
|
# Inbox codyd@qq.com
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import glob
|
||||||
|
import googleapiclient.discovery
|
||||||
|
import json
|
||||||
|
import progress.bar
|
||||||
|
import time
|
||||||
|
from google.auth.transport.requests import Request
|
||||||
|
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||||
|
|
||||||
|
stt = time.time()
|
||||||
|
|
||||||
|
parse = argparse.ArgumentParser(
|
||||||
|
description='A tool to add service accounts to groups for your organization from a folder containing credential '
|
||||||
|
'files.')
|
||||||
|
parse.add_argument('--path', '-p', default='accounts',
|
||||||
|
help='Specify an alternative path to the service accounts folder.')
|
||||||
|
parse.add_argument('--credentials', '-c', default='credentials/credentials.json',
|
||||||
|
help='Specify the relative path for the controller file.')
|
||||||
|
parsereq = parse.add_argument_group('required arguments')
|
||||||
|
# service-account@googlegroups.com
|
||||||
|
parsereq.add_argument('--groupaddr', '-g', help='The address of groups for your organization.', required=True)
|
||||||
|
|
||||||
|
args = parse.parse_args()
|
||||||
|
acc_dir = args.path
|
||||||
|
gaddr = args.groupaddr
|
||||||
|
credentials = glob.glob(args.credentials)
|
||||||
|
|
||||||
|
creds = None
|
||||||
|
if os.path.exists('credentials/token.pickle'):
|
||||||
|
with open('credentials/token.pickle', 'rb') as token:
|
||||||
|
creds = pickle.load(token)
|
||||||
|
# If there are no (valid) credentials available, let the user log in.
|
||||||
|
if not creds or not creds.valid:
|
||||||
|
if creds and creds.expired and creds.refresh_token:
|
||||||
|
creds.refresh(Request())
|
||||||
|
else:
|
||||||
|
flow = InstalledAppFlow.from_client_secrets_file(credentials[0], scopes=[
|
||||||
|
'https://www.googleapis.com/auth/admin.directory.group',
|
||||||
|
'https://www.googleapis.com/auth/admin.directory.group.member'
|
||||||
|
])
|
||||||
|
# creds = flow.run_local_server(port=0)
|
||||||
|
creds = flow.run_console()
|
||||||
|
# Save the credentials for the next run
|
||||||
|
with open('credentials/token.pickle', 'wb') as token:
|
||||||
|
pickle.dump(creds, token)
|
||||||
|
|
||||||
|
group = googleapiclient.discovery.build("admin", "directory_v1", credentials=creds)
|
||||||
|
|
||||||
|
print(group.members())
|
||||||
|
|
||||||
|
batch = group.new_batch_http_request()
|
||||||
|
|
||||||
|
sa = glob.glob('%s/*.json' % acc_dir)
|
||||||
|
|
||||||
|
# sa = sa[0:5]
|
||||||
|
|
||||||
|
pbar = progress.bar.Bar("Readying accounts", max=len(sa))
|
||||||
|
for i in sa:
|
||||||
|
ce = json.loads(open(i, 'r').read())['client_email']
|
||||||
|
|
||||||
|
body = {"email": ce, "role": "MEMBER"}
|
||||||
|
batch.add(group.members().insert(groupKey=gaddr, body=body))
|
||||||
|
# group.members().insert(groupKey=gaddr, body=body).execute()
|
||||||
|
|
||||||
|
pbar.next()
|
||||||
|
pbar.finish()
|
||||||
|
print('Adding...')
|
||||||
|
batch.execute()
|
||||||
|
|
||||||
|
print('Complete.')
|
||||||
|
hours, rem = divmod((time.time() - stt), 3600)
|
||||||
|
minutes, sec = divmod(rem, 60)
|
||||||
|
print("Elapsed Time:\n{:0>2}:{:0>2}:{:05.2f}".format(int(hours), int(minutes), sec))
|
||||||
|
|
@ -90,6 +90,16 @@ try:
|
||||||
IS_TEAM_DRIVE = False
|
IS_TEAM_DRIVE = False
|
||||||
except KeyError:
|
except KeyError:
|
||||||
IS_TEAM_DRIVE = False
|
IS_TEAM_DRIVE = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
USE_SERVICE_ACCOUNTS = getConfig('USE_SERVICE_ACCOUNTS')
|
||||||
|
if USE_SERVICE_ACCOUNTS.lower() == 'true':
|
||||||
|
USE_SERVICE_ACCOUNTS = True
|
||||||
|
else:
|
||||||
|
USE_SERVICE_ACCOUNTS = False
|
||||||
|
except KeyError:
|
||||||
|
USE_SERVICE_ACCOUNTS = False
|
||||||
|
|
||||||
updater = tg.Updater(token=BOT_TOKEN)
|
updater = tg.Updater(token=BOT_TOKEN)
|
||||||
bot = updater.bot
|
bot = updater.bot
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
|
|
||||||
|
|
@ -4,33 +4,66 @@ import urllib.parse as urlparse
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from google.auth.transport.requests import Request
|
from google.auth.transport.requests import Request
|
||||||
|
from google.oauth2 import service_account
|
||||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||||
from googleapiclient.discovery import build
|
from googleapiclient.discovery import build
|
||||||
from googleapiclient.errors import HttpError
|
from googleapiclient.errors import HttpError
|
||||||
from googleapiclient.http import MediaFileUpload
|
from googleapiclient.http import MediaFileUpload
|
||||||
from tenacity import *
|
from tenacity import *
|
||||||
|
|
||||||
from bot import LOGGER, parent_id, DOWNLOAD_DIR, IS_TEAM_DRIVE, INDEX_URL
|
from bot import LOGGER, parent_id, DOWNLOAD_DIR, IS_TEAM_DRIVE, INDEX_URL, DOWNLOAD_STATUS_UPDATE_INTERVAL, \
|
||||||
|
USE_SERVICE_ACCOUNTS
|
||||||
from bot.helper.ext_utils.bot_utils import *
|
from bot.helper.ext_utils.bot_utils import *
|
||||||
from bot.helper.ext_utils.fs_utils import get_mime_type
|
from bot.helper.ext_utils.fs_utils import get_mime_type
|
||||||
|
|
||||||
logging.getLogger('googleapiclient.discovery').setLevel(logging.ERROR)
|
logging.getLogger('googleapiclient.discovery').setLevel(logging.ERROR)
|
||||||
|
|
||||||
|
G_DRIVE_TOKEN_FILE = "token.pickle"
|
||||||
|
# Check https://developers.google.com/drive/scopes for all available scopes
|
||||||
|
OAUTH_SCOPE = ["https://www.googleapis.com/auth/drive"]
|
||||||
|
|
||||||
|
SERVICE_ACCOUNT_INDEX = 0
|
||||||
|
|
||||||
|
|
||||||
|
def authorize():
|
||||||
|
# Get credentials
|
||||||
|
credentials = None
|
||||||
|
if not USE_SERVICE_ACCOUNTS:
|
||||||
|
if os.path.exists(G_DRIVE_TOKEN_FILE):
|
||||||
|
with open(G_DRIVE_TOKEN_FILE, 'rb') as f:
|
||||||
|
credentials = pickle.load(f)
|
||||||
|
if credentials is None or not credentials.valid:
|
||||||
|
if credentials and credentials.expired and credentials.refresh_token:
|
||||||
|
credentials.refresh(Request())
|
||||||
|
else:
|
||||||
|
flow = InstalledAppFlow.from_client_secrets_file(
|
||||||
|
'credentials.json', OAUTH_SCOPE)
|
||||||
|
LOGGER.info(flow)
|
||||||
|
credentials = flow.run_console(port=0)
|
||||||
|
|
||||||
|
# Save the credentials for the next run
|
||||||
|
with open(G_DRIVE_TOKEN_FILE, 'wb') as token:
|
||||||
|
pickle.dump(credentials, token)
|
||||||
|
else:
|
||||||
|
credentials = service_account.Credentials \
|
||||||
|
.from_service_account_file(f'accounts/{SERVICE_ACCOUNT_INDEX}.json',
|
||||||
|
scopes=OAUTH_SCOPE)
|
||||||
|
return build('drive', 'v3', credentials=credentials, cache_discovery=False)
|
||||||
|
|
||||||
|
|
||||||
|
service = authorize()
|
||||||
|
|
||||||
|
|
||||||
class GoogleDriveHelper:
|
class GoogleDriveHelper:
|
||||||
|
# Redirect URI for installed apps, can be left as is
|
||||||
|
REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob"
|
||||||
|
G_DRIVE_DIR_MIME_TYPE = "application/vnd.google-apps.folder"
|
||||||
|
G_DRIVE_BASE_DOWNLOAD_URL = "https://drive.google.com/uc?id={}&export=download"
|
||||||
|
|
||||||
def __init__(self, name=None, listener=None):
|
def __init__(self, name=None, listener=None):
|
||||||
self.__G_DRIVE_TOKEN_FILE = "token.pickle"
|
|
||||||
# Check https://developers.google.com/drive/scopes for all available scopes
|
|
||||||
self.__OAUTH_SCOPE = ["https://www.googleapis.com/auth/drive"]
|
|
||||||
# Redirect URI for installed apps, can be left as is
|
|
||||||
self.__REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob"
|
|
||||||
self.__G_DRIVE_DIR_MIME_TYPE = "application/vnd.google-apps.folder"
|
|
||||||
self.__G_DRIVE_BASE_DOWNLOAD_URL = "https://drive.google.com/uc?id={}&export=download"
|
|
||||||
self.__G_DRIVE_DIR_BASE_DOWNLOAD_URL = "https://drive.google.com/drive/folders/{}"
|
|
||||||
self.__listener = listener
|
self.__listener = listener
|
||||||
self.__service = self.authorize()
|
|
||||||
self._file_uploaded_bytes = 0
|
self._file_uploaded_bytes = 0
|
||||||
self.uploaded_bytes = 0
|
self.uploaded_bytes = 0
|
||||||
self.start_time = 0
|
self.start_time = 0
|
||||||
|
|
@ -74,6 +107,21 @@ class GoogleDriveHelper:
|
||||||
self.uploaded_bytes += chunk_size
|
self.uploaded_bytes += chunk_size
|
||||||
self.total_time += self.update_interval
|
self.total_time += self.update_interval
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __upload_empty_file(path, file_name, mime_type, parent_id=None):
|
||||||
|
media_body = MediaFileUpload(path,
|
||||||
|
mimetype=mime_type,
|
||||||
|
resumable=False)
|
||||||
|
file_metadata = {
|
||||||
|
'name': file_name,
|
||||||
|
'description': 'mirror',
|
||||||
|
'mimeType': mime_type,
|
||||||
|
}
|
||||||
|
if parent_id is not None:
|
||||||
|
file_metadata['parents'] = [parent_id]
|
||||||
|
return service.files().create(supportsTeamDrives=True,
|
||||||
|
body=file_metadata, media_body=media_body).execute()
|
||||||
|
|
||||||
@retry(wait=wait_exponential(multiplier=2, min=3, max=6), stop=stop_after_attempt(5),
|
@retry(wait=wait_exponential(multiplier=2, min=3, max=6), stop=stop_after_attempt(5),
|
||||||
retry=retry_if_exception_type(HttpError), before=before_log(LOGGER, logging.DEBUG))
|
retry=retry_if_exception_type(HttpError), before=before_log(LOGGER, logging.DEBUG))
|
||||||
def __set_permission(self, drive_id):
|
def __set_permission(self, drive_id):
|
||||||
|
|
@ -83,11 +131,13 @@ class GoogleDriveHelper:
|
||||||
'value': None,
|
'value': None,
|
||||||
'withLink': True
|
'withLink': True
|
||||||
}
|
}
|
||||||
return self.__service.permissions().create(supportsTeamDrives=True, fileId=drive_id, body=permissions).execute()
|
return service.permissions().create(supportsTeamDrives=True, fileId=drive_id, body=permissions).execute()
|
||||||
|
|
||||||
@retry(wait=wait_exponential(multiplier=2, min=3, max=6), stop=stop_after_attempt(5),
|
@retry(wait=wait_exponential(multiplier=2, min=3, max=6), stop=stop_after_attempt(5),
|
||||||
retry=retry_if_exception_type(HttpError), before=before_log(LOGGER, logging.DEBUG))
|
retry=retry_if_exception_type(HttpError), before=before_log(LOGGER, logging.DEBUG))
|
||||||
def upload_file(self, file_path, file_name, mime_type, parent_id):
|
def upload_file(self, file_path, file_name, mime_type, parent_id):
|
||||||
|
global SERVICE_ACCOUNT_INDEX
|
||||||
|
global service
|
||||||
# File body description
|
# File body description
|
||||||
file_metadata = {
|
file_metadata = {
|
||||||
'name': file_name,
|
'name': file_name,
|
||||||
|
|
@ -101,13 +151,13 @@ class GoogleDriveHelper:
|
||||||
media_body = MediaFileUpload(file_path,
|
media_body = MediaFileUpload(file_path,
|
||||||
mimetype=mime_type,
|
mimetype=mime_type,
|
||||||
resumable=False)
|
resumable=False)
|
||||||
response = self.__service.files().create(supportsTeamDrives=True,
|
response = service.files().create(supportsTeamDrives=True,
|
||||||
body=file_metadata, media_body=media_body).execute()
|
body=file_metadata, media_body=media_body).execute()
|
||||||
if not IS_TEAM_DRIVE:
|
if not IS_TEAM_DRIVE:
|
||||||
self.__set_permission(response['id'])
|
self.__set_permission(response['id'])
|
||||||
drive_file = self.__service.files().get(supportsTeamDrives=True,
|
drive_file = service.files().get(supportsTeamDrives=True,
|
||||||
fileId=response['id']).execute()
|
fileId=response['id']).execute()
|
||||||
download_url = self.__G_DRIVE_BASE_DOWNLOAD_URL.format(drive_file.get('id'))
|
download_url = self.G_DRIVE_BASE_DOWNLOAD_URL.format(drive_file.get('id'))
|
||||||
return download_url
|
return download_url
|
||||||
media_body = MediaFileUpload(file_path,
|
media_body = MediaFileUpload(file_path,
|
||||||
mimetype=mime_type,
|
mimetype=mime_type,
|
||||||
|
|
@ -115,20 +165,28 @@ class GoogleDriveHelper:
|
||||||
chunksize=50 * 1024 * 1024)
|
chunksize=50 * 1024 * 1024)
|
||||||
|
|
||||||
# Insert a file
|
# Insert a file
|
||||||
drive_file = self.__service.files().create(supportsTeamDrives=True,
|
drive_file = service.files().create(supportsTeamDrives=True,
|
||||||
body=file_metadata, media_body=media_body)
|
body=file_metadata, media_body=media_body)
|
||||||
response = None
|
response = None
|
||||||
while response is None:
|
while response is None:
|
||||||
if self.is_cancelled:
|
if self.is_cancelled:
|
||||||
return None
|
return None
|
||||||
self.status, response = drive_file.next_chunk()
|
try:
|
||||||
|
self.status, response = drive_file.next_chunk()
|
||||||
|
except HttpError as err:
|
||||||
|
if err.resp.get('content-type', '').startswith('application/json'):
|
||||||
|
reason = json.loads(err.content).get('error').get('errors')[0].get('reason')
|
||||||
|
if reason == 'userRateLimitExceeded':
|
||||||
|
SERVICE_ACCOUNT_INDEX += 1
|
||||||
|
service = authorize()
|
||||||
|
raise err
|
||||||
self._file_uploaded_bytes = 0
|
self._file_uploaded_bytes = 0
|
||||||
# Insert new permissions
|
# Insert new permissions
|
||||||
if not IS_TEAM_DRIVE:
|
if not IS_TEAM_DRIVE:
|
||||||
self.__set_permission(response['id'])
|
self.__set_permission(response['id'])
|
||||||
# Define file instance and get url for download
|
# Define file instance and get url for download
|
||||||
drive_file = self.__service.files().get(supportsTeamDrives=True, fileId=response['id']).execute()
|
drive_file = service.files().get(supportsTeamDrives=True, fileId=response['id']).execute()
|
||||||
download_url = self.__G_DRIVE_BASE_DOWNLOAD_URL.format(drive_file.get('id'))
|
download_url = self.G_DRIVE_BASE_DOWNLOAD_URL.format(drive_file.get('id'))
|
||||||
return download_url
|
return download_url
|
||||||
|
|
||||||
def upload(self, file_name: str):
|
def upload(self, file_name: str):
|
||||||
|
|
@ -249,11 +307,11 @@ class GoogleDriveHelper:
|
||||||
def create_directory(self, directory_name, parent_id):
|
def create_directory(self, directory_name, parent_id):
|
||||||
file_metadata = {
|
file_metadata = {
|
||||||
"name": directory_name,
|
"name": directory_name,
|
||||||
"mimeType": self.__G_DRIVE_DIR_MIME_TYPE
|
"mimeType": self.G_DRIVE_DIR_MIME_TYPE
|
||||||
}
|
}
|
||||||
if parent_id is not None:
|
if parent_id is not None:
|
||||||
file_metadata["parents"] = [parent_id]
|
file_metadata["parents"] = [parent_id]
|
||||||
file = self.__service.files().create(supportsTeamDrives=True, body=file_metadata).execute()
|
file = service.files().create(supportsTeamDrives=True, body=file_metadata).execute()
|
||||||
file_id = file.get("id")
|
file_id = file.get("id")
|
||||||
if not IS_TEAM_DRIVE:
|
if not IS_TEAM_DRIVE:
|
||||||
self.__set_permission(file_id)
|
self.__set_permission(file_id)
|
||||||
|
|
@ -280,26 +338,6 @@ class GoogleDriveHelper:
|
||||||
new_id = parent_id
|
new_id = parent_id
|
||||||
return new_id
|
return new_id
|
||||||
|
|
||||||
def authorize(self):
|
|
||||||
# Get credentials
|
|
||||||
credentials = None
|
|
||||||
if os.path.exists(self.__G_DRIVE_TOKEN_FILE):
|
|
||||||
with open(self.__G_DRIVE_TOKEN_FILE, 'rb') as f:
|
|
||||||
credentials = pickle.load(f)
|
|
||||||
if credentials is None or not credentials.valid:
|
|
||||||
if credentials and credentials.expired and credentials.refresh_token:
|
|
||||||
credentials.refresh(Request())
|
|
||||||
else:
|
|
||||||
flow = InstalledAppFlow.from_client_secrets_file(
|
|
||||||
'credentials.json', self.__OAUTH_SCOPE)
|
|
||||||
LOGGER.info(flow)
|
|
||||||
credentials = flow.run_console(port=0)
|
|
||||||
|
|
||||||
# Save the credentials for the next run
|
|
||||||
with open(self.__G_DRIVE_TOKEN_FILE, 'wb') as token:
|
|
||||||
pickle.dump(credentials, token)
|
|
||||||
return build('drive', 'v3', credentials=credentials, cache_discovery=False)
|
|
||||||
|
|
||||||
def drive_list(self, fileName):
|
def drive_list(self, fileName):
|
||||||
msg = ""
|
msg = ""
|
||||||
# Create Search Query for API request.
|
# Create Search Query for API request.
|
||||||
|
|
@ -307,13 +345,13 @@ class GoogleDriveHelper:
|
||||||
page_token = None
|
page_token = None
|
||||||
results = []
|
results = []
|
||||||
while True:
|
while True:
|
||||||
response = self.__service.files().list(supportsTeamDrives=True,
|
response = service.files().list(supportsTeamDrives=True,
|
||||||
includeTeamDriveItems=True,
|
includeTeamDriveItems=True,
|
||||||
q=query,
|
q=query,
|
||||||
spaces='drive',
|
spaces='drive',
|
||||||
fields='nextPageToken, files(id, name, mimeType, size)',
|
fields='nextPageToken, files(id, name, mimeType, size)',
|
||||||
pageToken=page_token,
|
pageToken=page_token,
|
||||||
orderBy='modifiedTime desc').execute()
|
orderBy='modifiedTime desc').execute()
|
||||||
for file in response.get('files', []):
|
for file in response.get('files', []):
|
||||||
if len(results) >= 20:
|
if len(results) >= 20:
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -12,4 +12,5 @@ IS_TEAM_DRIVE = ""
|
||||||
INDEX_URL = ""
|
INDEX_URL = ""
|
||||||
USER_SESSION_STRING = ""
|
USER_SESSION_STRING = ""
|
||||||
TELEGRAM_API =
|
TELEGRAM_API =
|
||||||
TELEGRAM_HASH = ""
|
TELEGRAM_HASH = ""
|
||||||
|
USE_SERVICE_ACCOUNTS = ""
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,365 @@
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import sys
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from base64 import b64decode
|
||||||
|
from glob import glob
|
||||||
|
from json import loads
|
||||||
|
from random import choice
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from google.auth.transport.requests import Request
|
||||||
|
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||||
|
from googleapiclient.discovery import build
|
||||||
|
from googleapiclient.errors import HttpError
|
||||||
|
|
||||||
|
SCOPES = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/cloud-platform',
|
||||||
|
'https://www.googleapis.com/auth/iam']
|
||||||
|
project_create_ops = []
|
||||||
|
current_key_dump = []
|
||||||
|
sleep_time = 30
|
||||||
|
|
||||||
|
|
||||||
|
# Create count SAs in project
|
||||||
|
def _create_accounts(service, project, count):
|
||||||
|
batch = service.new_batch_http_request(callback=_def_batch_resp)
|
||||||
|
for i in range(count):
|
||||||
|
aid = _generate_id('mfc-')
|
||||||
|
batch.add(service.projects().serviceAccounts().create(name='projects/' + project, body={'accountId': aid,
|
||||||
|
'serviceAccount': {
|
||||||
|
'displayName': aid}}))
|
||||||
|
batch.execute()
|
||||||
|
|
||||||
|
|
||||||
|
# Create accounts needed to fill project
|
||||||
|
def _create_remaining_accounts(iam, project):
|
||||||
|
print('Creating accounts in %s' % project)
|
||||||
|
sa_count = len(_list_sas(iam, project))
|
||||||
|
while sa_count != 100:
|
||||||
|
_create_accounts(iam, project, 100 - sa_count)
|
||||||
|
sa_count = len(_list_sas(iam, project))
|
||||||
|
|
||||||
|
|
||||||
|
# Generate a random id
|
||||||
|
def _generate_id(prefix='saf-'):
|
||||||
|
chars = '-abcdefghijklmnopqrstuvwxyz1234567890'
|
||||||
|
return prefix + ''.join(choice(chars) for _ in range(25)) + choice(chars[1:])
|
||||||
|
|
||||||
|
|
||||||
|
# List projects using service
|
||||||
|
def _get_projects(service):
|
||||||
|
return [i['projectId'] for i in service.projects().list().execute()['projects']]
|
||||||
|
|
||||||
|
|
||||||
|
# Default batch callback handler
|
||||||
|
def _def_batch_resp(id, resp, exception):
|
||||||
|
if exception is not None:
|
||||||
|
if str(exception).startswith('<HttpError 429'):
|
||||||
|
sleep(sleep_time / 100)
|
||||||
|
else:
|
||||||
|
print(str(exception))
|
||||||
|
|
||||||
|
|
||||||
|
# Project Creation Batch Handler
|
||||||
|
def _pc_resp(id, resp, exception):
|
||||||
|
global project_create_ops
|
||||||
|
if exception is not None:
|
||||||
|
print(str(exception))
|
||||||
|
else:
|
||||||
|
for i in resp.values():
|
||||||
|
project_create_ops.append(i)
|
||||||
|
|
||||||
|
|
||||||
|
# Project Creation
|
||||||
|
def _create_projects(cloud, count):
|
||||||
|
global project_create_ops
|
||||||
|
batch = cloud.new_batch_http_request(callback=_pc_resp)
|
||||||
|
new_projs = []
|
||||||
|
for i in range(count):
|
||||||
|
new_proj = _generate_id()
|
||||||
|
new_projs.append(new_proj)
|
||||||
|
batch.add(cloud.projects().create(body={'project_id': new_proj}))
|
||||||
|
batch.execute()
|
||||||
|
|
||||||
|
for i in project_create_ops:
|
||||||
|
while True:
|
||||||
|
resp = cloud.operations().get(name=i).execute()
|
||||||
|
if 'done' in resp and resp['done']:
|
||||||
|
break
|
||||||
|
sleep(3)
|
||||||
|
return new_projs
|
||||||
|
|
||||||
|
|
||||||
|
# Enable services ste for projects in projects
|
||||||
|
def _enable_services(service, projects, ste):
|
||||||
|
batch = service.new_batch_http_request(callback=_def_batch_resp)
|
||||||
|
for i in projects:
|
||||||
|
for j in ste:
|
||||||
|
batch.add(service.services().enable(name='projects/%s/services/%s' % (i, j)))
|
||||||
|
batch.execute()
|
||||||
|
|
||||||
|
|
||||||
|
# List SAs in project
|
||||||
|
def _list_sas(iam, project):
|
||||||
|
resp = iam.projects().serviceAccounts().list(name='projects/' + project, pageSize=100).execute()
|
||||||
|
if 'accounts' in resp:
|
||||||
|
return resp['accounts']
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
# Create Keys Batch Handler
|
||||||
|
def _batch_keys_resp(id, resp, exception):
|
||||||
|
global current_key_dump
|
||||||
|
if exception is not None:
|
||||||
|
current_key_dump = None
|
||||||
|
sleep(sleep_time / 100)
|
||||||
|
elif current_key_dump is None:
|
||||||
|
sleep(sleep_time / 100)
|
||||||
|
else:
|
||||||
|
current_key_dump.append((
|
||||||
|
resp['name'][resp['name'].rfind('/'):],
|
||||||
|
b64decode(resp['privateKeyData']).decode('utf-8')
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
# Create Keys
|
||||||
|
def _create_sa_keys(iam, projects, path):
|
||||||
|
global current_key_dump
|
||||||
|
for i in projects:
|
||||||
|
current_key_dump = []
|
||||||
|
print('Downloading keys from %s' % i)
|
||||||
|
while current_key_dump is None or len(current_key_dump) != 100:
|
||||||
|
batch = iam.new_batch_http_request(callback=_batch_keys_resp)
|
||||||
|
total_sas = _list_sas(iam, i)
|
||||||
|
for j in total_sas:
|
||||||
|
batch.add(iam.projects().serviceAccounts().keys().create(
|
||||||
|
name='projects/%s/serviceAccounts/%s' % (i, j['uniqueId']),
|
||||||
|
body={
|
||||||
|
'privateKeyType': 'TYPE_GOOGLE_CREDENTIALS_FILE',
|
||||||
|
'keyAlgorithm': 'KEY_ALG_RSA_2048'
|
||||||
|
}
|
||||||
|
))
|
||||||
|
batch.execute()
|
||||||
|
if current_key_dump is None:
|
||||||
|
print('Redownloading keys from %s' % i)
|
||||||
|
current_key_dump = []
|
||||||
|
else:
|
||||||
|
index = 0
|
||||||
|
for j in current_key_dump:
|
||||||
|
with open(f'{path}/{index}.json', 'w+') as f:
|
||||||
|
f.write(j[1])
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
|
||||||
|
# Delete Service Accounts
|
||||||
|
def _delete_sas(iam, project):
|
||||||
|
sas = _list_sas(iam, project)
|
||||||
|
batch = iam.new_batch_http_request(callback=_def_batch_resp)
|
||||||
|
for i in sas:
|
||||||
|
batch.add(iam.projects().serviceAccounts().delete(name=i['name']))
|
||||||
|
batch.execute()
|
||||||
|
|
||||||
|
|
||||||
|
def serviceaccountfactory(
|
||||||
|
credentials='credentials.json',
|
||||||
|
token='token.pickle',
|
||||||
|
path=None,
|
||||||
|
list_projects=False,
|
||||||
|
list_sas=None,
|
||||||
|
create_projects=None,
|
||||||
|
max_projects=12,
|
||||||
|
enable_services=None,
|
||||||
|
services=['iam', 'drive'],
|
||||||
|
create_sas=None,
|
||||||
|
delete_sas=None,
|
||||||
|
download_keys=None
|
||||||
|
):
|
||||||
|
selected_projects = []
|
||||||
|
proj_id = loads(open(credentials, 'r').read())['installed']['project_id']
|
||||||
|
creds = None
|
||||||
|
if os.path.exists(token):
|
||||||
|
with open(token, 'rb') as t:
|
||||||
|
creds = pickle.load(t)
|
||||||
|
if not creds or not creds.valid:
|
||||||
|
if creds and creds.expired and creds.refresh_token:
|
||||||
|
creds.refresh(Request())
|
||||||
|
else:
|
||||||
|
flow = InstalledAppFlow.from_client_secrets_file(credentials, SCOPES)
|
||||||
|
|
||||||
|
# creds = flow.run_local_server(port=0)
|
||||||
|
creds = flow.run_console()
|
||||||
|
|
||||||
|
with open(token, 'wb') as t:
|
||||||
|
pickle.dump(creds, t)
|
||||||
|
|
||||||
|
cloud = build('cloudresourcemanager', 'v1', credentials=creds)
|
||||||
|
iam = build('iam', 'v1', credentials=creds)
|
||||||
|
serviceusage = build('serviceusage', 'v1', credentials=creds)
|
||||||
|
|
||||||
|
projs = None
|
||||||
|
while projs == None:
|
||||||
|
try:
|
||||||
|
projs = _get_projects(cloud)
|
||||||
|
except HttpError as e:
|
||||||
|
if loads(e.content.decode('utf-8'))['error']['status'] == 'PERMISSION_DENIED':
|
||||||
|
try:
|
||||||
|
serviceusage.services().enable(
|
||||||
|
name='projects/%s/services/cloudresourcemanager.googleapis.com' % proj_id).execute()
|
||||||
|
except HttpError as e:
|
||||||
|
print(e._get_reason())
|
||||||
|
input('Press Enter to retry.')
|
||||||
|
if list_projects:
|
||||||
|
return _get_projects(cloud)
|
||||||
|
if list_sas:
|
||||||
|
return _list_sas(iam, list_sas)
|
||||||
|
if create_projects:
|
||||||
|
print("creat projects: {}".format(create_projects))
|
||||||
|
if create_projects > 0:
|
||||||
|
current_count = len(_get_projects(cloud))
|
||||||
|
if current_count + create_projects <= max_projects:
|
||||||
|
print('Creating %d projects' % (create_projects))
|
||||||
|
nprjs = _create_projects(cloud, create_projects)
|
||||||
|
selected_projects = nprjs
|
||||||
|
else:
|
||||||
|
sys.exit('No, you cannot create %d new project (s).\n'
|
||||||
|
'Please reduce value of --quick-setup.\n'
|
||||||
|
'Remember that you can totally create %d projects (%d already).\n'
|
||||||
|
'Please do not delete existing projects unless you know what you are doing' % (
|
||||||
|
create_projects, max_projects, current_count))
|
||||||
|
else:
|
||||||
|
print('Will overwrite all service accounts in existing projects.\n'
|
||||||
|
'So make sure you have some projects already.')
|
||||||
|
input("Press Enter to continue...")
|
||||||
|
|
||||||
|
if enable_services:
|
||||||
|
ste = []
|
||||||
|
ste.append(enable_services)
|
||||||
|
if enable_services == '~':
|
||||||
|
ste = selected_projects
|
||||||
|
elif enable_services == '*':
|
||||||
|
ste = _get_projects(cloud)
|
||||||
|
services = [i + '.googleapis.com' for i in services]
|
||||||
|
print('Enabling services')
|
||||||
|
_enable_services(serviceusage, ste, services)
|
||||||
|
if create_sas:
|
||||||
|
stc = []
|
||||||
|
stc.append(create_sas)
|
||||||
|
if create_sas == '~':
|
||||||
|
stc = selected_projects
|
||||||
|
elif create_sas == '*':
|
||||||
|
stc = _get_projects(cloud)
|
||||||
|
for i in stc:
|
||||||
|
_create_remaining_accounts(iam, i)
|
||||||
|
if download_keys:
|
||||||
|
try:
|
||||||
|
os.mkdir(path)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.EEXIST:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
std = []
|
||||||
|
std.append(download_keys)
|
||||||
|
if download_keys == '~':
|
||||||
|
std = selected_projects
|
||||||
|
elif download_keys == '*':
|
||||||
|
std = _get_projects(cloud)
|
||||||
|
_create_sa_keys(iam, std, path)
|
||||||
|
if delete_sas:
|
||||||
|
std = []
|
||||||
|
std.append(delete_sas)
|
||||||
|
if delete_sas == '~':
|
||||||
|
std = selected_projects
|
||||||
|
elif delete_sas == '*':
|
||||||
|
std = _get_projects(cloud)
|
||||||
|
for i in std:
|
||||||
|
print('Deleting service accounts in %s' % i)
|
||||||
|
_delete_sas(iam, i)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parse = ArgumentParser(description='A tool to create Google service accounts.')
|
||||||
|
parse.add_argument('--path', '-p', default='accounts',
|
||||||
|
help='Specify an alternate directory to output the credential files.')
|
||||||
|
parse.add_argument('--token', default='token.pickle', help='Specify the pickle token file path.')
|
||||||
|
parse.add_argument('--credentials', default='credentials.json', help='Specify the credentials file path.')
|
||||||
|
parse.add_argument('--list-projects', default=False, action='store_true',
|
||||||
|
help='List projects viewable by the user.')
|
||||||
|
parse.add_argument('--list-sas', default=False, help='List service accounts in a project.')
|
||||||
|
parse.add_argument('--create-projects', type=int, default=None, help='Creates up to N projects.')
|
||||||
|
parse.add_argument('--max-projects', type=int, default=12, help='Max amount of project allowed. Default: 12')
|
||||||
|
parse.add_argument('--enable-services', default=None,
|
||||||
|
help='Enables services on the project. Default: IAM and Drive')
|
||||||
|
parse.add_argument('--services', nargs='+', default=['iam', 'drive'],
|
||||||
|
help='Specify a different set of services to enable. Overrides the default.')
|
||||||
|
parse.add_argument('--create-sas', default=None, help='Create service accounts in a project.')
|
||||||
|
parse.add_argument('--delete-sas', default=None, help='Delete service accounts in a project.')
|
||||||
|
parse.add_argument('--download-keys', default=None, help='Download keys for all the service accounts in a project.')
|
||||||
|
parse.add_argument('--quick-setup', default=None, type=int,
|
||||||
|
help='Create projects, enable services, create service accounts and download keys. ')
|
||||||
|
parse.add_argument('--new-only', default=False, action='store_true', help='Do not use exisiting projects.')
|
||||||
|
args = parse.parse_args()
|
||||||
|
# If credentials file is invalid, search for one.
|
||||||
|
if not os.path.exists(args.credentials):
|
||||||
|
options = glob('*.json')
|
||||||
|
print('No credentials found at %s. Please enable the Drive API in:\n'
|
||||||
|
'https://developers.google.com/drive/api/v3/quickstart/python\n'
|
||||||
|
'and save the json file as credentials.json' % args.credentials)
|
||||||
|
if len(options) < 1:
|
||||||
|
exit(-1)
|
||||||
|
else:
|
||||||
|
i = 0
|
||||||
|
print('Select a credentials file below.')
|
||||||
|
inp_options = [str(i) for i in list(range(1, len(options) + 1))] + options
|
||||||
|
while i < len(options):
|
||||||
|
print(' %d) %s' % (i + 1, options[i]))
|
||||||
|
i += 1
|
||||||
|
inp = None
|
||||||
|
while True:
|
||||||
|
inp = input('> ')
|
||||||
|
if inp in inp_options:
|
||||||
|
break
|
||||||
|
if inp in options:
|
||||||
|
args.credentials = inp
|
||||||
|
else:
|
||||||
|
args.credentials = options[int(inp) - 1]
|
||||||
|
print('Use --credentials %s next time to use this credentials file.' % args.credentials)
|
||||||
|
if args.quick_setup:
|
||||||
|
opt = '*'
|
||||||
|
if args.new_only:
|
||||||
|
opt = '~'
|
||||||
|
args.services = ['iam', 'drive']
|
||||||
|
args.create_projects = args.quick_setup
|
||||||
|
args.enable_services = opt
|
||||||
|
args.create_sas = opt
|
||||||
|
args.download_keys = opt
|
||||||
|
resp = serviceaccountfactory(
|
||||||
|
path=args.path,
|
||||||
|
token=args.token,
|
||||||
|
credentials=args.credentials,
|
||||||
|
list_projects=args.list_projects,
|
||||||
|
list_sas=args.list_sas,
|
||||||
|
create_projects=args.create_projects,
|
||||||
|
max_projects=args.max_projects,
|
||||||
|
create_sas=args.create_sas,
|
||||||
|
delete_sas=args.delete_sas,
|
||||||
|
enable_services=args.enable_services,
|
||||||
|
services=args.services,
|
||||||
|
download_keys=args.download_keys
|
||||||
|
)
|
||||||
|
if resp is not None:
|
||||||
|
if args.list_projects:
|
||||||
|
if resp:
|
||||||
|
print('Projects (%d):' % len(resp))
|
||||||
|
for i in resp:
|
||||||
|
print(' ' + i)
|
||||||
|
else:
|
||||||
|
print('No projects.')
|
||||||
|
elif args.list_sas:
|
||||||
|
if resp:
|
||||||
|
print('Service accounts in %s (%d):' % (args.list_sas, len(resp)))
|
||||||
|
for i in resp:
|
||||||
|
print(' %s (%s)' % (i['email'], i['uniqueId']))
|
||||||
|
else:
|
||||||
|
print('No service accounts.')
|
||||||
Loading…
Reference in New Issue