This commit is contained in:
PierreOzoux 2022-05-18 17:46:40 +02:00
commit 4d3e5da116
17 changed files with 3573 additions and 525 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
vendor

1
.php-cs-fixer.cache Normal file
View file

@ -0,0 +1 @@
{"php":"7.3.33","version":"3.4.0:v3.4.0#47177af1cfb9dab5d1cc4daf91b7179c2efe7fad","indent":"\t","lineEnding":"\n","rules":{"encoding":true,"full_opening_tag":true,"blank_line_after_namespace":true,"braces":{"position_after_anonymous_constructs":"same","position_after_control_structures":"same","position_after_functions_and_oop_constructs":"same"},"class_definition":true,"constant_case":true,"elseif":true,"function_declaration":{"closure_function_spacing":"one"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ignore"},"no_break_comment":true,"no_closing_tag":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":true,"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":{"elements":["property","method","const"]},"align_multiline_comment":true,"array_indentation":true,"binary_operator_spaces":{"default":"single_space"},"blank_line_after_opening_tag":true,"no_unused_imports":true},"hashes":{"appinfo\/routes.php":3044887257,"lib\/Responses\/SCIMJSONResponse.php":2876810868,"lib\/Responses\/SCIMListResponse.php":76345573,"lib\/Responses\/SCIMErrorResponse.php":1118832559,"lib\/Controller\/ASCIMGroup.php":3712028548,"lib\/Controller\/UserController.php":2528458696,"lib\/Controller\/ASCIMUser.php":430477040,"lib\/Controller\/GroupController.php":3769468563}}

18
.php-cs-fixer.dist.php Normal file
View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
require_once './vendor/autoload.php';
use Nextcloud\CodingStandard\Config;
$config = new Config();
$config
->getFinder()
->ignoreVCSIgnored(true)
->notPath('build')
->notPath('l10n')
->notPath('src')
->notPath('vendor')
->in(__DIR__);
return $config;

View file

@ -1,72 +1,47 @@
# S C I M Server
Place this app in **nextcloud/apps/**
# SCIM Service Provider
## Building the app
This app allows to provision users and groups in Nextcloud from a scim client.
The app can be built by using the provided Makefile by running:
You can see the [video](https://hot-objects.liiib.re/meet-liiib-re-recordings/pair_2022-05-02-15-40-37.mp4) that shows how it works.
make
## How to use
This requires the following things to be present:
* make
* which
* tar: for building the archive
* curl: used if phpunit and composer are not installed to fetch them from the web
* npm: for building and testing everything JS, only required if a package.json is placed inside the **js/** folder
We plan to publish on the Nextcloud app store, but in the mean time, you can use instructions at the bottom.
The make command will install or update Composer dependencies if a composer.json is present and also **npm run build** if a package.json is present in the **js/** folder. The npm **build** script should use local paths for build systems and package managers, so people that simply want to build the app won't need to install npm libraries globally, e.g.:
## Use with Keycloak
**package.json**:
```json
"scripts": {
"test": "node node_modules/gulp-cli/bin/gulp.js karma",
"prebuild": "npm install && node_modules/bower/bin/bower install && node_modules/bower/bin/bower update",
"build": "node node_modules/gulp-cli/bin/gulp.js"
}
```
## Publish to App Store
First get an account for the [App Store](http://apps.nextcloud.com/) then run:
make && make appstore
The archive is located in build/artifacts/appstore and can then be uploaded to the App Store.
You can use with the [SCIM plugin we developped for keycloak](https://lab.libreho.st/libre.sh/scim/keycloak-scim).
## Running tests
You can use the provided Makefile to run all tests by using:
make test
To run the test, you can use [insomnia UI](https://docs.insomnia.rest).
This will run the PHP unit and integration tests and if a package.json is present in the **js/** folder will execute **npm run test**
![screenshot insomnia ui](./screenshots/insomnia.png)
Of course you can also install [PHPUnit](http://phpunit.de/getting-started.html) and use the configurations directly:
For CI, there is still [a bug](https://github.com/Kong/insomnia/issues/4747) we need to find a fix.
phpunit -c phpunit.xml
## Todo
or:
- ExternalID
- json exceptions
- Meta ->
- createdAt
- lastModified
- if no emails in user, return nice array
- pagination
- group member removal
phpunit -c phpunit.integration.xml
## Quick "Deploy" to test
for integration tests
# TODO
- [x]attribute active
- group mgmt
- list user
- add/remove user from groups
- update, displayName
- Erreur/Exception
- [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
```
cd apps
wget https://lab.libreho.st/libre.sh/scim/nextcloud-scim/-/archive/main/nextcloud-scim-main.zip
unzip nextcloud-scim-main.zip
rm nextcloud-scim-main.zip
rm -rf scimserviceprovider
mv nextcloud-scim-test-branchxxx scimserviceprovider
mv nextcloud-scim-main scimserviceprovider
```
## NextGov Hackathon
This app was started during the [Nextgov hackathon](https://eventornado.com/submission/automatic-sso-saml-sync-from-identity-provider-keycloak-through-a-well-known-protocol-scim?s=1#idea)!

View file

@ -1,32 +1,7 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Tom Needham <tom@owncloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
return [
'resources' => [
'user' => ['url' => '/Users']
'user' => ['url' => '/Users'],
'group' => ['url' => '/Groups']
]
];

16
composer.json Normal file
View file

@ -0,0 +1,16 @@
{
"require-dev": {
"vimeo/psalm": "^4.23",
"christophwurst/nextcloud": "v22.1.1",
"nextcloud/coding-standard": "^1.0"
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true
}
},
"scripts": {
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix"
}
}

3176
composer.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace OCA\SCIMServiceProvider\Controller;
use OC\Group\Manager;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\ApiController;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\IUserSession;
abstract class ASCIMGroup extends ApiController {
/** @var IUserManager */
protected $userManager;
/** @var IConfig */
protected $config;
/** @var IGroupManager|Manager */ // FIXME Requires a method that is not on the interface
protected $groupManager;
/** @var IUserSession */
protected $userSession;
/** @var IAccountManager */
protected $accountManager;
public function __construct(string $appName,
IRequest $request,
IUserManager $userManager,
IConfig $config,
IGroupManager $groupManager,
IUserSession $userSession,
IAccountManager $accountManager) {
parent::__construct($appName, $request);
$this->userManager = $userManager;
$this->config = $config;
$this->groupManager = $groupManager;
$this->userSession = $userSession;
$this->accountManager = $accountManager;
}
/**
* creates an object with all group data
*
* @param string $groupId
* @param bool $includeScopes
* @return array
* @throws Exception
*/
protected function getSCIMGroup(string $groupId): array {
$groupId = urldecode($groupId);
// Check the group exists
$group = $this->groupManager->get($groupId);
if ($group === null) {
return [];
}
$members = array();
foreach ($this->groupManager->get($groupId)->getUsers() as $member) {
$members[] = [
'value' => $member->getUID(),
'$ref' => '/Users/' . $member->getUID(),
'display' => $member->getDisplayName()
];
}
return [
'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:Group'],
'id' => $groupId,
'displayName' => $group->getDisplayName(),
'externalId' => '1234', // todo
'meta' => [
'resourceType' => 'Group',
'location' => '/Groups/' . $groupId,
'created' => '2022-04-28T18:27:17.783Z', // todo
'lastModified' => '2022-04-28T18:27:17.783Z' // todo
],
'members' => $members
];
}
}

