wip: db api endpoints
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
89e6c541ab
commit
bb7539dcb6
|
@ -6,3 +6,4 @@ __pycache__/
|
||||||
*.o
|
*.o
|
||||||
*.so
|
*.so
|
||||||
/ceo_common/krb5/_krb5.c
|
/ceo_common/krb5/_krb5.c
|
||||||
|
.idea/
|
||||||
|
|
|
@ -49,3 +49,7 @@ class UserNotSubscribedError(Exception):
|
||||||
class NoSuchListError(Exception):
|
class NoSuchListError(Exception):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__('mailing list does not exist')
|
super().__init__('mailing list does not exist')
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseConnectionError(Exception):
|
||||||
|
pass
|
||||||
|
|
|
@ -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"""
|
|
@ -9,10 +9,11 @@ from zope import component
|
||||||
from .error_handlers import register_error_handlers
|
from .error_handlers import register_error_handlers
|
||||||
from .krb5_cred_handlers import before_request, teardown_request
|
from .krb5_cred_handlers import before_request, teardown_request
|
||||||
from ceo_common.interfaces import IConfig, IKerberosService, ILDAPService, IFileService, \
|
from ceo_common.interfaces import IConfig, IKerberosService, ILDAPService, IFileService, \
|
||||||
IMailmanService, IMailService, IUWLDAPService, IHTTPClient
|
IMailmanService, IMailService, IUWLDAPService, IHTTPClient, IDatabaseService
|
||||||
from ceo_common.model import Config, HTTPClient, RemoteMailmanService
|
from ceo_common.model import Config, HTTPClient, RemoteMailmanService
|
||||||
from ceod.model import KerberosService, LDAPService, FileService, \
|
from ceod.model import KerberosService, LDAPService, FileService, \
|
||||||
MailmanService, MailService, UWLDAPService
|
MailmanService, MailService, UWLDAPService
|
||||||
|
from ceod.db import MySQLService, PostgreSQLService
|
||||||
|
|
||||||
|
|
||||||
def create_app(flask_config={}):
|
def create_app(flask_config={}):
|
||||||
|
@ -107,3 +108,11 @@ def register_services(app):
|
||||||
# UWLDAPService
|
# UWLDAPService
|
||||||
uwldap_srv = UWLDAPService()
|
uwldap_srv = UWLDAPService()
|
||||||
component.provideUtility(uwldap_srv, IUWLDAPService)
|
component.provideUtility(uwldap_srv, IUWLDAPService)
|
||||||
|
|
||||||
|
# MySQLService
|
||||||
|
mysql_srv = MySQLService()
|
||||||
|
component.provideUtility(mysql_srv, IDatabaseService, 'mysql')
|
||||||
|
|
||||||
|
# PostgreSQLService
|
||||||
|
psql_srv = PostgreSQLService()
|
||||||
|
component.provideUtility(psql_srv, IDatabaseService, 'postgresql')
|
||||||
|
|
|
@ -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
|
|
@ -7,3 +7,4 @@ requests==2.26.0
|
||||||
requests-gssapi==1.2.3
|
requests-gssapi==1.2.3
|
||||||
zope.component==5.0.1
|
zope.component==5.0.1
|
||||||
zope.interface==5.4.0
|
zope.interface==5.4.0
|
||||||
|
mysql-connector-python==8.0.26
|
Loading…
Reference in New Issue