processone / processone/ejabberd

API permissions

Open
#4,619 3 comments 0 reactions 1 assignee View on GitHub

@badlop is already working on this.

Since Sep 18, 2026.

Dominant language
Erlang
Stars
6.7k
Forks
1.6k
PR merge metrics
No merged PRs in 30d

Description

Hello,

I am working with ejabberd 26.07.7 and have encountered an authorization issue involving a custom API command, policy = user, and api_permissions.

I would like to confirm whether the behavior described below is intentional and, if so, what the officially supported configuration is for this use case.

  1. Custom command

I have developed a small custom module exposing the following ejabberd command:

#ejabberd_commands{
    name = get_account_created_at,
    tags = [accounts],
    desc = "Get XMPP account creation timestamp",
    longdesc =
        "Returns the creation timestamp of an XMPP account. "
        "The authenticated caller can only query an account "
        "belonging to the same XMPP host.",
    module = ?MODULE,
    function = get_account_created_at,
    policy = user,
    args = [
        {target_user, binary},
        {target_host, binary}
    ],
    args_desc = [
        "Target username",
        "Target XMPP host"
    ],
    result = {created_at, string}
}.

The implementation is deliberately restricted to the caller's own XMPP host:

get_account_created_at(
    _CallerUser,
    CallerHost,
    TargetUser,
    TargetHost
) ->
    case CallerHost =:= TargetHost of
        true ->
            query_created_at(TargetUser, TargetHost);
        false ->
            {error, <<"forbidden">>}
    end.
  1. API authorization

The command is exposed through mod_http_api.

The relevant api_permissions configuration is:

api_permissions:
  "account_creation_api":
    from:
      - mod_http_api
    who:
      acl: account_creation_api_user
    what:
      - "get_account_created_at"

acl:
  account_creation_api_user:
    user: "authorized_m_instance@totototo.tld"

The account is intentionally a normal XMPP account:

authorized_m_instance@totototo.tld

It is not intended to have administrative privileges.

  1. Access configuration

The server currently has:

access_rules:
  configure:
    - allow: admin
    - allow: hostadmin
    - allow: loopback
    - deny: all


and:

mod_configure:
  access: configure

mod_http_api:
  default_version: 2
  1. Observed behavior

When authorized_m_instance@totototo.tld is neither admin nor hostadmin, the authenticated HTTP request returns:

HTTP/1.1 403 Forbidden
{"code":32,"message":"AccessRules: Account does not have the right to perform the operation.","status":"error"}

However, if I add exactly the same account to the hostadmin group, the same HTTP request succeeds and returns:

HTTP/1.1 200 OK
"2026-08-01 13:50:22.84842"

Making the account an administrator also makes the request succeed.

The command itself works correctly when executed through ejabberdctl, so the problem is specifically related to authorization when the command is invoked through the HTTP API as a normal authenticated user.

  1. Source-code/history investigation

While investigating the ejabberd source code, I found two relevant commits in the ejabberd history.

The first is:

16f4c90 — "When command has host argument, check account is admin on that host"

The commit modifies ejabberd_access_permissions.erl and introduces a can_access/4 path which extracts a host argument and checks it using the configure access rule:

acl:match_rule(
    HostArg,
    configure,
    jid:make(USR)
)

The commit also changes command execution so that this extended permission check receives the command arguments.

The second relevant commit is:

62957e6 — "When policy=user, add arguments user,host during command registration"

This adds the user and host arguments to commands declared with:

policy = user

This appears particularly relevant because the command in my module is declared with policy = user.

The resulting interaction appears to be:

policy = user
        |
        +--> implicit user argument
        |
        +--> implicit host argument
                     |
                     v
          host/configure permission check
                     |
          +----------+----------+
          |                     |
       admin/hostadmin       normal user
          |                     |
       permitted              denied

This would explain the observed behavior:

normal authenticated user → HTTP 403

hostadmin → HTTP 200

admin → HTTP 200

I may be misunderstanding the intended interaction between these mechanisms, so I would appreciate confirmation from the ejabberd developers.

  1. Security requirement

The intended security model is very strict.

The account:

authorized_m_instance@totototo.tld

should:

remain a normal XMPP account;

not be an admin;

not be a hostadmin;

not receive general configure privileges;

be authorized only for get_account_created_at;

be unable to execute other administrative API commands.

The command itself additionally verifies that:

CallerHost == TargetHost

The purpose of api_permissions is specifically to restrict this account to this single command.

I therefore do not want to solve the problem by granting hostadmin or configure privileges to this account.

  1. Question

According to the ejabberd documentation, policy = user is the mechanism used to make a command available to a server user through the REST/XML-RPC API, while api_permissions controls which authenticated users may execute particular API commands.

Could you please clarify the following:

Is it intentional that a policy = user command containing the implicit host argument requires the authenticated user to have configure access for that host?

If this is intentional, what is the officially supported way to expose a custom policy = user command through mod_http_api to a normal non-administrative XMPP account?

Is there another command policy or command definition that should be used when the desired authorization is exclusively controlled by api_permissions?

If api_permissions is intended to authorize the specific command, should the configure check introduced by the host argument occur before or after the api_permissions authorization?

If there is currently no supported way to implement this security model, should this behavior be considered a limitation or a bug in the interaction between policy = user, the implicit host argument, and api_permissions?

I am specifically looking for a solution that does not require:

making the API account an administrator;

making it a host administrator;

granting it general configure privileges;

modifying the ejabberd core source code.

The goal is simply to allow one authenticated XMPP account to execute one specifically authorized custom HTTP API command.

Environment:

ejabberd: 26.07.7
mod_http_api: enabled
API version: 2
Custom module: mod_account_created_at
Schema: singlehost

Thank you for clarifying the intended authorization model and the recommended supported solution for this use case.

Best regards,

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.