View file

@ -2,48 +2,16 @@
declare(strict_types=1);
/**
* @copyright Copyright (c) 2018 John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @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\Controller;
use OC\Group\Manager;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\PropertyDoesNotExistException;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use OCA\SCIMServiceProvider\Responses\SCIMErrorResponse;
abstract class ASCIMUser extends ApiController {
/** @var IUserManager */
@ -91,27 +59,27 @@ abstract class ASCIMUser extends ApiController {
$enabled = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'enabled', 'true') === 'true';
return [
'schemas' => ["urn:ietf:params:scim:schemas:core:2.0:User"],
'id' => $userId,
'schemas' => ["urn:ietf:params:scim:schemas:core:2.0:User"],
'id' => $userId,
'name' => [
'formatted' => $targetUserObject->getDisplayName()
],
'meta' => [
'meta' => [
'resourceType' => 'User',
'location' => '/Users/' . $userId,
'created' => '2022-04-28T18:27:17.783Z', // todo
'lastModified' => '2022-04-28T18:27:17.783Z' // todo
],
'userName' => $userId,
'displayName' => $targetUserObject->getDisplayName(),
'emails' => [ // todo if no emails
],
'userName' => $userId,
'displayName' => $targetUserObject->getDisplayName(),
'emails' => [ // todo if no emails
[
'primary' => true,
'value' => $targetUserObject->getSystemEMailAddress()
'primary' => true,
'value' => $targetUserObject->getSystemEMailAddress()
]
],
'externalId' => '1234', // todo
'active' => $enabled
];
'active' => $enabled
];
}
}

