Skip to content

Conversation

@unkn0wnAPI
Copy link

Description

This PR adds an API endpoint that allows admins to perform the following user/calendar management tasks, which are normally only available via the admin dashboard:

  • Getting a list of all users added to Davis
  • Getting details of a specific user account
  • Getting a list of all calendars owned by a specific user
  • Getting details about a specific calendar owned by a specific user
  • Getting a list of users that have access to another user's calendar (Shared Calendar)
  • Sharing a user's calendar with a specific user (with write access enabled/disabled)
  • Removing access to a user's calendar from a specific user

Additionally, this PR would include:

  • Markdown API documentation with examples
  • Brief information about the endpoint in the main README.md file
  • A new Symfony command that can generate an API key

Reasoning for this PR

I wanted to centralize and automate calendar management in my environment. Rather than emulating browser clicks, I created an API endpoint that allows admins to perform basic tasks via API requests.

The logic for specific endpoints is based on existing code in this project and was designed to mimic its output and functionality, with additional security checks. I made the API endpoint paths match the paths I found using console debug:router to make it easier to understand what each route does. Since I use Davis with OpenLDAP, I skipped the logic regarding user creation, removal, etc.

Source / References

Logic found in /src/Controller/*.php files
Symfony Command Creation

Copy link
Owner

@tchapi tchapi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @unkn0wnAPI 👋🏼

First of all, thanks for your interest and the work you put in adding this feature.

I need to get my head around it and I lack some time right now, but I'll probably be able to dedicate more focus in the coming week. Still, I've added a few comments on the code so we can move forward.

Apart from them, an important point would be on the API authentication. I'd rather use a standard Symfony protocol than custom checks in the controller (maybe JSON-login, tied to the admin user or to each user, or a custom authenticator that indeed uses an API-key header).

Happy to discuss, and thanks again!

Comment on lines 61 to 63
if (!$principals) {
return $this->json(['status' => 'success', 'data' => []], 200);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uneeded

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in commit f6b302d

}

foreach ($principals as $principal) {
$users[] = [
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you should declare $users = [] before the loop

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 4f92ed5


$response = [
'status' => 'success',
'data' => $data ?? [],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$data is never falsey

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 7dc6fa2

Comment on lines 102 to 104
if (!$user) {
return $this->json(['status' => 'success', 'data' => []], 200);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not a 404?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first I thought that in theory no data return on valid API request is valid output, but you are right, it is better to return 404 to user for better DX.
Fixed in commit 0c28377

Comment on lines 137 to 139
if (empty($username) || !is_string($username) || preg_match('/[^a-zA-Z0-9_-]/', $username)) {
return $this->json(['status' => 'error', 'message' => 'Invalid Username'], 400);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a helper, maybe validateUserName() ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover it could probably be done with a route requirement

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added both helper function (to be used if it's required in the future) and moved the username check to route requirement.
Fixed in commit: d8fc2e9

Comment on lines 144 to 146
if (!$allCalendars && !$subscriptions) {
return $this->json(['status' => 'success', 'data' => []], 200);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: not needed, but you should declare $calendars etc before the loop below

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in commit 9f719b2

Comment on lines 216 to 218
if (empty($calendar_id) || !is_int($calendar_id)) {
return $this->json(['status' => 'error', 'message' => 'Invalid Calendar ID'], 400);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this validation done by the route requirements already?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in commit 9f719b2

@unkn0wnAPI
Copy link
Author

unkn0wnAPI commented Jan 28, 2026

Hi,
Thank you for the message and for this project.

I will try my best to go over the code in the next few days (midterms are approaching, which limits my free time). In the meantime, I attempted to address most of the code review questions.

Regarding API authentication, I would need to explore the options available in Symfony, as I have not worked with Symfony Custom/JSON Auth before. However, I believe this functionality should be restricted to administrators, since most of the API functionality revolves around admin tasks (most users only interact with their CalDav-supporting application of choice).

Both JSON Login and API Key authentication seem promising, depending on the implementation:

  • JSON Login allows for token expiration.
  • Including the API Key in the request body could simplify the API flow in applications by removing the need to check for reauthentication with the endpoint.

Do you have a preferred approach or any suggestions regarding API authentication?

@tchapi
Copy link
Owner

tchapi commented Jan 28, 2026

I will try my best to go over the code in the next few days (midterms are approaching, which limits my free time)

Absolutely no pressure. I myself have a tight planning.

However, I believe this functionality should be restricted to administrators, since most of the API functionality revolves around admin tasks (most users only interact with their CalDav-supporting application of choice).

Agreed

Do you have a preferred approach or any suggestions regarding API authentication?

I guess an API key in the request (headers probably) — just like you initially envisioned — but embedded in a proper Symfony class / process should fit the use case quite well.

BR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants