use create_sync_response
This commit is contained in:
parent
c32e565f68
commit
7c67a07200
|
@ -65,6 +65,9 @@ def create_app(flask_config={}):
|
||||||
uwldap_srv = UWLDAPService()
|
uwldap_srv = UWLDAPService()
|
||||||
component.provideUtility(uwldap_srv, IUWLDAPService)
|
component.provideUtility(uwldap_srv, IUWLDAPService)
|
||||||
|
|
||||||
|
from ceod.api import uwldap
|
||||||
|
app.register_blueprint(uwldap.bp, url_prefix='/api/uwldap')
|
||||||
|
|
||||||
@app.route('/ping')
|
@app.route('/ping')
|
||||||
def ping():
|
def ping():
|
||||||
"""Health check"""
|
"""Health check"""
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from .utils import authz_restrict_to_staff
|
|
||||||
|
|
||||||
|
from .utils import authz_restrict_to_staff, create_sync_response
|
||||||
from ceod.transactions.mailman import SubscribeMemberTransaction, UnsubscribeMemberTransaction
|
from ceod.transactions.mailman import SubscribeMemberTransaction, UnsubscribeMemberTransaction
|
||||||
|
|
||||||
bp = Blueprint('mailman', __name__)
|
bp = Blueprint('mailman', __name__)
|
||||||
|
@ -10,13 +10,11 @@ bp = Blueprint('mailman', __name__)
|
||||||
@authz_restrict_to_staff
|
@authz_restrict_to_staff
|
||||||
def subscribe(mailing_list, username):
|
def subscribe(mailing_list, username):
|
||||||
txn = SubscribeMemberTransaction(username, mailing_list)
|
txn = SubscribeMemberTransaction(username, mailing_list)
|
||||||
txn.execute()
|
return create_sync_response(txn)
|
||||||
return {'message': f"{username} successfully subscribed to {mailing_list}"}
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<mailing_list>/<username>', methods=['DELETE'])
|
@bp.route('/<mailing_list>/<username>', methods=['DELETE'])
|
||||||
@authz_restrict_to_staff
|
@authz_restrict_to_staff
|
||||||
def unsubscribe(mailing_list, username):
|
def unsubscribe(mailing_list, username):
|
||||||
txn = UnsubscribeMemberTransaction(username, mailing_list)
|
txn = UnsubscribeMemberTransaction(username, mailing_list)
|
||||||
txn.execute()
|
return create_sync_response(txn)
|
||||||
return {'message': f"{username} successfully unsubscribed from {mailing_list}"}
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from zope import component
|
||||||
|
|
||||||
from .utils import authz_restrict_to_staff, authz_restrict_to_syscom, \
|
from .utils import authz_restrict_to_staff, authz_restrict_to_syscom, \
|
||||||
user_is_in_group, requires_authentication_no_realm, \
|
user_is_in_group, requires_authentication_no_realm, \
|
||||||
create_streaming_response
|
create_streaming_response, create_sync_response
|
||||||
from ceo_common.errors import UserNotFoundError
|
from ceo_common.errors import UserNotFoundError
|
||||||
from ceo_common.interfaces import ILDAPService
|
from ceo_common.interfaces import ILDAPService
|
||||||
from ceod.transactions.members import (
|
from ceod.transactions.members import (
|
||||||
|
@ -71,11 +71,11 @@ def renew_user(username: str):
|
||||||
terms=body.get('terms'),
|
terms=body.get('terms'),
|
||||||
non_member_terms=body.get('non_member_terms'),
|
non_member_terms=body.get('non_member_terms'),
|
||||||
)
|
)
|
||||||
return create_streaming_response(txn)
|
return create_sync_response(txn)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<username>/pwreset', methods=['POST'])
|
@bp.route('/<username>/pwreset', methods=['POST'])
|
||||||
@authz_restrict_to_syscom
|
@authz_restrict_to_syscom
|
||||||
def reset_user_password(username: str):
|
def reset_user_password(username: str):
|
||||||
txn = ResetPasswordTransaction(username)
|
txn = ResetPasswordTransaction(username)
|
||||||
return create_streaming_response(txn)
|
return create_sync_response(txn)
|
||||||
|
|
|
@ -3,55 +3,18 @@ import grp
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
import socket
|
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Callable, List
|
from typing import Callable, List
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask_kerberos import requires_authentication
|
from flask_kerberos import requires_authentication
|
||||||
from zope import component
|
|
||||||
|
|
||||||
from ceo_common.logger_factory import logger_factory
|
from ceo_common.logger_factory import logger_factory
|
||||||
from ceo_common.interfaces import IConfig
|
|
||||||
from ceod.transactions import AbstractTransaction
|
from ceod.transactions import AbstractTransaction
|
||||||
|
|
||||||
logger = logger_factory(__name__)
|
logger = logger_factory(__name__)
|
||||||
|
|
||||||
|
|
||||||
def restrict_host(role: str) -> Callable[[Callable], Callable]:
|
|
||||||
"""
|
|
||||||
This is a function which returns a decorator.
|
|
||||||
It returns a 400 if the client makes a request to an endpoint
|
|
||||||
which is restricted to a different host.
|
|
||||||
|
|
||||||
:param role: a key in the app's config (e.g. 'ceod_admin_host')
|
|
||||||
which maps to a specific hostname
|
|
||||||
|
|
||||||
Example:
|
|
||||||
@app.route('/<mailing_list>/<username>', methods=['POST'])
|
|
||||||
@restrict_host('mailman_host')
|
|
||||||
def subscribe(mailing_list, username):
|
|
||||||
....
|
|
||||||
"""
|
|
||||||
|
|
||||||
hostname = socket.gethostname()
|
|
||||||
cfg = component.getUtility(IConfig)
|
|
||||||
desired_hostname = cfg.get(role)
|
|
||||||
|
|
||||||
def identity(f: Callable):
|
|
||||||
return f
|
|
||||||
|
|
||||||
def error_decorator(f: Callable):
|
|
||||||
@functools.wraps(f)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
return {'error': f'Wrong host! Use {desired_hostname} instead'}, 400
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
if hostname == desired_hostname:
|
|
||||||
return identity
|
|
||||||
return error_decorator
|
|
||||||
|
|
||||||
|
|
||||||
def requires_authentication_no_realm(f: Callable) -> Callable:
|
def requires_authentication_no_realm(f: Callable) -> Callable:
|
||||||
"""
|
"""
|
||||||
Like requires_authentication, but strips the realm out of the principal string.
|
Like requires_authentication, but strips the realm out of the principal string.
|
||||||
|
@ -140,3 +103,18 @@ def create_streaming_response(txn: AbstractTransaction):
|
||||||
}) + '\n'
|
}) + '\n'
|
||||||
|
|
||||||
return current_app.response_class(generate(), mimetype='text/plain')
|
return current_app.response_class(generate(), mimetype='text/plain')
|
||||||
|
|
||||||
|
|
||||||
|
def create_sync_response(txn: AbstractTransaction):
|
||||||
|
"""
|
||||||
|
Runs the transaction synchronously and returns a JSON response.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
txn.execute()
|
||||||
|
return {'result': txn.result}
|
||||||
|
except Exception as err:
|
||||||
|
logger.warning('Transaction failed:\n' + traceback.format_exc())
|
||||||
|
txn.rollback()
|
||||||
|
return {
|
||||||
|
'error': str(err),
|
||||||
|
}, 500
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
from flask import Blueprint
|
||||||
|
from zope import component
|
||||||
|
|
||||||
|
from ceo_common.interfaces import IUWLDAPService
|
||||||
|
|
||||||
|
bp = Blueprint('uwldap', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<username>')
|
||||||
|
def get_user(username: str):
|
||||||
|
uwldap_srv = component.getUtility(IUWLDAPService)
|
||||||
|
record = uwldap_srv.get(username)
|
||||||
|
if record is None:
|
||||||
|
return {
|
||||||
|
'error': 'user not found',
|
||||||
|
}, 404
|
||||||
|
return record.to_dict()
|
|
@ -17,7 +17,7 @@ class UWLDAPRecord:
|
||||||
self.mail_local_addresses = mail_local_addresses
|
self.mail_local_addresses = mail_local_addresses
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def deserialize_from_ldap(self, data: Dict[str, List[bytes]]):
|
def deserialize_from_ldap(data: Dict[str, List[bytes]]):
|
||||||
"""
|
"""
|
||||||
Deserializes a dict returned from ldap.search_s() into a
|
Deserializes a dict returned from ldap.search_s() into a
|
||||||
UWLDAPRecord.
|
UWLDAPRecord.
|
||||||
|
@ -28,3 +28,10 @@ class UWLDAPRecord:
|
||||||
program=data.get('ou', [None])[0],
|
program=data.get('ou', [None])[0],
|
||||||
mail_local_addresses=data['mailLocalAddress'],
|
mail_local_addresses=data['mailLocalAddress'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'uid': self.uid,
|
||||||
|
'program': self.program,
|
||||||
|
'mail_local_addresses': self.mail_local_addresses,
|
||||||
|
}
|
||||||
|
|
|
@ -20,4 +20,5 @@ class UWLDAPService:
|
||||||
results = conn.search_s(self.uwldap_base, ldap.SCOPE_SUBTREE, f'uid={username}')
|
results = conn.search_s(self.uwldap_base, ldap.SCOPE_SUBTREE, f'uid={username}')
|
||||||
if not results:
|
if not results:
|
||||||
return None
|
return None
|
||||||
return UWLDAPRecord.deserialize_from_ldap(results[0])
|
_, data = results[0] # discard the dn
|
||||||
|
return UWLDAPRecord.deserialize_from_ldap(data)
|
||||||
|
|
|
@ -26,8 +26,4 @@ class SubscribeMemberTransaction(AbstractTransaction):
|
||||||
self.mailman_srv.subscribe(self.address, self.mailing_list)
|
self.mailman_srv.subscribe(self.address, self.mailing_list)
|
||||||
yield 'subscribe_to_mailing_list'
|
yield 'subscribe_to_mailing_list'
|
||||||
|
|
||||||
self.finish('success')
|
self.finish('OK')
|
||||||
|
|
||||||
def rollback(self):
|
|
||||||
# nothing to do, since there was only one operation
|
|
||||||
pass
|
|
||||||
|
|
|
@ -26,8 +26,4 @@ class UnsubscribeMemberTransaction(AbstractTransaction):
|
||||||
self.mailman_srv.unsubscribe(self.address, self.mailing_list)
|
self.mailman_srv.unsubscribe(self.address, self.mailing_list)
|
||||||
yield 'unsubscribe_to_mailing_list'
|
yield 'unsubscribe_to_mailing_list'
|
||||||
|
|
||||||
self.finish('success')
|
self.finish('OK')
|
||||||
|
|
||||||
def rollback(self):
|
|
||||||
# nothing to do, since there was only one operation
|
|
||||||
pass
|
|
||||||
|
|
Loading…
Reference in New Issue