View file

@ -0,0 +1,168 @@
<?php
declare(strict_types=1);
namespace OCA\SCIMServiceProvider\Controller;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\Http\Response;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\Security\ISecureRandom;
use OCP\EventDispatcher\IEventDispatcher;
use Psr\Log\LoggerInterface;
use OCA\SCIMServiceProvider\Responses\SCIMListResponse;
use OCA\SCIMServiceProvider\Responses\SCIMJSONResponse;
use OCA\SCIMServiceProvider\Responses\SCIMErrorResponse;
class GroupController extends ASCIMGroup {
/** @var IURLGenerator */
protected $urlGenerator;
/** @var LoggerInterface */
private $logger;
/** @var ISecureRandom */
private $secureRandom;
/** @var IEventDispatcher */
private $eventDispatcher;
public function __construct(string $appName,
IRequest $request,
IUserManager $userManager,
IConfig $config,
IGroupManager $groupManager,
IUserSession $userSession,
IAccountManager $accountManager,
IURLGenerator $urlGenerator,
LoggerInterface $logger,
ISecureRandom $secureRandom,
IEventDispatcher $eventDispatcher) {
parent::__construct($appName,
$request,
$userManager,
$config,
$groupManager,
$userSession,
$accountManager);
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->secureRandom = $secureRandom;
$this->eventDispatcher = $eventDispatcher;
}
/**
* @NoCSRFRequired
*
* returns a list of groups and their data
*/
public function index(): SCIMListResponse {
$SCIMGroups = $this->groupManager->search('', null, 0);
$SCIMGroups = array_map(function ($group) {
return $this->getSCIMGroup($group->getGID());
}, $SCIMGroups);
return new SCIMListResponse($SCIMGroups);
}
/**
* @NoCSRFRequired
*
* gets group info
*
* @param string $id
* @return SCIMJSONResponse
* @throws Exception
*/
public function show(string $id): SCIMJSONResponse {
$group = $this->getSCIMGroup($id);
// getUserData returns empty array if not enough permissions
if (empty($group)) {
return new SCIMErrorResponse(['message' => 'Group not found'], 404);
}
return new SCIMJSONResponse($group);
}
/**
* @NoCSRFRequired
*
* @param string $displayName
* @param array $members
* @return SCIMJSONResponse
* @throws Exception
*/
public function create(string $displayName = '',
array $members = []): SCIMJSONResponse {
$id = urlencode($displayName);
// Validate name
if (empty($id)) {
$this->logger->error('Group name not supplied', ['app' => 'provisioning_api']);
return new SCIMErrorResponse(['message' => 'Invalid group name'], 400);
}
// Check if it exists
if ($this->groupManager->groupExists($id)) {
return new SCIMErrorResponse(['message' => 'Group exists'], 409);
}
$group = $this->groupManager->createGroup($id);
if ($group === null) {
return new SCIMErrorResponse(['message' => 'Not supported by backend'], 103);
}
$group->setDisplayName($displayName);
foreach ($members as $member) {
$this->logger->error('Group name not supplied' . $member['value'], ['app' => 'provisioning_api']);
$targetUser = $this->userManager->get($member['value']);
$group->addUser($targetUser);
}
return new SCIMJSONResponse($this->getSCIMGroup($id));
}
/**
* @NoCSRFRequired
*
* @param string $id
*
* @param string $displayName
* @param array $members
* @return DataResponse
* @throws Exception
*/
public function update(string $id,
string $displayName = '',
array $members = []): SCIMJSONResponse {
$group = $this->groupManager->get($id);
if (!$this->groupManager->groupExists($id)) {
return new SCIMErrorResponse(['message' => 'Group not found'], 404);
}
foreach ($members as $member) {
$targetUser = $this->userManager->get($member['value']);
$group->addUser($targetUser);
// todo implement member removal (:
}
return new SCIMJSONResponse($this->getSCIMGroup($id));
}
/**
* @NoCSRFRequired
*
* @param string $id
* @return DataResponse
*/
public function destroy(string $id): Response {
$groupId = urldecode($id);
// Check it exists
if (!$this->groupManager->groupExists($groupId)) {
return new SCIMErrorResponse(['message' => 'Group not found'], 404);
} elseif ($groupId === 'admin' || !$this->groupManager->get($groupId)->delete()) {
// Cannot delete admin group
return new SCIMErrorResponse(['message' => 'Can\'t delete this group, not enough rights or admin group'], 403);
}
$response = new Response();
$response->setStatus(204);
return $response;
}
}

