Skip to content

Commit

Permalink
feat: CardDAV support (#1284)
Browse files Browse the repository at this point in the history
  • Loading branch information
kidk authored and asbiin committed Oct 29, 2018
1 parent 3ee534a commit c7eea2b
Show file tree
Hide file tree
Showing 23 changed files with 1,338 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ APP_TRUSTED_PROXIES=

# Frequency of creation of new log files. Logs are written when an error occurs.
# Refer to config/logging.php for the possible values.
LOG_CHANNEL=syslog
LOG_CHANNEL=single

SENTRY_SUPPORT=false
SENTRY_LARAVEL_DSN=
Expand Down
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ AWS_SERVER=
# Allow Two Factor Authentication feature on your instance
MFA_ENABLED=false

# Enable CardDAV support (beta feature)
CARDDAV_ENABLED=false

# CLIENT ID and SECRET used for the official mobile application
# This is to make sure that only the mobile application that you approve can
# access the route to let your users sign in with their credentials
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ UNRELEASED CHANGES:
* API breaking change: Remove 'PUT /contacts/:contact_id/pets/:id' in favor of 'PUT /pets/:id' with a 'contact_id'
* API breaking change: Every validator fails now send a HTTP 400 code (was 200) with the error 32
* API breaking change: Every Invald Parameters errors now send a HTTP 400 (was 500 or 200) code with the error 41
* Add CardDAV support — disabled by default. To enable it, toggle the `CARDDAV_ENABLED` env variable.
* Use Laravel email verification, and remove the old module used for that

RELEASED VERSIONS:
Expand Down
82 changes: 82 additions & 0 deletions app/Http/Controllers/CardDAVController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Barryvdh\Debugbar\Facade as Debugbar;
use App\Models\CardDAV\MonicaAddressBookRoot;
use App\Models\CardDAV\Backends\MonicaSabreBackend;
use App\Models\CardDAV\Backends\MonicaCardDAVBackend;
use App\Models\CardDAV\Backends\MonicaPrincipleBackend;

class CardDAVController extends Controller
{
/**
* Display the specified resource.
*/
public function init(Request $request)
{
// Disable debugger for caldav output
Debugbar::disable();

// Initiate custom backends for link between Sabre and Monica
$authBackend = new MonicaSabreBackend(); // Authentication
$principalBackend = new MonicaPrincipleBackend(); // User rights
$carddavBackend = new MonicaCardDAVBackend(); // Contacts

$nodes = [
new \Sabre\DAVACL\PrincipalCollection($principalBackend),
new MonicaAddressBookRoot($principalBackend, $carddavBackend),
];

// Initiate Sabre server
$server = new \Sabre\DAV\Server($nodes);
$server->setBaseUri('/carddav');
$server->debugExceptions = true;

// Modify Laravel request to include trailing slash. Laravel removes it by default, Sabre needs it.
$server->httpRequest->setUrl(str_replace('/carddav', '/carddav/', $request->getRequestUri()));
$server->httpRequest->setBaseUrl('/carddav/');

// Testing needs method verb to be set manually
if (App::environment('testing')) {
$server->httpRequest->setMethod($request->method());
}

// Add required plugins
$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, 'SabreDAV'));

// CardDAV plugin
$server->addPlugin(new \Sabre\CardDAV\Plugin());

// ACL plugnin
$aclPlugin = new \Sabre\DAVACL\Plugin();
$aclPlugin->allowUnauthenticatedAccess = false;
$server->addPlugin($aclPlugin);

// In debug mode add browser plugin
if (App::environment('local')) {
$server->addPlugin(new \Sabre\DAV\Browser\Plugin());
}

// Execute requests and catch output
// We do this because laravel always sends a 200 back, but we need to use the StatusCode and of Sabre
ob_start();
$server->exec();
$status = $server->httpResponse->getStatus();
$content = ob_get_contents();
$headers = $server->httpResponse->getHeaders();
ob_end_clean();

// Return response through laravel
Log::debug(__CLASS__.' init', [
'status' => $status,
'content' => $content,
]);

return response($content, $status)
->withHeaders($headers);
}
}
1 change: 1 addition & 0 deletions app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Kernel extends HttpKernel
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.tokenonbasic' => \App\Http\Middleware\AuthenticateWithTokenOnBasicAuth::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \App\Http\Middleware\ThrottleRequestsMiddleware::class,
Expand Down
74 changes: 74 additions & 0 deletions app/Http/Middleware/AuthenticateWithTokenOnBasicAuth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Auth\AuthManager;

/**
* Authenticate user with Basic Authentication, with two methods:
* - Basic auth: login + password
* - Bearer on basic: login + api token.
*/
class AuthenticateWithTokenOnBasicAuth
{
/**
* The guard factory instance.
*
* @var AuthManager
*/
protected $auth;

/**
* Create a new middleware instance.
*
* @param AuthManager $auth
* @return void
*/
public function __construct(AuthManager $auth)
{
$this->auth = $auth;
}

/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$this->authenticate($request);

return $next($request);
}

/**
* Handle authentication.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
private function authenticate($request)
{
if ($this->auth->check()) {
return;
}

// Try Bearer authentication, with token in 'password' field on basic auth
if (! $request->bearerToken()) {
$password = $request->getPassword();
$request->headers->set('Authorization', 'Bearer '.$password);
}

$user = $this->auth->guard('api')->user($request);

if ($user && (! $request->getUser() || $request->getUser() === $user->email)) {
$this->auth->setUser($user);
} else {
// Basic authentication
$this->auth->onceBasic();
}
}
}
Loading

0 comments on commit c7eea2b

Please sign in to comment.