Skip to content
<?php
namespace OCA\SCIMServiceProvider\Repositories\Groups;
use Opf\Models\SCIM\Standard\Groups\CoreGroup;
use Opf\Repositories\Repository;
use Opf\Util\Filters\FilterUtil;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class NextcloudGroupRepository extends Repository
{
/** @var Psr\Log\LoggerInterface */
private $logger;
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
$this->dataAccess = $container->get('GroupDataAccess');
$this->adapter = $container->get('GroupAdapter');
$this->logger = $container->get(LoggerInterface::class);
}
/**
* Read all groups in SCIM format
*/
public function getAll(
$filter = '',
$startIndex = 0,
$count = 0,
$attributes = [],
$excludedAttributes = []
): array {
$this->logger->info(
"[" . NextcloudGroupRepository::class . "] reading all groups"
);
// Read all NC groups
$ncGroups = $this->dataAccess->getAll();
$scimGroups = [];
$this->logger->info(
"[" . NextcloudGroupRepository::class . "] fetched " . count($ncGroups) . " NC groups"
);
foreach ($ncGroups as $ncGroup) {
$scimGroup = $this->adapter->getCoreGroup($ncGroup);
$scimGroups[] = $scimGroup;
}
$this->logger->info(
"[" . NextcloudGroupRepository::class . "] transformed " . count($scimGroups) . " SCIM groups"
);
if (isset($filter) && !empty($filter)) {
$scimGroupsToFilter = [];
foreach ($scimGroups as $scimGroup) {
$scimGroupsToFilter[] = $scimGroup->toSCIM(false);
}
$filteredScimData = FilterUtil::performFiltering($filter, $scimGroupsToFilter);
$scimGroups = [];
foreach ($filteredScimData as $filteredScimGroup) {
$scimGroup = new CoreGroup();
$scimGroup->fromSCIM($filteredScimGroup);
$scimGroups[] = $scimGroup;
}
return $scimGroups;
}
return $scimGroups;
}
/**
* Read a single group by ID in SCIM format
*/
public function getOneById(
string $id,
$filter = '',
$startIndex = 0,
$count = 0,
$attributes = [],
$excludedAttributes = []
): ?CoreGroup {
$this->logger->info(
"[" . NextcloudGroupRepository::class . "] reading group with ID: " . $id
);
$ncGroup = $this->dataAccess->getOneById($id);
return $this->adapter->getCoreGroup($ncGroup);
}
/**
* Create a group from SCIM data
*/
public function create($object): ?CoreGroup
{
$scimGroupToCreate = new CoreGroup();
$scimGroupToCreate->fromSCIM($object);
$displayName = $scimGroupToCreate->getDisplayName();
$ncGroupCreated = $this->dataAccess->create($displayName);
$this->logger->info(
"[" . NextcloudGroupRepository::class . "] creating group with displayName: " . $displayName
);
if (isset($ncGroupCreated)) {
// Set the rest of the properties of the NC group with the adapter
$ncGroupCreated = $this->adapter->getNCGroup($scimGroupToCreate, $ncGroupCreated);
return $this->adapter->getCoreGroup($ncGroupCreated);
}
$this->logger->error(
"[" . NextcloudGroupRepository::class . "] creation of group with displayName: " . $displayName . " failed"
);
return null;
}
/**
* Update a group by ID from SCIM data
*/
public function update(string $id, $object): ?CoreGroup
{
$this->logger->info(
"[" . NextcloudGroupRepository::class . "] updating group with ID: " . $id
);
$scimGroupToUpdate = new CoreGroup();
$scimGroupToUpdate->fromSCIM($object);
$ncGroup = $this->dataAccess->getOneById($id);
if (isset($ncGroup)) {
$ncGroupToUpdate = $this->adapter->getNCGroup($scimGroupToUpdate, $ncGroup);
$ncGroupUpdated = $this->dataAccess->update($id, $ncGroupToUpdate);
if (isset($ncGroupUpdated)) {
return $this->adapter->getCoreGroup($ncGroupUpdated);
}
}
$this->logger->error(
"[" . NextcloudGroupRepository::class . "] update of group with ID: " . $id . " failed"
);
return null;
}
/**
* Delete a group by ID
*/
public function delete(string $id): bool
{
$this->logger->info(
"[" . NextcloudGroupRepository::class . "] deleting group with ID: " . $id
);
return $this->dataAccess->delete($id);
}
}
<?php
namespace OCA\SCIMServiceProvider\Repositories\Users;
use Opf\Models\SCIM\Standard\Users\CoreUser;
use Opf\Repositories\Repository;
use Opf\Util\Filters\FilterUtil;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class NextcloudUserRepository extends Repository
{
/** @var Psr\Log\LoggerInterface */
private $logger;
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
$this->dataAccess = $container->get('UserDataAccess');
$this->adapter = $container->get('UserAdapter');
$this->logger = $container->get(LoggerInterface::class);
}
/**
* Read all users in SCIM format
*/
public function getAll(
$filter = '',
$startIndex = 0,
$count = 0,
$attributes = [],
$excludedAttributes = []
): array {
$this->logger->info(
"[" . NextcloudUserRepository::class . "] reading all users"
);
// Read all NC users
$ncUsers = $this->dataAccess->getAll();
$scimUsers = [];
$this->logger->info(
"[" . NextcloudUserRepository::class . "] fetched " . count($ncUsers) . " NC users"
);
foreach ($ncUsers as $ncUser) {
$scimUser = $this->adapter->getCoreUser($ncUser);
$scimUsers[] = $scimUser;
}
$this->logger->info(
"[" . NextcloudUserRepository::class . "] transformed " . count($scimUsers) . " SCIM users"
);
if (isset($filter) && !empty($filter)) {
$scimUsersToFilter = [];
foreach ($scimUsers as $scimUser) {
$scimUsersToFilter[] = $scimUser->toSCIM(false);
}
$filteredScimData = FilterUtil::performFiltering($filter, $scimUsersToFilter);
$scimUsers = [];
foreach ($filteredScimData as $filteredScimUser) {
$scimUser = new CoreUser();
$scimUser->fromSCIM($filteredScimUser);
$scimUsers[] = $scimUser;
}
return $scimUsers;
}
return $scimUsers;
}
/**
* Read a single user by ID in SCIM format
*/
public function getOneById(
string $id,
$filter = '',
$startIndex = 0,
$count = 0,
$attributes = [],
$excludedAttributes = []
): ?CoreUser {
$this->logger->info(
"[" . NextcloudUserRepository::class . "] reading user with ID: " . $id
);
$ncUser = $this->dataAccess->getOneById($id);
$scimUser = $this->adapter->getCoreUser($ncUser);
if (isset($filter) && !empty($filter)) {
$scimUsersToFilter = array($scimUser->toSCIM(false));
$filteredScimData = FilterUtil::performFiltering($filter, $scimUsersToFilter);
if (!empty($filteredScimData)) {
$scimUser = new CoreUser();
$scimUser->fromSCIM($filteredScimData[0]);
return $scimUser;
}
}
return $scimUser;
}
/**
* Create a user from SCIM data
*/
public function create($object): ?CoreUser
{
$scimUserToCreate = new CoreUser();
$scimUserToCreate->fromSCIM($object);
$username = $scimUserToCreate->getUserName();
$ncUserCreated = $this->dataAccess->create($username);
$this->logger->info(
"[" . NextcloudUserRepository::class . "] creating user with userName: " . $username
);
if (isset($ncUserCreated)) {
// Set the rest of the properties of the NC user via the adapter
$ncUserCreated = $this->adapter->getNCUser($scimUserToCreate, $ncUserCreated);
return $this->adapter->getCoreUser($ncUserCreated);
}
$this->logger->error(
"[" . NextcloudUserRepository::class . "] creation of user with username: " . $username . " failed"
);
return null;
}
/**
* Update a user by ID from SCIM data
*/
public function update(string $id, $object): ?CoreUser
{
$this->logger->info(
"[" . NextcloudUserRepository::class . "] updating user with ID: " . $id
);
$scimUserToUpdate = new CoreUser();
$scimUserToUpdate->fromSCIM($object);
$ncUser = $this->dataAccess->getOneById($id);
if (isset($ncUser)) {
$ncUserToUpdate = $this->adapter->getNCUser($scimUserToUpdate, $ncUser);
$ncUserUpdated = $this->dataAccess->update($id, $ncUserToUpdate);
if (isset($ncUserUpdated)) {
return $this->adapter->getCoreUser($ncUserUpdated);
}
}
$this->logger->error(
"[" . NextcloudUserRepository::class . "] update of user with ID: " . $id . " failed"
);
return null;
}
/**
* Delete a user by ID
*/
public function delete(string $id): bool
{
$this->logger->info(
"[" . NextcloudUserRepository::class . "] deleting user with ID: " . $id
);
return $this->dataAccess->delete($id);
}
}
<?php
declare(strict_types=1);
namespace OCA\SCIMServiceProvider\Service;
use Exception;
use OCA\SCIMServiceProvider\Responses\SCIMErrorResponse;
use OCA\SCIMServiceProvider\Responses\SCIMJSONResponse;
use OCA\SCIMServiceProvider\Responses\SCIMListResponse;
use OCA\SCIMServiceProvider\Util\Util;
use OCP\AppFramework\Http\Response;
use OCP\IGroupManager;
use OCP\IRequest;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class GroupService
{
/** @var LoggerInterface */
private $logger;
/** @var \OCA\SCIMServiceProvider\Repositories\Groups\NextcloudGroupRepository */
private $repository;
/** @var IGroupManager */
private $groupManager;
/** @var IRequest */
private $request;
public function __construct(ContainerInterface $container)
{
$this->logger = $container->get(LoggerInterface::class);
$this->repository = $container->get('GroupRepository');
$this->groupManager = $container->get(IGroupManager::class);
$this->request = $container->get(IRequest::class);
}
public function getAll(string $filter = ''): SCIMListResponse
{
$this->logger->info("Reading all groups");
$baseUrl = $this->request->getServerProtocol() . "://"
. $this->request->getServerHost() . Util::SCIM_APP_URL_PATH;
$groups = $this->repository->getAll($filter);
$scimGroups = [];
if (!empty($groups)) {
foreach ($groups as $group) {
$scimGroups[] = $group->toSCIM(false, $baseUrl);
}
}
return new SCIMListResponse($scimGroups);
}
public function getOneById(string $id): SCIMJSONResponse
{
$this->logger->info("Reading group with ID: " . $id);
$baseUrl = $this->request->getServerProtocol() . "://" . $this->request->getServerHost() . Util::SCIM_APP_URL_PATH;
$group = $this->repository->getOneById($id);
if (!isset($group) || empty($group)) {
$this->logger->error("Group with ID " . $id . " not found");
return new SCIMErrorResponse(['message' => 'Group not found'], 404);
}
return new SCIMJSONResponse($group->toSCIM(false, $baseUrl));
}
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)) {
$this->logger->error("Group to be created already exists");
return new SCIMErrorResponse(['message' => 'Group exists'], 409);
}
try {
$this->logger->info("Creating group with displayName: " . $displayName);
$baseUrl = $this->request->getServerProtocol() . "://" . $this->request->getServerHost() . Util::SCIM_APP_URL_PATH;
$data = [
'displayName' => $displayName,
'members' => $members
];
$createdGroup = $this->repository->create($data);
if (isset($createdGroup) && !empty($createdGroup)) {
return new SCIMJSONResponse($createdGroup->toSCIM(false, $baseUrl), 201);
} else {
$this->logger->error("Creating group failed");
return new SCIMErrorResponse(['message' => 'Creating group failed'], 400);
}
} catch (Exception $e) {
$this->logger->warning('Failed createGroup attempt with SCIMException exception.', ['app' => 'SCIMServiceProvider']);
throw $e;
}
}
public function update(string $id, string $displayName = '', array $members = []): SCIMJSONResponse
{
$this->logger->info("Updating group with ID: " . $id);
$baseUrl = $this->request->getServerProtocol() . "://" . $this->request->getServerHost() . Util::SCIM_APP_URL_PATH;
$group = $this->repository->getOneById($id);
if (!isset($group) || empty($group)) {
$this->logger->error("Group with ID " . $id . " not found for update");
return new SCIMErrorResponse(['message' => 'Group not found'], 404);
}
$data = [
'displayName' => $displayName,
'members' => $members
];
$updatedGroup = $this->repository->update($id, $data);
if (isset($updatedGroup) && !empty($updatedGroup)) {
return new SCIMJSONResponse($updatedGroup->toSCIM(false, $baseUrl));
} else {
$this->logger->error("Updating group with ID " . $id . " failed");
return new SCIMErrorResponse(['message' => 'Updating group failed'], 400);
}
}
public function destroy(string $id): Response
{
$this->logger->info("Deleting group with ID: " . $id);
if ($id === 'admin') {
// Cannot delete admin group
$this->logger->error("Deleting admin group is not allowed");
return new SCIMErrorResponse(['message' => 'Can\'t delete admin group'], 403);
}
$deleteRes = $this->repository->delete($id);
if ($deleteRes) {
$response = new Response();
$response->setStatus(204);
return $response;
} else {
$this->logger->error("Deletion of group with ID " . $id . " failed");
return new SCIMErrorResponse(['message' => 'Couldn\'t delete group'], 503);
}
}
}
<?php
declare(strict_types=1);
namespace OCA\SCIMServiceProvider\Service;
use Exception;
use OCA\SCIMServiceProvider\Responses\SCIMErrorResponse;
use OCA\SCIMServiceProvider\Responses\SCIMJSONResponse;
use OCA\SCIMServiceProvider\Responses\SCIMListResponse;
use OCA\SCIMServiceProvider\Util\Util;
use OCP\AppFramework\Http\Response;
use OCP\IRequest;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class UserService
{
/** @var LoggerInterface */
private $logger;
/** @var \OCA\SCIMServiceProvider\Repositories\Users\NextcloudUserRepository */
private $repository;
/** @var IRequest */
private $request;
public function __construct(ContainerInterface $container)
{
$this->logger = $container->get(LoggerInterface::class);
$this->repository = $container->get('UserRepository');
$this->request = $container->get(IRequest::class);
}
public function getAll(string $filter = ''): SCIMListResponse
{
$this->logger->info("Reading all users");
$baseUrl = $this->request->getServerProtocol() . "://"
. $this->request->getServerHost() . Util::SCIM_APP_URL_PATH;
$users = $this->repository->getAll($filter);
$scimUsers = [];
if (!empty($users)) {
foreach ($users as $user) {
$scimUsers[] = $user->toSCIM(false, $baseUrl);
}
}
return new SCIMListResponse($scimUsers);
}
public function getOneById(string $id): SCIMJSONResponse
{
$this->logger->info("Reading user with ID: " . $id);
$baseUrl = $this->request->getServerProtocol() . "://" . $this->request->getServerHost() . Util::SCIM_APP_URL_PATH;
$user = $this->repository->getOneById($id);
if (!isset($user) || empty($user)) {
$this->logger->error("User with ID " . $id . " not found");
return new SCIMErrorResponse(['message' => 'User not found'], 404);
}
return new SCIMJSONResponse($user->toSCIM(false, $baseUrl));
}
public function create(
bool $active = true,
string $displayName = '',
array $emails = [],
string $externalId = '',
string $userName = ''
): SCIMJSONResponse
{
try {
$this->logger->info("Creating user with userName: " . $userName);
$baseUrl = $this->request->getServerProtocol() . "://" . $this->request->getServerHost() . Util::SCIM_APP_URL_PATH;
$data = [
'active' => $active,
'displayName' => $displayName,
'emails' => $emails,
'externalId' => $externalId,
'userName' => $userName
];
$createdUser = $this->repository->create($data);
if (isset($createdUser) && !empty($createdUser)) {
return new SCIMJSONResponse($createdUser->toSCIM(false, $baseUrl), 201);
} else {
$this->logger->error("Creating user failed");
return new SCIMErrorResponse(['message' => 'Creating user failed'], 400);
}
} catch (Exception $e) {
$this->logger->warning('Failed createUser attempt with SCIMException exeption.', ['app' => 'SCIMServiceProvider']);
throw $e;
}
}
public function update(
string $id,
bool $active,
string $displayName = '',
array $emails = []
): SCIMJSONResponse
{
$this->logger->info("Updating user with ID: " . $id);
$baseUrl = $this->request->getServerProtocol() . "://" . $this->request->getServerHost() . Util::SCIM_APP_URL_PATH;
$user = $this->repository->getOneById($id);
if (!isset($user) || empty($user)) {
$this->logger->error("User with ID " . $id . " not found for update");
return new SCIMErrorResponse(['message' => 'User not found'], 404);
}
$data = [
'active' => $active,
'displayName' => $displayName,
'emails' => $emails
];
$updatedUser = $this->repository->update($id, $data);
if (isset($updatedUser) && !empty($updatedUser)) {
return new SCIMJSONResponse($updatedUser->toSCIM(false, $baseUrl));
} else {
$this->logger->error("Updating user with ID " . $id . " failed");
return new SCIMErrorResponse(['message' => 'Updating user failed'], 400);
}
}
public function destroy(string $id): Response
{
$this->logger->info("Deleting user with ID: " . $id);
$deleteRes = $this->repository->delete($id);
if ($deleteRes) {
$response = new Response();
$response->setStatus(204);
return $response;
} else {
$this->logger->error("Deletion of user with ID " . $id . " failed");
return new SCIMErrorResponse(['message' => 'Couldn\'t delete user'], 503);
}
}
}
<?php
namespace OCA\SCIMServiceProvider\Util\Authentication;
use Exception;
use Opf\ScimServerPhp\Firebase\JWT\JWT;
use Opf\ScimServerPhp\Firebase\JWT\Key;
use OCA\SCIMServiceProvider\Util\Util;
use OCP\IUserManager;
use Opf\Util\Authentication\AuthenticatorInterface;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
class BearerAuthenticator implements AuthenticatorInterface
{
/** @var \Psr\Log\LoggerInterface */
private LoggerInterface $logger;
/** @var \OCP\IUserManager */
private IUserManager $userManager;
public function __construct(ContainerInterface $container)
{
$this->logger = $container->get(LoggerInterface::class);
$this->userManager = $container->get(IUserManager::class);
}
public function authenticate(string $credentials, array $authorizationInfo): bool
{
$jwtPayload = [];
$jwtSecret = Util::getConfigFile()['jwt']['secret'];
try {
$jwtPayload = (array) JWT::decode($credentials, new Key($jwtSecret, 'HS256'));
} catch (Exception $e) {
$this->logger->error($e->getMessage());
return false;
}
// If the 'user' claim is missing from the JWT, then auth is considered to have failed
if (!isset($jwtPayload['user']) || empty($jwtPayload['user'])) {
$this->logger->error("No \"user\" claim found in JWT");
return false;
}
$username = $jwtPayload['user'];
// If we managed to find a user with that username, then auth succeeded
$user = $this->userManager->get($username);
if ($user !== null) {
return true;
}
$this->logger->error("User with this username doesn't exist");
return false;
}
}
<?php
namespace OCA\SCIMServiceProvider\Util;
class Util
{
public const SCIM_APP_URL_PATH = "index.php/apps/scimserviceprovider";
public static function getConfigFile()
{
$configFilePath = dirname(__DIR__) . '/Config/config.php';
$config = require($configFilePath);
return $config;
}
}
{
"info": {
"_postman_id": "65bcae79-ee78-4eb8-92cc-f23f21913bb9",
"name": "SCIM Nextcloud App Collection",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Groups",
"item": [
{
"name": "Create a single group",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 201\", () => {",
" pm.response.to.have.status(201);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains group with displayName \\\"createdtestgroup\\\"\", () => {",
" pm.expect(pm.response.json().displayName).to.eql(\"createdtestgroup\");",
"});",
"",
"pm.test(\"Response body contains a valid non-null group ID (the ID of the group which was created)\", () => {",
" pm.expect(pm.response.json().id).to.not.be.null;",
"});",
"",
"pm.collectionVariables.set(\"testGroupId\", pm.response.json().id);"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"type": "default"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"displayName\": \"createdtestgroup\",\n \"members\": []\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/Groups",
"host": [
"{{url}}"
],
"path": [
"Groups"
]
}
},
"response": []
},
{
"name": "Read a single group",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains the group ID of the group we want to read\", () => {",
" pm.expect(pm.response.json().id).to.eql(pm.collectionVariables.get('testGroupId'));",
"});",
"",
"pm.test(\"Response body contains group with displayName \\\"createdtestgroup\\\"\", () => {",
" pm.expect(pm.response.json().displayName).to.eql(\"createdtestgroup\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{url}}/Groups/{{testGroupId}}",
"host": [
"{{url}}"
],
"path": [
"Groups",
"{{testGroupId}}"
]
}
},
"response": []
},
{
"name": "Read all groups",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json().Resources).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains group with displayName \\\"createdtestgroup\\\"\", () => {",
" var resources = pm.response.json().Resources.map(x => x.displayName);",
" pm.expect(resources).to.contain(\"createdtestgroup\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{url}}/Groups",
"host": [
"{{url}}"
],
"path": [
"Groups"
]
}
},
"response": []
},
{
"name": "Update a single group",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains the group ID of the group we want to read\", () => {",
" pm.expect(pm.response.json().id).to.eql(pm.collectionVariables.get('testGroupId'));",
"});",
"",
"pm.test(\"Response body contains group with displayName \\\"updatedtestgroup\\\"\", () => {",
" pm.expect(pm.response.json().displayName).to.eql(\"updatedtestgroup\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"displayName\": \"updatedtestgroup\",\n \"members\": []\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/Groups/{{testGroupId}}",
"host": [
"{{url}}"
],
"path": [
"Groups",
"{{testGroupId}}"
]
}
},
"response": []
},
{
"name": "Delete a single group",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 204\", () => {",
" pm.response.to.have.status(204);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{url}}/Groups/{{testGroupId}}",
"host": [
"{{url}}"
],
"path": [
"Groups",
"{{testGroupId}}"
]
}
},
"response": []
}
],
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
]
},
{
"name": "ResourceTypes",
"item": [
{
"name": "Read all ResourceTypes",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json().Resources).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains exactly one entry\", () => {",
" pm.expect(pm.response.json().Resources.length).to.eql(2);",
"});",
"",
"pm.test(\"Response body contains ResourceType with id \\\"User\\\"\", () => {",
" pm.expect(pm.response.json().Resources[0].id).to.eql(\"User\");",
"});",
"",
"pm.test(\"Response body contains ResourceType with id \\\"Group\\\"\", () => {",
" pm.expect(pm.response.json().Resources[1].id).to.eql(\"Group\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer: {{jwt_token}}",
"type": "default"
}
],
"url": {
"raw": "{{url}}/ResourceTypes",
"host": [
"{{url}}"
],
"path": [
"ResourceTypes"
]
}
},
"response": []
}
]
},
{
"name": "Schemas",
"item": [
{
"name": "Read all Schemas",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json().Resources).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains exactly four entries\", () => {",
" pm.expect(pm.response.json().Resources.length).to.eql(4);",
"});",
"",
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:Group\\\"\", () => {",
" pm.expect(pm.response.json().Resources[0].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:Group\");",
"});",
"",
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:ResourceType\\\"\", () => {",
" pm.expect(pm.response.json().Resources[1].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:ResourceType\");",
"});",
"",
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:User\\\"\", () => {",
" pm.expect(pm.response.json().Resources[2].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:User\");",
"});",
"",
"pm.test(\"Response body contains Schema with id \\\"urn:ietf:params:scim:schemas:core:2.0:Schema\\\"\", () => {",
" pm.expect(pm.response.json().Resources[3].id).to.eql(\"urn:ietf:params:scim:schemas:core:2.0:Schema\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer: {{jwt_token}}",
"type": "default"
}
],
"url": {
"raw": "{{url}}/Schemas",
"host": [
"{{url}}"
],
"path": [
"Schemas"
]
}
},
"response": []
}
]
},
{
"name": "ServiceProviderConfigs",
"item": [
{
"name": "Read all ServiceProviderConfigs",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains a ServiceProviderConfig with a correct schema\", () => {",
" pm.expect(pm.response.json().schemas).to.include(\"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer: {{jwt_token}}",
"type": "default"
}
],
"url": {
"raw": "{{url}}/ServiceProviderConfig",
"host": [
"{{url}}"
],
"path": [
"ServiceProviderConfig"
]
}
},
"response": []
}
]
},
{
"name": "Users",
"item": [
{
"name": "Create a single user",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 201\", () => {",
" pm.response.to.have.status(201);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains user with userName \\\"createdtestuser\\\"\", () => {",
" pm.expect(pm.response.json().userName).to.eql(\"createdtestuser\");",
"});",
"",
"pm.test(\"Response body contains a valid non-null user ID (the ID of the user which was created)\", () => {",
" pm.expect(pm.response.json().id).to.not.be.null;",
"});",
"",
"pm.collectionVariables.set(\"testUserId\", pm.response.json().id);"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"type": "default"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"userName\": \"createdtestuser\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/Users",
"host": [
"{{url}}"
],
"path": [
"Users"
]
}
},
"response": []
},
{
"name": "Read a single user",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains the user ID of the user we want to read\", () => {",
" pm.expect(pm.response.json().id).to.eql(pm.collectionVariables.get('testUserId'));",
"});",
"",
"pm.test(\"Response body contains user with userName \\\"createdtestuser\\\"\", () => {",
" pm.expect(pm.response.json().userName).to.eql(\"createdtestuser\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{url}}/Users/{{testUserId}}",
"host": [
"{{url}}"
],
"path": [
"Users",
"{{testUserId}}"
]
}
},
"response": []
},
{
"name": "Read all users",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json().Resources).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains user with userName \\\"createdtestuser\\\"\", () => {",
" var resources = pm.response.json().Resources.map(x => x.userName);",
" pm.expect(resources).to.contain(\"createdtestuser\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{url}}/Users",
"host": [
"{{url}}"
],
"path": [
"Users"
]
}
},
"response": []
},
{
"name": "Update a single user",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains the user ID of the user we want to read\", () => {",
" pm.expect(pm.response.json().id).to.eql(pm.collectionVariables.get('testUserId'));",
"});",
"",
"pm.test(\"Response body contains user with displayName \\\"updatedtestuser\\\"\", () => {",
" pm.expect(pm.response.json().displayName).to.eql(\"updatedtestuser\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"type": "default"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"displayName\": \"updatedtestuser\",\n \"active\": false\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/Users/{{testUserId}}",
"host": [
"{{url}}"
],
"path": [
"Users",
"{{testUserId}}"
]
}
},
"response": []
},
{
"name": "Delete a single user",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 204\", () => {",
" pm.response.to.have.status(204);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "DELETE",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"type": "default"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"userName\": \"updatedtestuser\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/Users/{{testUserId}}",
"host": [
"{{url}}"
],
"path": [
"Users",
"{{testUserId}}"
]
}
},
"response": []
}
],
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
]
}
],
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "admin",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"key": "testUserId",
"value": ""
},
{
"key": "testGroupId",
"value": ""
},
{
"key": "url",
"value": "http://localhost:8888/index.php/apps/scimserviceprovider",
"type": "default"
}
]
}
\ No newline at end of file
{
"info": {
"_postman_id": "606af599-3dec-46c8-9464-f52a7fd8f5b7",
"name": "SCIM Nextcloud App Collection (Bearer Token)",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Users",
"item": [
{
"name": "Create a single user",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 201\", () => {",
" pm.response.to.have.status(201);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains user with userName \\\"createdtestuser\\\"\", () => {",
" pm.expect(pm.response.json().userName).to.eql(\"createdtestuser\");",
"});",
"",
"pm.test(\"Response body contains a valid non-null user ID (the ID of the user which was created)\", () => {",
" pm.expect(pm.response.json().id).to.not.be.null;",
"});",
"",
"pm.collectionVariables.set(\"testUserId\", pm.response.json().id);"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"type": "default"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"userName\": \"createdtestuser\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/bearer/Users",
"host": [
"{{url}}"
],
"path": [
"bearer",
"Users"
]
}
},
"response": []
},
{
"name": "Read a single user",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains the user ID of the user we want to read\", () => {",
" pm.expect(pm.response.json().id).to.eql(pm.collectionVariables.get('testUserId'));",
"});",
"",
"pm.test(\"Response body contains user with userName \\\"createdtestuser\\\"\", () => {",
" pm.expect(pm.response.json().userName).to.eql(\"createdtestuser\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{url}}/bearer/Users/{{testUserId}}",
"host": [
"{{url}}"
],
"path": [
"bearer",
"Users",
"{{testUserId}}"
]
}
},
"response": []
},
{
"name": "Read all users",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json().Resources).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains user with userName \\\"createdtestuser\\\"\", () => {",
" var resources = pm.response.json().Resources.map(x => x.userName);",
" pm.expect(resources).to.contain(\"createdtestuser\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{url}}/bearer/Users",
"host": [
"{{url}}"
],
"path": [
"bearer",
"Users"
]
}
},
"response": []
},
{
"name": "Update a single user",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains the user ID of the user we want to read\", () => {",
" pm.expect(pm.response.json().id).to.eql(pm.collectionVariables.get('testUserId'));",
"});",
"",
"pm.test(\"Response body contains user with displayName \\\"updatedtestuser\\\"\", () => {",
" pm.expect(pm.response.json().displayName).to.eql(\"updatedtestuser\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "PUT",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"type": "default"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"displayName\": \"updatedtestuser\",\n \"active\": false\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/bearer/Users/{{testUserId}}",
"host": [
"{{url}}"
],
"path": [
"bearer",
"Users",
"{{testUserId}}"
]
}
},
"response": []
},
{
"name": "Delete a single user",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 204\", () => {",
" pm.response.to.have.status(204);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "DELETE",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"type": "default"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"userName\": \"updatedtestuser\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/bearer/Users/{{testUserId}}",
"host": [
"{{url}}"
],
"path": [
"bearer",
"Users",
"{{testUserId}}"
]
}
},
"response": []
}
],
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
]
},
{
"name": "Groups",
"item": [
{
"name": "Create a single group",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 201\", () => {",
" pm.response.to.have.status(201);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains group with displayName \\\"createdtestgroup\\\"\", () => {",
" pm.expect(pm.response.json().displayName).to.eql(\"createdtestgroup\");",
"});",
"",
"pm.test(\"Response body contains a valid non-null group ID (the ID of the group which was created)\", () => {",
" pm.expect(pm.response.json().id).to.not.be.null;",
"});",
"",
"pm.collectionVariables.set(\"testGroupId\", pm.response.json().id);"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json",
"type": "default"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"displayName\": \"createdtestgroup\",\n \"members\": []\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/bearer/Groups",
"host": [
"{{url}}"
],
"path": [
"bearer",
"Groups"
]
}
},
"response": []
},
{
"name": "Read a single group",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains the group ID of the group we want to read\", () => {",
" pm.expect(pm.response.json().id).to.eql(pm.collectionVariables.get('testGroupId'));",
"});",
"",
"pm.test(\"Response body contains group with displayName \\\"createdtestgroup\\\"\", () => {",
" pm.expect(pm.response.json().displayName).to.eql(\"createdtestgroup\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{url}}/bearer/Groups/{{testGroupId}}",
"host": [
"{{url}}"
],
"path": [
"bearer",
"Groups",
"{{testGroupId}}"
]
}
},
"response": []
},
{
"name": "Read all groups",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json().Resources).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains group with displayName \\\"createdtestgroup\\\"\", () => {",
" var resources = pm.response.json().Resources.map(x => x.displayName);",
" pm.expect(resources).to.contain(\"createdtestgroup\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{url}}/bearer/Groups",
"host": [
"{{url}}"
],
"path": [
"bearer",
"Groups"
]
}
},
"response": []
},
{
"name": "Update a single group",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 200\", () => {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"Response body is not empty\", () => {",
" pm.expect(pm.response.json()).to.not.be.empty;",
"});",
"",
"pm.test(\"Response body contains the group ID of the group we want to read\", () => {",
" pm.expect(pm.response.json().id).to.eql(pm.collectionVariables.get('testGroupId'));",
"});",
"",
"pm.test(\"Response body contains group with displayName \\\"updatedtestgroup\\\"\", () => {",
" pm.expect(pm.response.json().displayName).to.eql(\"updatedtestgroup\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"displayName\": \"updatedtestgroup\",\n \"members\": []\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/bearer/Groups/{{testGroupId}}",
"host": [
"{{url}}"
],
"path": [
"bearer",
"Groups",
"{{testGroupId}}"
]
}
},
"response": []
},
{
"name": "Delete a single group",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response status code is 204\", () => {",
" pm.response.to.have.status(204);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{url}}/bearer/Groups/{{testGroupId}}",
"host": [
"{{url}}"
],
"path": [
"bearer",
"Groups",
"{{testGroupId}}"
]
}
},
"response": []
}
],
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
]
}
],
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.Oetm7xvhkYbiItRiqNx-z7LZ6ZkmDe1z_95igbPUSjA",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"key": "testUserId",
"value": null
},
{
"key": "testGroupId",
"value": null
},
{
"key": "url",
"value": "http://localhost:8888/index.php/apps/scimserviceprovider",
"type": "default"
}
]
}
\ No newline at end of file