mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
WIP: foundation for a new portal API to partially replace SSOwat
This commit is contained in:
parent
8eaa701230
commit
2845914d44
5 changed files with 271 additions and 1 deletions
53
bin/yunohost-portal-api
Executable file
53
bin/yunohost-portal-api
Executable 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)
|
51
share/actionsmap-portal.yml
Normal file
51
share/actionsmap-portal.yml
Normal 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
|
|
@ -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 #
|
||||
|
|
59
src/authenticators/ldap_ynhuser.py
Normal file
59
src/authenticators/ldap_ynhuser.py
Normal 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
89
src/portal.py
Normal 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 ?
|
Loading…
Add table
Reference in a new issue