Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/python-sanity-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ jobs:
run: |
sudo apt install -y libenchant-2-dev libcrack2-dev libssl-dev
python -m pip install --upgrade pip
pip install flake8 pylint #pytest
if [ -f local-requirements.txt ]; then pip install -r local-requirements.txt; fi
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f recommended.txt ]; then pip install -r recommended.txt; fi
# We may need git installed to get a full repo clone rather than unpacked archive
- name: Check out source repository
uses: actions/checkout@v4
Expand Down Expand Up @@ -125,7 +127,7 @@ jobs:
run: |
dnf install -y enchant cracklib openssl-devel
python -m pip install --upgrade pip
pip install flake8 pylint #pytest
if [ -f local-requirements.txt ]; then pip install -r local-requirements.txt; fi
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f recommended.txt ]; then pip install -r recommended.txt; fi
# We need git installed to get a full repo clone rather than unpacked archive
Expand Down
2 changes: 2 additions & 0 deletions local-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
# This list is mainly used to specify addons needed for the unit tests.
# We only need autopep8 on py 3 as it's used in 'make fmt' (with py3)
autopep8;python_version >= "3"
flake8
# We need paramiko for the ssh unit tests
# NOTE: paramiko-3.0.0 dropped python2 and python3.6 support
paramiko;python_version >= "3.7"
paramiko<3;python_version < "3.7"
pylint
werkzeug
Empty file added mig/lib/__init__.py
Empty file.
103 changes: 103 additions & 0 deletions mig/lib/coreapi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import codecs
import json
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import urlopen, Request
import werkzeug.exceptions as httpexceptions

from mig.lib.coresvc.payloads import PAYLOAD_POST_USER


httpexceptions_by_code = {
exc.code: exc for exc in httpexceptions.__dict__.values() if hasattr(exc, 'code')}

Check warning on line 12 in mig/lib/coreapi/__init__.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (86 > 80 characters)


def attempt_to_decode_response_data(data, response_encoding=None):
if data is None:
return None
elif response_encoding == 'textual':
data = codecs.decode(data, 'utf8')

try:
return json.loads(data)
except Exception as e:
return data
elif response_encoding == 'binary':
return data
else:
raise AssertionError(
'issue_POST: unknown response_encoding "%s"' % (response_encoding,))


def http_error_from_status_code(http_status_code, description=None):
return httpexceptions_by_code[http_status_code](description)


class CoreApiClient:
def __init__(self, base_url):
self._base_url = base_url

def _issue_GET(self, request_path, query_dict=None, response_encoding='textual'):

Check warning on line 40 in mig/lib/coreapi/__init__.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (85 > 80 characters)
request_url = ''.join((self._base_url, request_path))

if query_dict is not None:
query_string = urlencode(query_dict)
request_url = ''.join((request_url, '?', query_string))

status = 0
data = None

try:
response = urlopen(request_url, None, timeout=2000)

status = response.getcode()
data = response.read()
except HTTPError as httpexc:
status = httpexc.code
data = None

content = attempt_to_decode_response_data(data, response_encoding)
return (status, content)

def _issue_POST(self, request_path, request_data=None, request_json=None, response_encoding='textual'):

Check warning on line 62 in mig/lib/coreapi/__init__.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (107 > 80 characters)
request_url = ''.join((self._base_url, request_path))

if request_data and request_json:
raise ValueError(
"only one of data or json request data may be specified")

status = 0
data = None

try:
if request_json is not None:
request_data = codecs.encode(json.dumps(request_json), 'utf8')
request_headers = {
'Content-Type': 'application/json'
}
request = Request(request_url, request_data,
headers=request_headers)
elif request_data is not None:
request = Request(request_url, request_data)
else:
request = Request(request_url)

response = urlopen(request, timeout=2000)

status = response.getcode()
data = response.read()
except HTTPError as httpexc:
status = httpexc.code
data = httpexc.fp.read()

content = attempt_to_decode_response_data(data, response_encoding)
return (status, content)

def createUser(self, user_dict):
payload = PAYLOAD_POST_USER.ensure(user_dict)

