Merge branch 'test-branch' into 'main'

Still work in progress..

See merge request libre.sh/scim/nextcloud-scim!1
This commit is contained in:
Pierre Ozoux 2022-05-02 10:21:25 +00:00
commit fa499d9c53
11 changed files with 156 additions and 274 deletions

View file

@ -53,7 +53,7 @@ for integration tests
# TODO
- attribute active
- [x]attribute active
- group mgmt
- list user
- add/remove user from groups
@ -62,3 +62,11 @@ for integration tests
- [x] json
- [x] status code
- [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\IUserSession;
use OCP\L10N\IFactory;
use OCA\SCIMServiceProvider\Exceptions\SCIMException;
use OCA\SCIMServiceProvider\Responses\SCIMErrorResponse;
abstract class ASCIMUser extends ApiController {
/** @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 bool $includeScopes
* @return array
* @throws SCIMException
* @throws Exception
*/
protected function getSCIMUser(string $userId): array {
// Check if the target user exists
$targetUserObject = $this->userManager->get($userId);
if ($targetUserObject === null) {
throw new SCIMException('User not found', 404);
return [];
}
$enabled = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'enabled', 'true') === 'true';
@ -93,23 +93,25 @@ abstract class ASCIMUser extends ApiController {
return [
'schemas' => ["urn:ietf:params:scim:schemas:core:2.0:User"],
'id' => $userId,
//'externalId' => $this->externalId,
'name' => [
'formatted' => $targetUserObject->getDisplayName()
],
'meta' => [
'resourceType' => 'User',
'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,
'displayName' => $targetUserObject->getDisplayName(),
'emails' => [
'emails' => [ // todo if no emails
[
'primary' => true,
'value' => $targetUserObject->getSystemEMailAddress()
]
],
//'groups' => [],
'externalId' => '1234', // todo
'active' => $enabled
];
}
}

View file

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