parent
d7551eaf5c
commit
fa9c2b33d5
@ -0,0 +1,24 @@ |
||||
from flask import Blueprint |
||||
from zope import component |
||||
|
||||
from .utils import requires_authentication_no_realm, authz_restrict_to_syscom |
||||
from ceo_common.interfaces import ICloudService, ILDAPService |
||||
|
||||
bp = Blueprint('cloud', __name__) |
||||
|
||||
|
||||
@bp.route('/accounts/create', methods=['POST']) |
||||
@requires_authentication_no_realm |
||||
def create_account(auth_user: str): |
||||
cloud_srv = component.getUtility(ICloudService) |
||||
ldap_srv = component.getUtility(ILDAPService) |
||||
user = ldap_srv.get_user(auth_user) |
||||
cloud_srv.create_account(user) |
||||
return {'status': 'OK'} |
||||
|
||||
|
||||
@bp.route('/accounts/purge', methods=['POST']) |
||||
@authz_restrict_to_syscom |
||||
def purge_accounts(): |
||||
cloud_srv = component.getUtility(ICloudService) |
||||
return cloud_srv.purge_accounts() |
@ -0,0 +1,14 @@ |
||||
Hello {{ user.given_name }}, |
||||
|
||||
This is an automated message from ceo, the CSC Electronic Office. |
||||
|
||||
Your club membership has expired, so your CSC Cloud account |
||||
has been deleted. If you decide to renew your membership, you |
||||
may create a new cloud account, but it will not have any of the |
||||
resources from your old cloud account. |
||||
|
||||
If you have any questions or concerns, please contact the Systems |
||||
Committee: syscom@csclub.uwaterloo.ca |
||||
|
||||
Best regards, |
||||
ceo |
@ -0,0 +1,18 @@ |
||||
Hello {{ user.given_name }}, |
||||
|
||||
This is an automated message from ceo, the CSC Electronic Office. |
||||
|
||||
Your club membership has expired, and you have an active account in |
||||
the CSC Cloud (https://cloud.csclub.uwaterloo.ca). All of your cloud |
||||
resources (VMs, templates, DNS records, etc.) will be permanently |
||||
deleted if your membership is not renewed in one week's time. |
||||
|
||||
If you wish to keep your cloud resources, please renew your club |
||||
membership before next week. If you do not wish to keep your cloud |
||||
resources, then you may safely ignore this message. |
||||
|
||||
If you have any questions or concerns, please contact the Systems |
||||
Committee: syscom@csclub.uwaterloo.ca |
||||
|
||||
Best regards, |
||||
ceo |
@ -0,0 +1,160 @@ |
||||
from uuid import uuid4 |
||||
|
||||
from aiohttp import web |
||||
|
||||
from .MockHTTPServerBase import MockHTTPServerBase |
||||
|
||||
|
||||
def gen_uuid(): |
||||
return str(uuid4()) |
||||
|
||||
|
||||
class MockCloudStackServer(MockHTTPServerBase): |
||||
def __init__(self, port=8080): |
||||
routes = [ |
||||
web.get('/client/api', self.generic_handler), |
||||
web.post('/client/api', self.generic_handler), |
||||
# for debugging purposes |
||||
web.get('/reset', self.reset_handler), |
||||
web.post('/reset', self.reset_handler), |
||||
] |
||||
super().__init__(port, routes) |
||||
|
||||
self.users_by_accountid = {} |
||||
self.users_by_username = {} |
||||
|
||||
def clear(self): |
||||
self.users_by_accountid.clear() |
||||
self.users_by_username.clear() |
||||
|
||||
def reset_handler(self, request): |
||||
self.clear() |
||||
return web.Response(text='OK\n') |
||||
|
||||
def _add_user(self, username: str): |
||||
account_id = gen_uuid() |
||||
user_id = gen_uuid() |
||||
user = { |
||||
"id": user_id, |
||||
"username": username, |
||||
"firstname": "Calum", |
||||
"lastname": "Dalek", |
||||
"email": username + "@csclub.internal", |
||||
"created": "2021-11-20T11:08:24-0500", |
||||
"state": "enabled", |
||||
"account": username, |
||||
"accounttype": 0, |
||||
"usersource": "ldap", |
||||
"roleid": "24422759-45de-11ec-b585-32ee6075b19b", |
||||
"roletype": "User", |
||||
"rolename": "User", |
||||
"domainid": "4d2a4a98-b1b4-47a8-ab8f-7e175013a0f0", |
||||
"domain": "Members", |
||||
"accountid": account_id, |
||||
"iscallerchilddomain": False, |
||||
"isdefault": False |
||||
} |
||||
self.users_by_accountid[account_id] = user |
||||
self.users_by_username[username] = user |
||||
return user |
||||
|
||||
def _delete_user(self, account_id: str): |
||||
user = self.users_by_accountid[account_id] |
||||
username = user['username'] |
||||
del self.users_by_accountid[account_id] |
||||
del self.users_by_username[username] |
||||
|
||||
def _account_from_username(self, username: str): |
||||
user = self.users_by_username[username] |
||||
return { |
||||
"id": user['accountid'], |
||||
"name": username, |
||||
"accounttype": 0, |
||||
"roleid": "24422759-45de-11ec-b585-32ee6075b19b", |
||||
"roletype": "User", |
||||
"rolename": "User", |
||||
"domainid": "4d2a4a98-b1b4-47a8-ab8f-7e175013a0f0", |
||||
"domain": "Members", |
||||
"domainpath": "ROOT/Members", |
||||
"state": "enabled", |
||||
"user": [user], |
||||
"isdefault": False, |
||||
"groups": [] |
||||
} |
||||
|
||||
async def generic_handler(self, request): |
||||
command = request.query['command'] |
||||
if command == 'listDomains': |
||||
return web.json_response({ |
||||
"listdomainsresponse": { |
||||
"count": 1, |
||||
"domain": [{ |
||||
"id": "4d2a4a98-b1b4-47a8-ab8f-7e175013a0f0", |
||||
"name": "Members", |
||||
"level": 1, |
||||
"parentdomainid": "f0f8263c-45dd-11ec-b585-32ee6075b19b", |
||||
"parentdomainname": "ROOT", |
||||
"haschild": False, |
||||
"path": "ROOT/Members", |
||||
"state": "Active", |
||||
"secondarystoragetotal": 0.0 |
||||
}] |
||||
} |
||||
}) |
||||
elif command == 'ldapCreateAccount': |
||||
username = request.query['username'] |
||||
if username in self.users_by_username: |
||||
return web.json_response({ |
||||
"createaccountresponse": { |
||||
"uuidList": [], |
||||
"errorcode": 530, |
||||
"cserrorcode": 4250, |
||||
"errortext": f"The user {username} already exists in domain 2" |
||||
} |
||||
}, status=530) |
||||
self._add_user(username) |
||||
return web.json_response({ |
||||
"createaccountresponse": { |
||||
"account": self._account_from_username(username), |
||||
} |
||||
}) |
||||
elif command == 'listUsers': |
||||
users = list(self.users_by_username.values()) |
||||
return web.json_response({ |
||||
'listusersresponse': { |
||||
'count': len(users), |
||||
'user': users, |
||||
} |
||||
}) |
||||
elif command == 'listAccounts': |
||||
usernames = list(self.users_by_username.keys()) |
||||
return web.json_response({ |
||||
'listaccountsresponse': { |
||||
'count': len(usernames), |
||||
'account': [ |
||||
self._account_from_username(username) |
||||
for username in usernames |
||||
] |
||||
} |
||||
}) |
||||
elif command == 'deleteAccount': |
||||
account_id = request.query['id'] |
||||
self._delete_user(account_id) |
||||
return web.json_response({ |
||||
'deleteaccountresponse': { |
||||
'jobid': gen_uuid() |
||||
} |
||||
}) |
||||
else: |
||||
return web.json_response({ |
||||
"errorresponse": { |
||||
"uuidList": [], |
||||
"errorcode": 401, |
||||
"errortext": "unable to verify user credentials and/or request signature" |
||||
} |
||||
}, status=401) |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
server = MockCloudStackServer() |
||||
server.start() |
@ -0,0 +1,29 @@ |
||||
from abc import ABC |
||||
import asyncio |
||||
from threading import Thread |
||||
from typing import List |
||||
|
||||
from aiohttp import web |
||||
|
||||
|
||||
class MockHTTPServerBase(ABC): |
||||
def __init__(self, port: int, routes: List): |
||||
self.port = port |
||||
self.app = web.Application() |
||||
self.app.add_routes(routes) |
||||
self.runner = web.AppRunner(self.app) |
||||
self.loop = asyncio.new_event_loop() |
||||
|
||||
def _start_loop(self): |
||||
asyncio.set_event_loop(self.loop) |
||||
self.loop.run_until_complete(self.runner.setup()) |
||||
site = web.TCPSite(self.runner, '127.0.0.1', self.port) |
||||
self.loop.run_until_complete(site.start()) |
||||
self.loop.run_forever() |
||||
|
||||
def start(self): |
||||
t = Thread(target=self._start_loop) |
||||
t.start() |
||||
|
||||
def stop(self): |
||||
self.loop.call_soon_threadsafe(self.loop.stop) |
@ -1,2 +0,0 @@ |
||||
from .MockSMTPServer import MockSMTPServer |
||||
from .MockMailmanServer import MockMailmanServer |
Loading…
Reference in new issue