status, output = self._issue_POST('/user', request_json=dict(payload))
if status != 201:
description = output if isinstance(output, str) else None
raise http_error_from_status_code(status, description)
return output
2 changes: 2 additions & 0 deletions mig/lib/coresvc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from mig.lib.coresvc.server import ThreadedApiHttpServer, \
_create_and_expose_server
30 changes: 30 additions & 0 deletions mig/lib/coresvc/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from argparse import ArgumentError
from getopt import getopt
import sys

from mig.shared.conf import get_configuration_object
from mig.services.coreapi.server import main as server_main


def _getopt_opts_to_options(opts):
options = {}
for k, v in opts:
options[k[1:]] = v
return options


def _required_argument_error(option, argument_name):
raise ArgumentError(None, 'Missing required argument: %s %s' %
(option, argument_name.upper()))


if __name__ == '__main__':
(opts, args) = getopt(sys.argv[1:], 'c:')
opts_dict = _getopt_opts_to_options(opts)

if 'c' not in opts_dict:
raise _required_argument_error('-c', 'config_file')

configuration = get_configuration_object(opts_dict['c'],
skip_log=True, disable_auth_log=True)

Check warning on line 29 in mig/lib/coresvc/__main__.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (82 > 80 characters)
server_main(configuration)
18 changes: 18 additions & 0 deletions mig/lib/coresvc/respond.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from flask import Response
import json
import werkzeug.exceptions as httpexceptions

httpexceptions_by_code = {
exc.code: exc for exc in httpexceptions.__dict__.values() if hasattr(exc, "code")

Check warning on line 6 in mig/lib/coresvc/respond.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (85 > 80 characters)
}


def http_error_from_status_code(http_status_code, http_url, description=None):
return httpexceptions_by_code[http_status_code](description)


def json_reponse_from_status_code(http_status_code, content):
json_content = json.dumps(content)
return Response(
json_content, http_status_code, {"Content-Type": "application/json"}
)
Empty file.
71 changes: 71 additions & 0 deletions mig/lib/coresvc/routes/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from flask import Blueprint, request, current_app

from mig.lib.coresvc.payloads import PayloadException, PAYLOAD_POST_USER
from mig.lib.coresvc.respond import \
http_error_from_status_code, \
json_reponse_from_status_code
from mig.shared.base import canonical_user, keyword_auto, force_native_str_rec
from mig.shared.useradm import fill_user, \
create_user as useradm_create_user, search_users as useradm_search_users

def _create_user(configuration, payload):

Check warning on line 11 in mig/lib/coresvc/routes/user.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

expected 2 blank lines, found 1
user_dict = canonical_user(
configuration, payload, PAYLOAD_POST_USER._fields)
fill_user(user_dict)
force_native_str_rec(user_dict)

try:
useradm_create_user(user_dict, configuration, keyword_auto, default_renew=True)

Check warning on line 18 in mig/lib/coresvc/routes/user.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (87 > 80 characters)
except:

Check warning on line 19 in mig/lib/coresvc/routes/user.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

do not use bare 'except'
raise http_error_from_status_code(500, None)
user_email = user_dict['email']
objects = search_users(configuration, {
'email': user_email
})
if len(objects) != 1:
raise http_error_from_status_code(400, None)
return objects[0]


def search_users(configuration, search_filter):
_, hits = useradm_search_users(search_filter, configuration, keyword_auto)
return list((obj for _, obj in hits))


bp = Blueprint('user', __name__)


@bp.get('/user')
def GET_user():
raise http_error_from_status_code(400, None)

@bp.get('/user/<username>')

Check warning on line 42 in mig/lib/coresvc/routes/user.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

expected 2 blank lines, found 1
def GET_user_username(username):
return 'FOOBAR'

@bp.get('/user/find')

Check warning on line 46 in mig/lib/coresvc/routes/user.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

expected 2 blank lines, found 1
def GET_user_find():
configuration, = current_app.migctx
query_params = request.args

objects = search_users(configuration, {
'email': query_params['email']
})

if len(objects) != 1:
raise http_error_from_status_code(404, None)

return dict(objects=objects)

@bp.post('/user')
def POST_user():
configuration, = current_app.migctx
payload = request.get_json()

try:
payload = PAYLOAD_POST_USER.ensure(payload)
except PayloadException as vr:
return http_error_from_status_code(400, None, vr.serialize())

user = _create_user(configuration, payload)
return json_reponse_from_status_code(201, user)
Loading
Loading