Commit f38fef4e authored by Rob Carleski's avatar Rob Carleski 🇮🇸
Browse files

Initial commit of collab-admin-kit

parents
__pycache__
venv
.eggs
Collab_Admin_Kit.egg-info
---
image: python:3.7
stages:
- test
test:
stage: test
before_script:
- pip install virtualenv
- ./init.sh
- export PATH=$PATH:./tests
script:
- ./venv/bin/python setup.py pytest
## 1.0.0 (2019-02-07)
- Initial stable release.
Copyright (c) 2019 Regents of The University of Michigan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
include CHANGELOG.md
include COPYING
include VERSION
include tests/data/*
include version.sh
dist_bin_SCRIPTS = google_create_resource.py google_sift_drive.py google_transfer_drive.py google_sift_labels.py google_transfer_mail.pu google_check_status.py
EXTRA_DIST = packaging/rpm/collab-admin-kit.spec
rpm: dist-xz
rpmbuild -ta $(distdir).tar.xz
# collab-admin-kit
Collab Admin Kit is a package of python scripts for performing various adminstrative actions
in the University of Michigan collaboration space.
## Setting up your environment
collab-admin-kit requires Python 3.
For development, we recommend using a virtualenv.
* Run `init.sh` to set up the virtualenv.
* When you want to hack on collab-admin-kit, source env-setup.sh
(`. env-setup.sh`) to set up the necessary environment variables.
## Testing
Tests can be run with `python setup.py test`.
collab-admin-kit uses the [pytest](https://pytest.org/) testing framework, so
tests can also be run directly with the command `pytest`.
#!/usr/bin/env python
import argparse
import boxsdk
import kadmin
import logging
import logging.handlers
import mcommunity
import os
import re
import yaml
from boxsdk.object.collaboration import CollaborationRole
from subprocess import check_output as cmd
from subprocess import CalledProcessError
from time import sleep
class SharedAccount:
def __init__(self, args, config):
self.__dict__.update(args)
self.config = config
self.create_extra_attr()
def create_box(self):
try:
auth = boxsdk.JWTAuth.from_settings_file(
self.config['box']['auth_file']
)
boxClient = boxsdk.Client(auth)
except IOError as e:
logging.error(e, extra={'entity': self.account})
exit(2)
except boxsdk.exception.BoxAPIException as e:
logging.error(e, extra={'entity': self.account})
exit(2)
try:
if len(self.full_name) > 50:
logging.warning(
'Full name attribute for user exceeds limit. Truncating.',
extra={'entity': self.account}
)
boxAccount = boxClient.create_user(
name=self.full_name[:50],
login=self.email,
space_amount=-1,
tracking_codes={
'name': 'account_type',
'value': 'shared'
}
)
boxAccount.update_info({'is_sync_enabled': False})
self.shared_box_folder = boxClient.as_user(
boxAccount
).folder(
0
).create_subfolder(
self.full_name
)
self.box_text = '''
Box-Specific Information:
- The ability to use Box Sync with shared accounts is disabled by default,
but allowed in certain circumstances; if you need this, please reply to this
message to discuss. Syncing the shared folder in your individual account is
always allowed.
- Files placed in shared account folders are owned by the shared account.
- To learn more about using your shared account, visit the Shared U-M Box
Accounts section of the M+Box support site.
(http://www.itcs.umich.edu/storage/box/shared-accounts.php)
'''
if self.secure:
self.shared_box_folder.update_info({
'can_non_owners_invite': False,
'allowed_shared_link_access_levels': ['collaborators']
})
self.box_text += '''
- Settings appropriate for sensitive data have been applied to this folder.
'''
self.upload_box_info_file()
for owner in self.owners:
search = boxClient.users(filter_term=owner)
for _ in search:
login = _['login'].split('@')[0].lower()
if owner.lower() == login:
try:
self.shared_box_folder.collaborate(
_,
CollaborationRole.CO_OWNER
)
except boxsdk.exception.BoxAPIException as e:
logging.warning(
'Unable to add {} as Box collaborator'.format(
_
),
extra={'entity': self.account}
)
break
except boxsdk.exception.BoxAPIException as e:
logging.error(e, extra={'entity': self.account})
def upload_box_info_file(self):
try:
readme = '{}/{}-{}'.format(
self.config['general']['data_dir'],
self.account,
'README.txt'
)
with open(readme, 'w+') as stream:
content = '''
The folder "{}" is owned by the shared account "{}".
The initial co-owners on this folder are based on the owners listed on the
request form at the time this folder was created.
The co-owners and members will NOT be kept in sync with MCommunity.
f you have any questions please contact the ITS Service Center:
http://its.umich.edu/help/'''.format(self.full_name, self.email)
stream.write(content)
self.shared_box_folder.upload(readme)
os.remove(readme)
except IOError as e:
logging.error(e, extra={'entity': self.account})
except boxsdk.exception.BoxAPIException as e:
logging.error(e, extra={'entity': self.account})
def create_google(self):
try:
cmd([
self.config['google']['gam_command'],
'create',
'user',
self.account,
'firstname',
self.first_name,
'lastname',
self.last_name,
'password',
self.password,
'org',
'Department accounts'
])
except CalledProcessError as e:
logging.error(e, extra={'entity': self.account})
self.google_text = '''
Google-Specific Information:
- For information on using this account, please check the following URL:
https://documentation.its.umich.edu/node/339/
'''
def set_up_mcommunity_group(self):
mcommClient = mcommunity.Client(self.config['mcommunity'])
try:
mcommClient.fetch_group(self.account)
controllerCn = mcommClient._create_entity_ldap(
self.config['mcommunity']['api_control_group']
)
print(mcommClient.group_data)
if controllerCn not in mcommClient.group_data['ownerDn']:
if self.take_group_ownership(self.account):
mcommClient.fetch_group(self.account)
logging.debug(
'Waiting 10 seconds for replication.',
extra={'entity': self.account}
)
sleep(1)
else:
logging.error(
'Failed to obtain group ownership.',
extra={'entity': self.account}
)
exit(2)
except Exception:
mcommClient.reserve_group(self.account)
logging.debug(
'Waiting 10 seconds for replication.',
extra={'entity': self.account}
)
sleep(1)
mcommClient.add_group_owners(self.owners)
try:
if self.services in ['google', 'both']:
email = self.account + '@go.itd.umich.edu'
mcommClient.add_group_members(email)
mcommClient.remove_group_owners('collab-api-client')
except Exception as e:
logging.warning(e, extra={'entity': self.account})
def take_group_ownership(self, group):
input('Add api controller as group owner, then press enter.')
return True
def set_kerberos_password(self):
try:
admin = '{}@{}'.format(
self.config['kerberos']['admin'],
self.config['kerberos']['realm'].upper()
)
kadm = kadmin.init_with_keytab(
admin,
self.config['kerberos']['keytab']
)
kadm.ank(
'{}@{}'.format(
self.account,
self.config['kerberos']['realm'].upper()
),
self.password
)
except kadmin.KAdminError as e:
logging.error(e, extra={'entity': self.account})
exit(2)
def upload_and_share_password(self):
passFilePath = '{}/{}-passwd'.format(
self.config['general']['data_dir'],
self.account
)
with open(passFilePath, 'w') as passwordFile:
passwordFile.write(self.password)
upload_output = cmd([
self.config['google']['gam_command'],
'user',
self.config['google']['admin_account'],
'add',
'drivefile',
'localfile',
passFilePath,
'parentname',
'Shared Account Passwords'
])
file_id = re.search(
r'.*\((.*)\)',
upload_output.decode('UTF-8')
).groups()[0]
for owner in self.owners:
try:
cmd([
self.config['google']['gam_command'],
'user',
self.config['google']['admin_account'],
'add',
'drivefileacl',
file_id,
'user',
owner,
'role',
'reader'
])
except CalledProcessError as e:
logging.warning(e.output, extra={'entity': self.account})
continue
os.remove(passFilePath)
return file_id
def create_extra_attr(self):
try:
self.full_name = self.first_name + self.last_name
except AttributeError:
split = re.split(r'[\-\_\.\s]+', self.account)
self.first_name = ' '.join(split[:len(split)//2]).title()
self.last_name = ' '.join(split[len(split)//2:]).title()
self.full_name = '{} {}'.format(
self.first_name,
self.last_name
)
self.email = '{}@{}'.format(
self.account,
self.config['google']['domain']
)
def main():
helptext = '''examples:
collab-create-shared -a collab-testing -p pass12word -o mlkjr moliere
collab-create-shared -a collab-testing -f collab -l testing -t google
-p pass12word -o mlkjr moliere -s
collab-create-shared --account collab-testing --password pass12word
--owners mlkjr moliere
collab-create-shared --acount collab-testing --password pass12word
--owners mlkdir moliere --first_name collab --last_name testing
--type box --secure
Note: If first and last names are not provided, they will be set
automatically using common delimiters
'''
parser = argparse.ArgumentParser(
description='Creates shared accounts in collab services',
epilog=helptext,
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'--account',
'-a',
help='The account name to use.',
required=True
)
parser.add_argument(
'--password',
'-p',
help='The password to use for the account',
required=True
)
parser.add_argument(
'--owners',
'-o',
help='Owners for the shared account',
nargs='+'
)
parser.add_argument(
'--services',
'-s',
help='The type of account(s) to create',
choices=[
'google',
'box',
'both'
],
default='both'
)
parser.add_argument(
'--first_name',
'-f',
help='The first name to use for the account',
required=True
)
parser.add_argument(
'--last_name',
'-l',
help='The last name to use for the account',
required=True
)
parser.add_argument(
'--secure',
help='Secure the account for sensitive data',
action='store_true'
)
parser.add_argument(
'--config',
'-c',
help='The CAK config to use for Google operations',
default='/etc/collab-admin-kit.yml'
)
args = parser.parse_args()
# Open the CAK Config
with open(args.config) as stream:
config = yaml.load(stream, Loader=yaml.BaseLoader)
# Get the root logger and set the debug level
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# Create a syslog handler, set format, and associate.
sh = logging.handlers.SysLogHandler(
address='/dev/log',
facility=config['general']['log_facility']
)
formatter = logging.Formatter(config['general']['log_format'])
sh.setFormatter(formatter)
logger.addHandler(sh)
# Create a console handler, set format, and associate.
ch = logging.StreamHandler()
formatter = logging.Formatter(config['general']['console_format'])
ch.setFormatter(formatter)
logger.addHandler(ch)
sa = SharedAccount(args, config)
sa.set_up_mcommunity_group()
sa.set_kerberos_password()
if sa.service in ['box', 'both']:
sa.create_box()
if sa.service in ['google', 'both']:
sa.create_google()
sa.upload_and_share_password()
header_text = '''
Hello,
Your request has been completed. The password for your shared account
can be found at the following link:
{}
We recommend using a Chrome incognito window or separate browser to log
into your shared account. Please see below for more information about the
shared account created per your request:
General Account Information:
Display Name: {}
Username: {}
Email Address: {}
'''.format(
'https://docs.google.com/a/umich.edu/file/d/' + sa.pass_file_id + '/',
sa.full_name,
sa.account,
sa.email,
)
footer_text = '''
Single-Sign On is used for both Box and Google, and the method of changing
the password is the same for both. For information on changing the password,
please check the documentation found here:
https://documentation.its.umich.edu/node/339/#password
I will be closing this ticket now, as your request has been completed, but
feel free to reply back to this email if you have any questions or concerns
about your new account and the ticket will be re-opened.
'''
print(
'========== CANNED TEXT ==========',
header_text,
sa.box_text,
sa.google_text,
footer_text
)
if __name__ == '__main__':
main()
#!/usr/bin/env python
import argparse
import boxsdk
import kadmin
import logging
import logging.handlers
import mcommunity
import os
import re
import yaml
from subprocess import check_output as cmd
from subprocess import CalledProcessError
from subprocess import STDOUT
class SharedAccount():
def __init__(self, account, password, config):
self.account = account
self.password = password
self.config = config
self.box_exists = False
self.google_exists = False
self.group_data = {}
def check_box(self):
try:
logging.error('checking!')
auth = boxsdk.JWTAuth.from_settings_file(
self.config['box']['auth_file']
)
boxClient = boxsdk.Client(auth)
boxUsers = boxClient.users(filter_term=self.account)
for _ in boxUsers:
login = _['login'].split('@')[0]
if self.account.lower() == login.lower():
self.box_exists = True
break
return False
except IOError as e:
logging.error(e, extras={'entity': self.account})
except boxsdk.exception.BoxAPIException as e:
logging.error(e, extras={'entity': self.account})
def check_google(self):
try:
output = cmd([
self.config['google']['gam_command'],
'whatis',
self.account,
'userview'
], stderr=STDOUT
)
if 'is a user' in output.decode('UTF-8'):
self.google_exists = True
return False
except CalledProcessError:
return False
def check_mcommunity(self):
try:
group = mcommunity.Client(self.config['mcommunity'])
group.fetch_group(self.account)
self.group_data = group.group_data
except yaml.parser.ParserError as e:
logging.error(e, extras={'entity': self.account})
exit(2)
except Exception as e:
logging.error(e, extras={'entity': self.account})
exit(2)
def set_kerberos_password(self):
try:
admin = '{}@{}'.format(
self.config['kerberos']['admin'],
self.config['kerberos']['realm'].upper()
)
kadm = kadmin.init_with_keytab(
admin,
self.config['kerberos']['keytab']
)
principal = self.account + self.config['kerberos']['realm'].upper()
princ = kadm.get_princ(principal)
princ.change_password(self.password)
except IOError as e:
logging.error(e, extras={'entity': self.account})
exit(2)
except kadmin.KAdminError as e:
logging.error(e, extras={'entity': self.account})
exit(2)
def set_google_password(self):
try: