Still work in progress..

This commit is contained in:
Pierre Ozoux 2022-05-02 10:21:25 +00:00
parent 2ee69f7bfd
commit 62ebb5d43e
11 changed files with 156 additions and 274 deletions

View file

@ -53,7 +53,7 @@ for integration tests
# TODO # TODO
- attribute active - [x]attribute active
- group mgmt - group mgmt
- list user - list user
- add/remove user from groups - add/remove user from groups
@ -62,3 +62,11 @@ for integration tests
- [x] json - [x] json
- [x] status code - [x] status code
- [x] escaped location - JSON_UNESCAPED_SLASHES - [x] escaped location - JSON_UNESCAPED_SLASHES
cd apps
wget https://lab.libreho.st/libre.sh/scim/nextcloud-scim/-/archive/test-branch/nextcloud-scim-main.zip
unzip nextcloud-scim-main.zip
rm nextcloud-scim-main.zip
rm -rf scimserviceprovider
mv nextcloud-scim-test-branchxxx scimserviceprovider

View file

@ -1,67 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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/>.
*
*/
namespace OCA\SCIMServiceProvider\AppInfo;
use OC\Group\Manager as GroupManager;
use OCA\Provisioning_API\Capabilities;
use OCA\Provisioning_API\Listener\UserDeletedListener;
use OCA\SCIMServiceProvider\Middleware\SCIMApiMiddleware;
use OCA\Settings\Mailer\NewUserMailHelper;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Utility\IControllerMethodReflector;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Defaults;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Mail\IMailer;
use OCP\Security\ICrypto;
use OCP\Security\ISecureRandom;
use OCP\User\Events\UserDeletedEvent;
use OCP\Util;
use Psr\Container\ContainerInterface;
class Application extends App implements IBootstrap {
public function __construct(array $urlParams = []) {
parent::__construct('scimserviceprovider', $urlParams);
}
public function register(IRegistrationContext $context): void {
$context->registerMiddleware(SCIMApiMiddleware::class);
}
public function boot(IBootContext $context): void {
}
}

View file

@ -43,7 +43,7 @@ use OCP\IRequest;
use OCP\IUserManager; use OCP\IUserManager;
use OCP\IUserSession; use OCP\IUserSession;
use OCP\L10N\IFactory; use OCP\L10N\IFactory;
use OCA\SCIMServiceProvider\Exceptions\SCIMException; use OCA\SCIMServiceProvider\Responses\SCIMErrorResponse;
abstract class ASCIMUser extends ApiController { abstract class ASCIMUser extends ApiController {
/** @var IUserManager */ /** @var IUserManager */
@ -74,18 +74,18 @@ abstract class ASCIMUser extends ApiController {
} }
/** /**
* creates a array with all user data * creates an object with all user data
* *
* @param string $userId * @param string $userId
* @param bool $includeScopes * @param bool $includeScopes
* @return array * @return array
* @throws SCIMException * @throws Exception
*/ */
protected function getSCIMUser(string $userId): array { protected function getSCIMUser(string $userId): array {
// Check if the target user exists // Check if the target user exists
$targetUserObject = $this->userManager->get($userId); $targetUserObject = $this->userManager->get($userId);
if ($targetUserObject === null) { if ($targetUserObject === null) {
throw new SCIMException('User not found', 404); return [];
} }
$enabled = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'enabled', 'true') === 'true'; $enabled = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'enabled', 'true') === 'true';
@ -93,23 +93,25 @@ abstract class ASCIMUser extends ApiController {
return [ return [
'schemas' => ["urn:ietf:params:scim:schemas:core:2.0:User"], 'schemas' => ["urn:ietf:params:scim:schemas:core:2.0:User"],
'id' => $userId, 'id' => $userId,
//'externalId' => $this->externalId, 'name' => [
'formatted' => $targetUserObject->getDisplayName()
],
'meta' => [ 'meta' => [
'resourceType' => 'User', 'resourceType' => 'User',
'location' => '/Users/' . $userId, 'location' => '/Users/' . $userId,
//'created' => Helper::dateTime2string($this->created_at), 'created' => '2022-04-28T18:27:17.783Z', // todo
'lastModified' => '2022-04-28T18:27:17.783Z' // todo
], ],
'userName' => $userId, 'userName' => $userId,
'displayName' => $targetUserObject->getDisplayName(), 'displayName' => $targetUserObject->getDisplayName(),
'emails' => [ 'emails' => [ // todo if no emails
[ [
'primary' => true, 'primary' => true,
'value' => $targetUserObject->getSystemEMailAddress() 'value' => $targetUserObject->getSystemEMailAddress()
] ]
], ],
//'groups' => [], 'externalId' => '1234', // todo
'active' => $enabled 'active' => $enabled
]; ];
} }
} }

View file

@ -45,7 +45,6 @@ namespace OCA\SCIMServiceProvider\Controller;
use InvalidArgumentException; use InvalidArgumentException;
use OC\HintException; use OC\HintException;
use OC\KnownUser\KnownUserService; use OC\KnownUser\KnownUserService;
use OCA\Settings\Mailer\NewUserMailHelper;
use OCP\Accounts\IAccountManager; use OCP\Accounts\IAccountManager;
use OCP\AppFramework\OCS\OCSException; use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCSController; use OCP\AppFramework\OCSController;
@ -60,7 +59,9 @@ use OCP\Security\ISecureRandom;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use OCA\SCIMServiceProvider\Responses\SCIMListResponse; use OCA\SCIMServiceProvider\Responses\SCIMListResponse;
use OCA\SCIMServiceProvider\Responses\SCIMResourceResponse; use OCA\SCIMServiceProvider\Responses\SCIMJSONResponse;
use OCA\SCIMServiceProvider\Responses\SCIMErrorResponse;
class UserController extends ASCIMUser { class UserController extends ASCIMUser {
@ -135,16 +136,16 @@ class UserController extends ASCIMUser {
* gets user info * gets user info
* *
* @param string $id * @param string $id
* @return SCIMResourceResponse * @return SCIMJSONResponse
* @throws SCIMException * @throws Exception
*/ */
public function show(string $id): SCIMResourceResponse { public function show(string $id): SCIMJSONResponse {
$user = $this->getSCIMUser($id); $user = $this->getSCIMUser($id);
// getUserData returns empty array if not enough permissions // getUserData returns empty array if not enough permissions
if (empty($user)) { if (empty($user)) {
throw new SCIMException('User not found', 404); return new SCIMErrorResponse(['message' => 'User not found'], 404);
} }
return new SCIMResourceResponse($user); return new SCIMJSONResponse($user);
} }
/** /**
@ -154,16 +155,16 @@ class UserController extends ASCIMUser {
* @param string $displayName * @param string $displayName
* @param array $emails * @param array $emails
* @param string $userName * @param string $userName
* @return SCIMResourceResponse * @return SCIMJSONResponse
* @throws SCIMException * @throws Exception
*/ */
public function create( bool $active = true, public function create( bool $active = true,
string $displayName = '', string $displayName = '',
array $emails = [], array $emails = [],
string $userName = ''): SCIMResourceResponse { string $userName = ''): SCIMJSONResponse {
if ($this->userManager->userExists($userName)) { if ($this->userManager->userExists($userName)) {
$this->logger->error('Failed createUser attempt: User already exists.', ['app' => 'SCIMServiceProvider']); $this->logger->error('Failed createUser attempt: User already exists.', ['app' => 'SCIMServiceProvider']);
throw new SCIMException('User already exists', 409, 'uniqueness'); return new SCIMErrorResponse(['message' => 'User already exists'], 409);
} }
try { try {
@ -176,8 +177,8 @@ class UserController extends ASCIMUser {
} }
} }
$newUser->setEnabled($active); $newUser->setEnabled($active);
return new SCIMResourceResponse($this->getSCIMUser($userName)); return new SCIMJSONResponse($this->getSCIMUser($userName));
} catch (SCIMException $e) { } catch (Exception $e) {
$this->logger->warning('Failed createUser attempt with SCIMException exeption.', ['app' => 'SCIMServiceProvider']); $this->logger->warning('Failed createUser attempt with SCIMException exeption.', ['app' => 'SCIMServiceProvider']);
throw $e; throw $e;
} }
@ -193,15 +194,15 @@ class UserController extends ASCIMUser {
* @param string $displayName * @param string $displayName
* @param array $emails * @param array $emails
* @return DataResponse * @return DataResponse
* @throws SCIMException * @throws Exception
*/ */
public function update( string $id, public function update( string $id,
bool $active, bool $active,
string $displayName = '', string $displayName = '',
array $emails = []): SCIMResourceResponse { array $emails = []): SCIMJSONResponse {
$targetUser = $this->userManager->get($id); $targetUser = $this->userManager->get($id);
if ($targetUser === null) { if ($targetUser === null) {
throw new SCIMException('User not found', 404); return new SCIMErrorResponse(['message' => 'User not found'], 404);
} }
foreach ($emails as $email) { foreach ($emails as $email) {
if ($email['primary'] === true) { if ($email['primary'] === true) {
@ -211,7 +212,7 @@ class UserController extends ASCIMUser {
if (isset($active)) { if (isset($active)) {
$targetUser->setEnabled($active); $targetUser->setEnabled($active);
} }
return new SCIMResourceResponse($this->getSCIMUser($id)); return new SCIMJSONResponse($this->getSCIMUser($id));
} }
@ -220,13 +221,12 @@ class UserController extends ASCIMUser {
* *
* @param string $id * @param string $id
* @return DataResponse * @return DataResponse
* @throws SCIMException
*/ */
public function destroy(string $id): Response { public function destroy(string $id): Response {
$targetUser = $this->userManager->get($id); $targetUser = $this->userManager->get($id);
if ($targetUser === null) { if ($targetUser === null) {
throw new SCIMException('User not found', 404); return new SCIMErrorResponse(['message' => 'User not found'], 404);
} }
// Go ahead with the delete // Go ahead with the delete
@ -235,7 +235,7 @@ class UserController extends ASCIMUser {
$response->setStatus(204); $response->setStatus(204);
return $response; return $response;
} else { } else {
throw new SCIMException(''); return new SCIMErrorResponse(['message' => 'Couldn\'t delete user'], 503);
} }
} }

View file

@ -1,17 +0,0 @@
<?php
namespace OCA\SCIMServiceProvider\Exception;
class SCIMErrorType
{
const INVALID_FILTER = "invalidFilter";
const TOO_MANY = "tooMany";
const UNIQUENESS = "uniqueness";
const MUTABILITY = "mutability";
const INVALID_SYNTAX = "invalidSyntax";
const INVALID_PATH = "invalidPath";
const NO_TARGET = "noTarget";
const INVALID_VALUE = "invalidValue";
const INVALID_VERS = "invalidVers";
const SENSITIVE = "sensitive";
}

View file

@ -1,47 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* 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/>.
*
*/
namespace OCA\SCIMServiceProvider\Exceptions;
use Exception;
/**
* Class SCIMException
*
* @since 9.1.0
*/
class SCIMException extends Exception {
public function __construct(string $detail,
int $status = 500,
string $scimType='') {
$message = [
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
"detail" => $detail,
"scimType" => $scimType,
"status" => $status
];
parent::__construct(json_encode($message), $status);
}
}

View file

@ -1,80 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016 Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* 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/>.
*
*/
namespace OCA\SCIMServiceProvider\Middleware;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\Utility\IControllerMethodReflector;
use OCA\SCIMServiceProvider\Responses\SCIMResourceResponse;
class SCIMApiMiddleware extends Middleware {
/** @var IControllerMethodReflector */
private $reflector;
/** @var bool */
private $isAdmin;
/** @var bool */
private $isSubAdmin;
/**
* ProvisioningApiMiddleware constructor.
*
* @param IControllerMethodReflector $reflector
* @param bool $isAdmin
* @param bool $isSubAdmin
*/
public function __construct(
IControllerMethodReflector $reflector,
bool $isAdmin,
bool $isSubAdmin) {
$this->reflector = $reflector;
$this->isAdmin = $isAdmin;
$this->isSubAdmin = $isSubAdmin;
}
/**
* @param Controller $controller
* @param string $methodName
* @param \Exception $exception
* @throws \Exception
* @return SCIMResourceResponse
*/
public function afterException($controller, $methodName, \Exception $exception) {
if ($exception instanceof SCIMException) {
return SCIMResourceResponse($exception->getMessage(), 500);
}
return SCIMResourceResponse([
"schema" => "error"
],500);
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace OCA\SCIMServiceProvider\Responses;
use OCP\AppFramework\Http\Response;
use OCA\SCIMServiceProvider\Responses\SCIMJSONResponse;
/**
* Class SCIMErrorResponse
*
*/
class SCIMErrorResponse extends SCIMJSONResponse {
/**
* response data
* @var array|object
*/
protected $data;
/**
* Returns the rendered json
* @return string the rendered json
* @since 6.0.0
* @throws \Exception If data could not get encoded
*/
public function render() {
$message = [
'schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'],
'detail' => $this->data['message'],
'scimType' => '',
'status' => $this->getStatus()
];
$response = json_encode($message, JSON_UNESCAPED_SLASHES);
if ($response === false) {
throw new Exception(sprintf('Could not json_encode due to invalid ' .
'non UTF-8 characters in the array: %s', var_export($this->data, true)));
}
return $response;
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace OCA\SCIMServiceProvider\Responses;
use OCP\AppFramework\Http\Response;
/**
* Class SCIMResourceResponse
*
*/
class SCIMJSONResponse extends Response {
/**
* response data
* @var array|object
*/
protected $data;
/**
* constructor of SCIMResourceResponse
* @param array|object $data the object or array that should be transformed
* @param int $statusCode the Http status code, defaults to 200
* @since 6.0.0
*/
public function __construct($data = [], $statusCode = 200) {
parent::__construct();
$this->data = $data;
$this->setStatus($statusCode);
$this->addHeader('Content-Type', 'application/scim+json; charset=utf-8');
}
/**
* Returns the rendered json
* @return string the rendered json
* @since 6.0.0
* @throws \Exception If data could not get encoded
*/
public function render() {
$response = json_encode($this->data, JSON_UNESCAPED_SLASHES);
if ($response === false) {
throw new Exception(sprintf('Could not json_encode due to invalid ' .
'non UTF-8 characters in the array: %s', var_export($this->data, true)));
}
return $response;
}
}

View file

@ -2,7 +2,7 @@
namespace OCA\SCIMServiceProvider\Responses; namespace OCA\SCIMServiceProvider\Responses;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\Response;
use OCA\SCIMServiceProvider\Exceptions\SCIMException; use OCA\SCIMServiceProvider\Exceptions\SCIMException;
@ -10,7 +10,28 @@ use OCA\SCIMServiceProvider\Exceptions\SCIMException;
* Class SCIMListResponse * Class SCIMListResponse
* *
*/ */
class SCIMListResponse extends JSONResponse { class SCIMListResponse extends Response {
/**
* response data
* @var array|object
*/
protected $data;
/**
* constructor of SCIMListResponse
* @param array|object $data the object or array that should be transformed
* @param int $statusCode the Http status code, defaults to 200
* @since 6.0.0
*/
public function __construct($data = [], $statusCode = 200) {
parent::__construct();
$this->data = $data;
$this->setStatus($statusCode);
$this->addHeader('Content-Type', 'application/scim+json; charset=utf-8');
}
/** /**
* Returns the rendered json * Returns the rendered json
* @return string the rendered json * @return string the rendered json

View file

@ -1,30 +0,0 @@
<?php
namespace OCA\SCIMServiceProvider\Responses;
use OCP\AppFramework\Http\JSONResponse;
use OCA\SCIMServiceProvider\Exceptions\SCIMException;
/**
* Class SCIMResourceResponse
*
*/
class SCIMResourceResponse extends JSONResponse {
/**
* Returns the rendered json
* @return string the rendered json
* @since 6.0.0
* @throws \Exception If data could not get encoded
*/
public function render() {
$response = json_encode($this->data, JSON_UNESCAPED_SLASHES);
if ($response === false) {
throw new SCIMException(sprintf('Could not json_encode due to invalid ' .
'non UTF-8 characters in the array: %s', var_export($this->data, true)));
}
return $response;
}
}