View file

@ -1,320 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Tom Needham <tom@owncloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\SCIMServiceProvider\Controller;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IConfig;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
class GroupsController extends AUserData {
/** @var LoggerInterface */
private $logger;
public function __construct(string $appName,
IRequest $request,
IUserManager $userManager,
IConfig $config,
IGroupManager $groupManager,
IUserSession $userSession,
IAccountManager $accountManager,
LoggerInterface $logger) {
parent::__construct($appName,
$request,
$userManager,
$config,
$groupManager,
$userSession,
$accountManager
);
$this->logger = $logger;
}
/**
* returns a list of groups
*
* @NoAdminRequired
*
* @param string $search
* @param int $limit
* @param int $offset
* @return DataResponse
*/
public function getGroups(string $search = '', int $limit = null, int $offset = 0): DataResponse {
$groups = $this->groupManager->search($search, $limit, $offset);
$groups = array_map(function ($group) {
/** @var IGroup $group */
return $group->getGID();
}, $groups);
return new DataResponse(['groups' => $groups]);
}
/**
* returns a list of groups details with ids and displaynames
*
* @NoAdminRequired
*
* @param string $search
* @param int $limit
* @param int $offset
* @return DataResponse
*/
public function getGroupsDetails(string $search = '', int $limit = null, int $offset = 0): DataResponse {
$groups = $this->groupManager->search($search, $limit, $offset);
$groups = array_map(function ($group) {
/** @var IGroup $group */
return [
'id' => $group->getGID(),
'displayname' => $group->getDisplayName(),
'usercount' => $group->count(),
'disabled' => $group->countDisabled(),
'canAdd' => $group->canAddUser(),
'canRemove' => $group->canRemoveUser(),
];
}, $groups);
return new DataResponse(['groups' => $groups]);
}
/**
* @NoAdminRequired
*
* @param string $groupId
* @return DataResponse
* @throws OCSException
*
* @deprecated 14 Use getGroupUsers
*/
public function getGroup(string $groupId): DataResponse {
return $this->getGroupUsers($groupId);
}
/**
* returns an array of users in the specified group
*
* @NoAdminRequired
*
* @param string $groupId
* @return DataResponse
* @throws OCSException
*/
public function getGroupUsers(string $groupId): DataResponse {
$groupId = urldecode($groupId);
$user = $this->userSession->getUser();
$isSubadminOfGroup = false;
// Check the group exists
$group = $this->groupManager->get($groupId);
if ($group !== null) {
$isSubadminOfGroup = $this->groupManager->getSubAdmin()->isSubAdminOfGroup($user, $group);
} else {
throw new OCSNotFoundException('The requested group could not be found');
}
// Check subadmin has access to this group
if ($this->groupManager->isAdmin($user->getUID())
|| $isSubadminOfGroup) {
$users = $this->groupManager->get($groupId)->getUsers();
$users = array_map(function ($user) {
/** @var IUser $user */
return $user->getUID();
}, $users);
$users = array_values($users);
return new DataResponse(['users' => $users]);
}
throw new OCSForbiddenException();
}
/**
* returns an array of users details in the specified group
*
* @NoAdminRequired
*
* @param string $groupId
* @param string $search
* @param int $limit
* @param int $offset
* @return DataResponse
* @throws OCSException
*/
public function getGroupUsersDetails(string $groupId, string $search = '', int $limit = null, int $offset = 0): DataResponse {
$groupId = urldecode($groupId);
$currentUser = $this->userSession->getUser();
// Check the group exists
$group = $this->groupManager->get($groupId);
if ($group !== null) {
$isSubadminOfGroup = $this->groupManager->getSubAdmin()->isSubAdminOfGroup($currentUser, $group);
} else {
throw new OCSException('The requested group could not be found', OCSController::RESPOND_NOT_FOUND);
}
// Check subadmin has access to this group
if ($this->groupManager->isAdmin($currentUser->getUID()) || $isSubadminOfGroup) {
$users = $group->searchUsers($search, $limit, $offset);
// Extract required number
$usersDetails = [];
foreach ($users as $user) {
try {
/** @var IUser $user */
$userId = (string)$user->getUID();
$userData = $this->getUserData($userId);
// Do not insert empty entry
if (!empty($userData)) {
$usersDetails[$userId] = $userData;
} else {
// Logged user does not have permissions to see this user
// only showing its id
$usersDetails[$userId] = ['id' => $userId];
}
} catch (OCSNotFoundException $e) {
// continue if a users ceased to exist.
}
}
return new DataResponse(['users' => $usersDetails]);
}
throw new OCSException('The requested group could not be found', OCSController::RESPOND_NOT_FOUND);
}
/**
* creates a new group
*
* @PasswordConfirmationRequired
*
* @param string $groupid
* @param string $displayname
* @return DataResponse
* @throws OCSException
*/
public function addGroup(string $groupid, string $displayname = ''): DataResponse {
// Validate name
if (empty($groupid)) {
$this->logger->error('Group name not supplied', ['app' => 'provisioning_api']);
throw new OCSException('Invalid group name', 101);
}
// Check if it exists
if ($this->groupManager->groupExists($groupid)) {
throw new OCSException('group exists', 102);
}
$group = $this->groupManager->createGroup($groupid);
if ($group === null) {
throw new OCSException('Not supported by backend', 103);
}
if ($displayname !== '') {
$group->setDisplayName($displayname);
}
return new DataResponse();
}
/**
* @PasswordConfirmationRequired
*
* @param string $groupId
* @param string $key
* @param string $value
* @return DataResponse
* @throws OCSException
*/
public function updateGroup(string $groupId, string $key, string $value): DataResponse {
$groupId = urldecode($groupId);
if ($key === 'displayname') {
$group = $this->groupManager->get($groupId);
if ($group->setDisplayName($value)) {
return new DataResponse();
}
throw new OCSException('Not supported by backend', 101);
} else {
throw new OCSException('', OCSController::RESPOND_UNKNOWN_ERROR);
}
}
/**
* @PasswordConfirmationRequired
*
* @param string $groupId
* @return DataResponse
* @throws OCSException
*/
public function deleteGroup(string $groupId): DataResponse {
$groupId = urldecode($groupId);
// Check it exists
if (!$this->groupManager->groupExists($groupId)) {
throw new OCSException('', 101);
} elseif ($groupId === 'admin' || !$this->groupManager->get($groupId)->delete()) {
// Cannot delete admin group
throw new OCSException('', 102);
}
return new DataResponse();
}
/**
* @param string $groupId
* @return DataResponse
* @throws OCSException
*/
public function getSubAdminsOfGroup(string $groupId): DataResponse {
// Check group exists
$targetGroup = $this->groupManager->get($groupId);
if ($targetGroup === null) {
throw new OCSException('Group does not exist', 101);
}
/** @var IUser[] $subadmins */
$subadmins = $this->groupManager->getSubAdmin()->getGroupsSubAdmins($targetGroup);
// New class returns IUser[] so convert back
$uids = [];
foreach ($subadmins as $user) {
$uids[] = $user->getUID();
}
return new DataResponse($uids);
}
}

