WIP: foundation for a new portal API to partially replace SSOwat

This commit is contained in:
Alexandre Aubin 2021-12-04 03:27:23 +01:00
parent 8eaa701230
commit 2845914d44
5 changed files with 271 additions and 1 deletions

53
bin/yunohost-portal-api Executable file
View file

@ -0,0 +1,53 @@
#! /usr/bin/python3
# -*- coding: utf-8 -*-
import argparse
import yunohost
# Default server configuration
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 6788
def _parse_api_args():
"""Parse main arguments for the api"""
parser = argparse.ArgumentParser(
add_help=False,
description="Run the YunoHost API to manage your server.",
)
srv_group = parser.add_argument_group("server configuration")
srv_group.add_argument(
"-h",
"--host",
action="store",
default=DEFAULT_HOST,
help="Host to listen on (default: %s)" % DEFAULT_HOST,
)
srv_group.add_argument(
"-p",
"--port",
action="store",
default=DEFAULT_PORT,
type=int,
help="Port to listen on (default: %d)" % DEFAULT_PORT,
)
glob_group = parser.add_argument_group("global arguments")
glob_group.add_argument(
"--debug",
action="store_true",
default=False,
help="Set log level to DEBUG",
)
glob_group.add_argument(
"--help",
action="help",
help="Show this help message and exit",
)
return parser.parse_args()
if __name__ == "__main__":
opts = _parse_api_args()
# Run the server
yunohost.portalapi(debug=opts.debug, host=opts.host, port=opts.port)

View file

@ -0,0 +1,51 @@
_global:
namespace: yunohost
cookie_name: yunohost.portal
authentication:
api: ldap_ynhuser
cli: null
portal:
category_help: Portal routes
actions:
### portal_me()
me:
action_help: Allow user to fetch their own infos
api: GET /me
### portal_apps()
apps:
action_help: Allow users to fetch lit of apps they have access to
api: GET /me/apps
### portal_update()
update:
action_help: Allow user to update their infos (display name, mail aliases/forward, password, ...)
api: PUT /me
# FIXME: add args etc
### portal_reset_password()
reset_password:
action_help: Allow user to update their infos (display name, mail aliases/forward, ...)
api: PUT /me/reset_password
authentication:
# FIXME: to be implemented ?
api: reset_password_token
# FIXME: add args etc
### portal_register()
register:
action_help: Allow user to register using an invite token or ???
api: POST /me
authentication:
# FIXME: to be implemented ?
api: register_invite_token
# FIXME: add args etc
### portal_public()
public:
action_help: Allow anybody to list public apps and other infos regarding the public portal
api: GET /public
authentication:
api: null

View file

@ -53,6 +53,20 @@ def api(debug, host, port):
sys.exit(ret)
def portalapi(debug, host, port):
# FIXME : is this the logdir we want ? (yolo to work around permission issue)
init_logging(interface="portalapi", debug=debug, logdir="/var/log")
ret = moulinette.api(
host=host,
port=port,
actionsmap="/usr/share/yunohost/actionsmap-portal.yml",
locales_dir="/usr/share/yunohost/locales/"
)
sys.exit(ret)
def check_command_is_valid_before_postinstall(args):
allowed_if_not_postinstalled = [
@ -125,6 +139,10 @@ def init_logging(interface="cli", debug=False, quiet=False, logdir="/var/log/yun
"level": "DEBUG" if debug else "INFO",
"class": "moulinette.interfaces.api.APIQueueHandler",
},
"portalapi": {
"level": "DEBUG" if debug else "INFO",
"class": "moulinette.interfaces.api.APIQueueHandler",
},
"file": {
"class": "logging.FileHandler",
"formatter": "precise",
@ -151,7 +169,7 @@ def init_logging(interface="cli", debug=False, quiet=False, logdir="/var/log/yun
}
# Logging configuration for CLI (or any other interface than api...) #
if interface != "api":
if interface not in ["api", "portalapi"]:
configure_logging(logging_configuration)
# Logging configuration for API #

View file

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
import logging
import ldap
import ldap.sasl
from moulinette import m18n
from moulinette.authentication import BaseAuthenticator
from yunohost.utils.error import YunohostError
logger = logging.getLogger("yunohostportal.authenticators.ldap_ynhuser")
URI = "ldap://localhost:389"
USERDN = "uid={username},ou=users,dc=yunohost,dc=org"
class Authenticator(BaseAuthenticator):
name = "ldap_ynhuser"
def _authenticate_credentials(self, credentials=None):
# FIXME ':' should a legit char in the password ? shall we encode the password as base64 or something idk
if ":" not in credentials or len(credentials.split(":")) != 2:
raise YunohostError("invalid_credentials_format")
username, password = credentials.split(":")
def _reconnect():
con = ldap.ldapobject.ReconnectLDAPObject(
URI, retry_max=2, retry_delay=0.5
)
con.simple_bind_s(USERDN.format(username=username), password)
return con
try:
con = _reconnect()
except ldap.INVALID_CREDENTIALS:
raise YunohostError("invalid_password")
except ldap.SERVER_DOWN:
logger.warning(m18n.n("ldap_server_down"))
# Check that we are indeed logged in with the expected identity
try:
# whoami_s return dn:..., then delete these 3 characters
who = con.whoami_s()[3:]
except Exception as e:
logger.warning("Error during ldap authentication process: %s", e)
raise
else:
if who != USERDN.format(username=username):
raise YunohostError(
"Not logged with the appropriate identity ?!",
raw_msg=True,
)
finally:
# Free the connection, we don't really need it to keep it open as the point is only to check authentication...
if con:
con.unbind_s()

89
src/portal.py Normal file
View file

@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2021 YUNOHOST.ORG
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program; if not, see http://www.gnu.org/licenses
"""
# from moulinette import Moulinette, m18n
from moulinette.utils.log import getActionLogger
from yunohost.utils.error import YunohostValidationError
logger = getActionLogger("yunohostportal.user")
def me():
"""
Get user informations
Keyword argument:
username -- Username to get informations
"""
username = None # FIXME : this info should come from the authentication layer
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
user_attrs = ["cn", "mail", "uid", "maildrop", "givenName", "sn", "mailuserquota"]
filter = "uid=" + username
result = ldap.search("ou=users,dc=yunohost,dc=org", filter, user_attrs)
if result:
user = result[0]
else:
raise YunohostValidationError("user_unknown", user=username)
result_dict = {
"username": user["uid"][0],
"fullname": user["cn"][0],
"firstname": user["givenName"][0],
"lastname": user["sn"][0],
"mail": user["mail"][0],
"mail-aliases": [],
"mail-forward": [],
}
if len(user["mail"]) > 1:
result_dict["mail-aliases"] = user["mail"][1:]
if len(user["maildrop"]) > 1:
result_dict["mail-forward"] = user["maildrop"][1:]
if "mailuserquota" in user:
pass
# FIXME
# result_dict["mailbox-quota"] = {
# "limit": userquota if is_limited else m18n.n("unlimit"),
# "use": storage_use,
# }
# FIXME : should also parse "permission" key in ldap maybe ?
# and list of groups / memberof ?
# (in particular to have e.g. the mail / xmpp / ssh / ... perms)
return result_dict
def apps(username):
return {"foo": "bar"}
# FIXME: should list available apps and corresponding infos ?
# from /etc/ssowat/conf.json ?