parent
89e6c541ab
commit
bb7539dcb6
@ -0,0 +1,14 @@ |
||||
from zope.interface import Attribute, Interface |
||||
from .IUser import IUser |
||||
|
||||
|
||||
class IDatabaseService(Interface): |
||||
"""Interface to create databases for users.""" |
||||
|
||||
type = Attribute('the type of database') |
||||
host = Attribute('the database address') |
||||
auth_username = Attribute('username of user creating connection') |
||||
auth_password = Attribute('password of user creating connection') |
||||
|
||||
def create_db(username: str) -> str: |
||||
"""create a database for user and return its password""" |
@ -0,0 +1,42 @@ |
||||
from flask import Blueprint, request |
||||
from zope import component |
||||
from ceod.api.utils import authz_restrict_to_staff, authz_restrict_to_syscom, \ |
||||
user_is_in_group, requires_authentication_no_realm, \ |
||||
create_streaming_response, create_sync_response, development_only |
||||
from ceo_common.errors import UserNotFoundError, DatabaseConnectionError |
||||
from ceo_common.interfaces import IDatabaseService |
||||
|
||||
|
||||
bp = Blueprint('db', __name__) |
||||
|
||||
# could combine create_mysql_db and create_postgresql_db into one function |
||||
|
||||
|
||||
@bp.route('/mysql/<username>', methods=['POST']) |
||||
@requires_authentication_no_realm |
||||
def create_mysql_db(auth_user: str, username: str): |
||||
if not (auth_user == username or user_is_in_group(auth_user, 'syscom')): |
||||
return {'error': "not authorized to create databases for others"}, 403 |
||||
try: |
||||
db_srv = component.getUtility(IDatabaseService, 'mysql') |
||||
password = db_srv.create_db(username) |
||||
return {'password': password} |
||||
except UserNotFoundError: |
||||
return {'error': 'user not found'}, 404 |
||||
except DatabaseConnectionError: |
||||
return {'error': 'unable to connect to mysql server'}, 400 |
||||
|
||||
|
||||
@bp.route('/postgresql/<username>', methods=['POST']) |
||||
@requires_authentication_no_realm |
||||
def create_postgresql_db(auth_user: str, username: str): |
||||
if not (auth_user == username or user_is_in_group(auth_user, 'syscom')): |
||||
return {'error': "not authorized to create databases for others"}, 403 |
||||
try: |
||||
db_srv = component.getUtility(IDatabaseService, 'postgresql') |
||||
password = db_srv.create_db(username) |
||||
return {'password': password} |
||||
except UserNotFoundError: |
||||
return {'error': 'user not found'}, 404 |
||||
except DatabaseConnectionError: |
||||
return {'error': 'unable to connect to postgresql server'}, 400 |
@ -0,0 +1,47 @@ |
||||
from zope.interface import implementer |
||||
from zope import component |
||||
from ceo_common.interfaces import IDatabaseService, ILDAPService, IConfig |
||||
from ceo_common.errors import DatabaseConnectionError |
||||
from mysql.connector import connect, Error |
||||
import ceod.utils as utils |
||||
|
||||
|
||||
@implementer(IDatabaseService) |
||||
class MySQLService: |
||||
def __init__(self): |
||||
# how to set default values for these |
||||
self.type = 'mysql' |
||||
config = component.getUtility(IConfig) |
||||
self.host = config.get('mysql_host') |
||||
self.auth_username = config.get('mysql_username') |
||||
self.auth_password = config.get('mysql_password') |
||||
|
||||
def create_db(self, username: str) -> str: |
||||
component.getUtility(ILDAPService).get_user(username) # make sure user exists |
||||
password = utils.gen_password() |
||||
user = {'username': username, 'password': password} |
||||
try: |
||||
with connect( |
||||
host=self.host, |
||||
user=self.auth_username, |
||||
password=self.auth_password, |
||||
) as con: |
||||
search_for_user = "SELECT user FROM mysql.user WHERE user='%(username)s'" |
||||
create_user = "CREATE USER '%(username)s'@'localhost' IDENTIFIED BY '%(password)s'" |
||||
create_database = "CREATE DATABASE %(username)s" |
||||
set_user_perms = "GRANT ALL PRIVILEGES ON %(username)s.* TO '%(username)s'@'localhost'" |
||||
flush_privileges = "FLUSH PRIVILEGES" |
||||
reset_password = "ALTER USER '%(username)s' IDENTIFIED BY '%(password)s'" |
||||
with con.cursor() as cursor: |
||||
cursor.execute(search_for_user, user) |
||||
response = cursor.fetchall() |
||||
if len(response) == 0: |
||||
cursor.execute(create_user, user) |
||||
cursor.execute(create_database, user) |
||||
cursor.execute(set_user_perms, user) |
||||
cursor.execute(flush_privileges) |
||||
else: |
||||
cursor.execute(reset_password, user) |
||||
return password |
||||
except Error: |
||||
raise DatabaseConnectionError() |
@ -0,0 +1,61 @@ |
||||
from zope.interface import implementer |
||||
from zope import component |
||||
from ceo_common.interfaces import IDatabaseService, ILDAPService, IConfig |
||||
from ceo_common.errors import DatabaseConnectionError |
||||
import ceod.utils as utils |
||||
from psycopg2 import connect, Error |
||||
|
||||
|
||||
@implementer(IDatabaseService) |
||||
class PostgreSQLService: |
||||
def __init__(self): |
||||
self.type = 'postgresql' |
||||
config = component.getUtility(IConfig) |
||||
self.host = config.get('postgresql_host') |
||||
self.auth_username = config.get('postgresql_username') |
||||
self.auth_password = config.get('postgresql_password') |
||||
|
||||
# https://www.postgresql.org/docs/9.1/auth-pg-hba-conf.html |
||||
# pg_hba.conf only listen to localhost and only allow users to login to database with the same name as user |
||||
# local sameuser all localhost md5 |
||||
# need different line for syscom |
||||
|
||||
# Allow only postgres to create on the schema public |
||||
# REVOKE ALL ON SCHEMA public FROM PUBLIC; |
||||
# GRANT ALL ON SCHEMA public TO postgres; |
||||
|
||||
# by default all database created are open to connection from anyone |
||||
# only the owner (and superusers) can ever drop a database |
||||
|
||||
# note that pg_catalog allows access list of database and user names for everyone and cannot be disabled with breaking some things |
||||
|
||||
def create_db(self, username: str) -> str: |
||||
component.getUtility(ILDAPService).get_user(username) # make sure user exists |
||||
password = utils.gen_password() |
||||
user = {'username': username, 'password': password} |
||||
try: |
||||
with connect( |
||||
host=self.host, |
||||
user=self.auth_username, |
||||
password=self.auth_password, |
||||
) as con: |
||||
# limit access to localhost? |
||||
search_for_user = "SELECT FROM pg_roles WHERE rolname='%(username)s'" |
||||
create_user = "CREATE USER %(username)s WITH NOSUPERUSER NOCREATEDB NOCREATEROLE PASSWORD '%(password)s'" |
||||
create_database = "CREATE DATABASE %(username)s" |
||||
set_db_perms = "REVOKE ALL ON DATABASE %(username)s FROM PUBLIC" |
||||
set_user_perms = "GRANT ALL ON DATABASE %(username)s TO %(username)s" |
||||
reset_password = "ALTER USER '%(username)s' WITH PASSWORD '%(password)s'" |
||||
with con.cursor() as cursor: |
||||
cursor.execute(search_for_user, user) |
||||
response = cursor.fetchall() |
||||
if len(response) == 0: |
||||
cursor.execute(create_user, user) |
||||
cursor.execute(create_database, user) |
||||
cursor.execute(set_db_perms, user) |
||||
cursor.execute(set_user_perms, user) |
||||
else: |
||||
cursor.execute(reset_password, user) |
||||
return password |
||||
except Error: |
||||
raise DatabaseConnectionError() |
@ -0,0 +1,2 @@ |
||||
from .MySQLService import MySQLService |
||||
from .PostgreSQLService import PostgreSQLService |
Loading…
Reference in new issue