View file

@ -2,52 +2,9 @@
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author michag86 <micha_g@arcor.de>
* @author Mikael Hammarin <mikael@try2.se>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Sujith Haridasan <sujith.h@gmail.com>
* @author Thomas Citharel <nextcloud@tcit.fr>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Tom Needham <tom@owncloud.com>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\SCIMServiceProvider\Controller;
use InvalidArgumentException;
use OC\HintException;
use OC\KnownUser\KnownUserService;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCSController;
use OCP\AppFramework\Http\Response;
use OCP\IConfig;
use OCP\IGroupManager;
@ -62,7 +19,6 @@ use OCA\SCIMServiceProvider\Responses\SCIMListResponse;
use OCA\SCIMServiceProvider\Responses\SCIMJSONResponse;
use OCA\SCIMServiceProvider\Responses\SCIMErrorResponse;
class UserController extends ASCIMUser {
/** @var IURLGenerator */
@ -71,8 +27,6 @@ class UserController extends ASCIMUser {
private $logger;
/** @var ISecureRandom */
private $secureRandom;
/** @var KnownUserService */
private $knownUserService;
/** @var IEventDispatcher */
private $eventDispatcher;
@ -86,7 +40,6 @@ class UserController extends ASCIMUser {
IURLGenerator $urlGenerator,
LoggerInterface $logger,
ISecureRandom $secureRandom,
KnownUserService $knownUserService,
IEventDispatcher $eventDispatcher) {
parent::__construct($appName,
$request,
@ -99,7 +52,6 @@ class UserController extends ASCIMUser {
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->secureRandom = $secureRandom;
$this->knownUserService = $knownUserService;
$this->eventDispatcher = $eventDispatcher;
}
@ -120,10 +72,6 @@ class UserController extends ASCIMUser {
// Do not insert empty entry
if (!empty($SCIMUser)) {
$SCIMUsers[] = $SCIMUser;
} else {
// Logged user does not have permissions to see this user
// only showing its id
$SCIMUsers[$userId] = ['id' => $userId];
}
}
@ -153,13 +101,13 @@ class UserController extends ASCIMUser {
*
* @param bool $active
* @param string $displayName
* @param array $emails
* @param array $emails
* @param string $userName
* @return SCIMJSONResponse
* @throws Exception
*/
public function create( bool $active = true,
string $displayName = '',
public function create(bool $active = true,
string $displayName = '',
array $emails = [],
string $userName = ''): SCIMJSONResponse {
if ($this->userManager->userExists($userName)) {
@ -169,7 +117,7 @@ class UserController extends ASCIMUser {
try {
$newUser = $this->userManager->createUser($userName, $this->secureRandom->generate(64));
$this->logger->info('Successful createUser call with userid: ' . ['app' => 'SCIMServiceProvider']);
$this->logger->info('Successful createUser call with userid: ' . $userName, ['app' => 'SCIMServiceProvider']);
foreach ($emails as $email) {
$this->logger->error('Log email: ' . $email['value'], ['app' => 'SCIMServiceProvider']);
if ($email['primary'] === true) {
@ -192,11 +140,11 @@ class UserController extends ASCIMUser {
*
* @param bool $active
* @param string $displayName
* @param array $emails
* @param array $emails
* @return DataResponse
* @throws Exception
*/
public function update( string $id,
public function update(string $id,
bool $active,
string $displayName = '',
array $emails = []): SCIMJSONResponse {
@ -213,7 +161,6 @@ class UserController extends ASCIMUser {
$targetUser->setEnabled($active);
}
return new SCIMJSONResponse($this->getSCIMUser($id));
}
/**
@ -238,5 +185,4 @@ class UserController extends ASCIMUser {
return new SCIMErrorResponse(['message' => 'Couldn\'t delete user'], 503);
}
}
}

View file

@ -3,9 +3,6 @@
namespace OCA\SCIMServiceProvider\Responses;
use OCP\AppFramework\Http\Response;
use OCA\SCIMServiceProvider\Responses\SCIMJSONResponse;
/**
* Class SCIMErrorResponse
@ -26,11 +23,11 @@ class SCIMErrorResponse extends SCIMJSONResponse {
*/
public function render() {
$message = [
'schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'],
'detail' => $this->data['message'],
'scimType' => '',
'status' => $this->getStatus()
];
'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) {

View file

@ -39,12 +39,12 @@ class SCIMListResponse extends Response {
* @throws \Exception If data could not get encoded
*/
public function render() {
$scimReponse = [
'schemas' => ['urn:ietf:params:scim:api:messages:2.0:ListResponse'],
'startIndex' => 1, // todo pagination
'Resources' => $this->data,
'totalResults' => sizeof($this->data)
];
$scimReponse = [
'schemas' => ['urn:ietf:params:scim:api:messages:2.0:ListResponse'],
'startIndex' => 1, // todo pagination
'Resources' => $this->data,
'totalResults' => sizeof($this->data)
];
$response = json_encode($scimReponse, JSON_UNESCAPED_SLASHES);
if ($response === false) {

43
psalm.xml Normal file
View file

@ -0,0 +1,43 @@
<?xml version="1.0"?>
<psalm
errorLevel="4"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config"
>
<projectFiles>
<directory name="lib" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<extraFiles>
<directory name="vendor" />
</extraFiles>
<issueHandlers>
<UndefinedClass>
<errorLevel type="suppress">
<referencedClass name="OC" />
<referencedClass name="OC\Group\Manager" />
<referencedClass name="OC\Security\CSP\ContentSecurityPolicyNonceManager" />
<referencedClass name="Psr\Http\Client\ClientExceptionInterface" />
</errorLevel>
</UndefinedClass>
<UndefinedDocblockClass>
<errorLevel type="suppress">
<referencedClass name="Doctrine\DBAL\Driver\Statement" />
<referencedClass name="Doctrine\DBAL\Schema\Schema" />
<referencedClass name="Doctrine\DBAL\Schema\SchemaException" />
<referencedClass name="Doctrine\DBAL\Schema\Table" />
<referencedClass name="OC\Security\CSP\ContentSecurityPolicyNonceManager" />
<referencedClass name="OC\Group\Manager" />
</errorLevel>
</UndefinedDocblockClass>
<UndefinedInterfaceMethod>
<errorLevel type="suppress">
<referencedMethod name="OCP\IUser::getSystemEMailAddress" />
</errorLevel>
</UndefinedInterfaceMethod>
</issueHandlers>
</psalm>

BIN
screenshots/insomnia.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB