Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose the API via a unix socket without authentication #2013

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
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
10 changes: 9 additions & 1 deletion bin/yunohost-api
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ def _parse_api_args() -> argparse.Namespace:
type=int,
help="Port to listen on (default: %d)" % DEFAULT_PORT,
)
srv_group.add_argument(
"-s",
"--socket",
action="store",
default=None,
type=str,
help="Socket path to listen on (default is none) without authentication"
)
glob_group = parser.add_argument_group("global arguments")
glob_group.add_argument(
"--debug",
Expand All @@ -68,4 +76,4 @@ def _parse_api_args() -> argparse.Namespace:
if __name__ == "__main__":
opts = _parse_api_args()
# Run the server
yunohost.api(debug=opts.debug, host=opts.host, port=opts.port)
yunohost.api(debug=opts.debug, host=opts.host, port=opts.port, socket=opts.socket)
1 change: 1 addition & 0 deletions share/actionsmap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ _global:
authentication:
api: ldap_admin
cli: null
sockapi: sockapi

#############################
# User #
Expand Down
32 changes: 22 additions & 10 deletions src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,20 @@

import os
import sys
import argparse

import moulinette
from moulinette import m18n
from moulinette.interfaces.cli import colorize, get_locale
from moulinette.utils.log import configure_logging
from moulinette.core import MoulinetteLock


def is_installed():
def is_installed() -> bool:
return os.path.isfile("/etc/yunohost/installed")


def cli(debug, quiet, output_as, timeout, args, parser):
def cli(debug: bool, quiet: bool, output_as: str, timeout: int, args: list[str], parser: argparse.ArgumentParser) -> None:
init_logging(interface="cli", debug=debug, quiet=quiet)

# Check that YunoHost is installed
Expand All @@ -49,7 +51,7 @@ def cli(debug, quiet, output_as, timeout, args, parser):
sys.exit(ret)


def api(debug, host, port):
def api(debug: bool, host: str, port: int, socket: str | None) -> None:

allowed_cors_origins = []
allowed_cors_origins_file = "/etc/yunohost/.admin-api-allowed-cors-origins"
Expand All @@ -73,10 +75,22 @@ def is_installed_api():
routes={("GET", "/installed"): is_installed_api},
allowed_cors_origins=allowed_cors_origins,
)
if ret:
sys.exit(ret)

if socket:
ret = moulinette.socket_api(
socket_path=socket,
actionsmap="/usr/share/yunohost/actionsmap.yml",
locales_dir="/usr/share/yunohost/locales/",
routes={("GET", "/installed"): is_installed_api},
allowed_cors_origins=allowed_cors_origins,
)

sys.exit(ret)


def portalapi(debug, host, port):
def portalapi(debug: bool, host: str, port: int) -> None:

allowed_cors_origins = []
allowed_cors_origins_file = "/etc/yunohost/.portal-api-allowed-cors-origins"
Expand All @@ -97,7 +111,7 @@ def portalapi(debug, host, port):
sys.exit(ret)


def check_command_is_valid_before_postinstall(args):
def check_command_is_valid_before_postinstall(args: list[str]) -> None:
allowed_if_not_postinstalled = [
"tools postinstall",
"tools versions",
Expand All @@ -113,28 +127,26 @@ def check_command_is_valid_before_postinstall(args):
sys.exit(1)


def init(interface="cli", debug=False, quiet=False, logdir="/var/log/yunohost"):
def init(interface: str = "cli", debug: bool = False, quiet: bool = False, logdir: str = "/var/log/yunohost") -> MoulinetteLock:
"""
This is a small util function ONLY meant to be used to initialize a Yunohost
context when ran from tests or from scripts.
"""
init_logging(interface=interface, debug=debug, quiet=quiet, logdir=logdir)
init_i18n()
from moulinette.core import MoulinetteLock

lock = MoulinetteLock("yunohost", timeout=30)
lock.acquire()
return lock


def init_i18n():
def init_i18n() -> None:
# This should only be called when not willing to go through moulinette.cli
# or moulinette.api but still willing to call m18n.n/g...
m18n.set_locales_dir("/usr/share/yunohost/locales/")
m18n.set_locale(get_locale())


def init_logging(interface="cli", debug=False, quiet=False, logdir="/var/log/yunohost"):
def init_logging(interface: str = "cli", debug: bool = False, quiet: bool = False, logdir: str = "/var/log/yunohost") -> None:
logfile = os.path.join(logdir, "yunohost-%s.log" % interface)

if not os.path.isdir(logdir):
Expand Down
44 changes: 44 additions & 0 deletions src/authenticators/sockapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
#
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://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.authentication import BaseAuthenticator


class Authenticator(BaseAuthenticator):
name = "sockapi"

def __init__(self, *args, **kwargs):
pass

def _authenticate_credentials(self, credentials=None):
return {"user": 0}

def set_session_cookie(self, infos):
pass

def get_session_cookie(self, raise_if_no_session_exists=True):
return {"id": "pouetpouet"}

def delete_session_cookie(self):
pass

@staticmethod
def invalidate_all_sessions_for_user(user):
pass
Loading