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)
|
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):
|
def check_command_is_valid_before_postinstall(args):
|
||||||
|
|
||||||
allowed_if_not_postinstalled = [
|
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",
|
"level": "DEBUG" if debug else "INFO",
|
||||||
"class": "moulinette.interfaces.api.APIQueueHandler",
|
"class": "moulinette.interfaces.api.APIQueueHandler",
|
||||||
},
|
},
|
||||||
|
"portalapi": {
|
||||||
|
"level": "DEBUG" if debug else "INFO",
|
||||||
|
"class": "moulinette.interfaces.api.APIQueueHandler",
|
||||||
|
},
|
||||||
"file": {
|
"file": {
|
||||||
"class": "logging.FileHandler",
|
"class": "logging.FileHandler",
|
||||||
"formatter": "precise",
|
"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...) #
|
# Logging configuration for CLI (or any other interface than api...) #
|
||||||
if interface != "api":
|
if interface not in ["api", "portalapi"]:
|
||||||
configure_logging(logging_configuration)
|
configure_logging(logging_configuration)
|
||||||
|
|
||||||
# Logging configuration for API #
|
# 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