Merge pull request #8913 from appwrite/feat-list-memberships-as-client

feat: list memberships as client
This commit is contained in:
Christy Jacob 2024-11-07 20:28:46 +01:00 committed by GitHub
commit d3bb5d963b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 3765 additions and 2901 deletions

View file

@ -4945,7 +4945,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 305,
"weight": 306,
"cookies": false,
"type": "",
"deprecated": false,
@ -5033,7 +5033,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 304,
"weight": 305,
"cookies": false,
"type": "",
"deprecated": false,
@ -5150,7 +5150,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 306,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@ -5226,7 +5226,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 330,
"weight": 331,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5280,7 +5280,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 329,
"weight": 330,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5766,7 +5766,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 381,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@ -5851,7 +5851,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 385,
"weight": 386,
"cookies": false,
"type": "",
"deprecated": false,
@ -5928,7 +5928,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 207,
"weight": 208,
"cookies": false,
"type": "",
"deprecated": false,
@ -6016,7 +6016,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 206,
"weight": 207,
"cookies": false,
"type": "upload",
"deprecated": false,
@ -6116,7 +6116,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 208,
"weight": 209,
"cookies": false,
"type": "",
"deprecated": false,
@ -6190,7 +6190,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 213,
"weight": 214,
"cookies": false,
"type": "",
"deprecated": false,
@ -6281,7 +6281,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 214,
"weight": 215,
"cookies": false,
"type": "",
"deprecated": false,
@ -6350,7 +6350,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 210,
"weight": 211,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6419,7 +6419,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 209,
"weight": 210,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6637,7 +6637,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 211,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6713,7 +6713,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 218,
"weight": 219,
"cookies": false,
"type": "",
"deprecated": false,
@ -6791,7 +6791,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 217,
"weight": 218,
"cookies": false,
"type": "",
"deprecated": false,
@ -6878,7 +6878,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 219,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@ -6942,7 +6942,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 221,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@ -7018,7 +7018,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 223,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@ -7069,7 +7069,7 @@
"tags": [
"teams"
],
"description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.",
"description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.",
"responses": {
"200": {
"description": "Memberships List",
@ -7084,7 +7084,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 225,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@ -7172,7 +7172,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 224,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
@ -7270,7 +7270,7 @@
"tags": [
"teams"
],
"description": "Get a team member by the membership unique id. All team members have read access for this resource.",
"description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.",
"responses": {
"200": {
"description": "Membership",
@ -7285,7 +7285,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 226,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@ -7359,7 +7359,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 227,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@ -7448,7 +7448,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 229,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@ -7524,7 +7524,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 228,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@ -7624,7 +7624,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 220,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@ -7687,7 +7687,7 @@
},
"x-appwrite": {
"method": "updatePrefs",
"weight": 222,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@ -9266,12 +9266,12 @@
},
"userName": {
"type": "string",
"description": "User name.",
"description": "User name. Hide this attribute by toggling membership privacy in the Console.",
"x-example": "John Doe"
},
"userEmail": {
"type": "string",
"description": "User email address.",
"description": "User email address. Hide this attribute by toggling membership privacy in the Console.",
"x-example": "john@appwrite.io"
},
"teamId": {
@ -9301,7 +9301,7 @@
},
"mfa": {
"type": "boolean",
"description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.",
"description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.",
"x-example": false
},
"roles": {
@ -9826,7 +9826,7 @@
"name": {
"type": "string",
"description": "Target Name.",
"x-example": "Aegon apple token"
"x-example": "Apple iPhone 12"
},
"userId": {
"type": "string",
@ -9848,6 +9848,11 @@
"type": "string",
"description": "The target identifier.",
"x-example": "token"
},
"expired": {
"type": "boolean",
"description": "Is the target expired.",
"x-example": false
}
},
"required": [
@ -9857,7 +9862,8 @@
"name",
"userId",
"providerType",
"identifier"
"identifier",
"expired"
]
}
},

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5101,7 +5101,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 305,
"weight": 306,
"cookies": false,
"type": "",
"deprecated": false,
@ -5186,7 +5186,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 304,
"weight": 305,
"cookies": false,
"type": "",
"deprecated": false,
@ -5307,7 +5307,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 306,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@ -5381,7 +5381,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 330,
"weight": 331,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5457,7 +5457,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 329,
"weight": 330,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5981,7 +5981,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 381,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@ -6070,7 +6070,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 385,
"weight": 386,
"cookies": false,
"type": "",
"deprecated": false,
@ -6145,7 +6145,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 207,
"weight": 208,
"cookies": false,
"type": "",
"deprecated": false,
@ -6230,7 +6230,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 206,
"weight": 207,
"cookies": false,
"type": "upload",
"deprecated": false,
@ -6324,7 +6324,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 208,
"weight": 209,
"cookies": false,
"type": "",
"deprecated": false,
@ -6396,7 +6396,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 213,
"weight": 214,
"cookies": false,
"type": "",
"deprecated": false,
@ -6487,7 +6487,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 214,
"weight": 215,
"cookies": false,
"type": "",
"deprecated": false,
@ -6561,7 +6561,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 210,
"weight": 211,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6635,7 +6635,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 209,
"weight": 210,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6836,7 +6836,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 211,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6910,7 +6910,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 218,
"weight": 219,
"cookies": false,
"type": "",
"deprecated": false,
@ -6987,7 +6987,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 217,
"weight": 218,
"cookies": false,
"type": "",
"deprecated": false,
@ -7081,7 +7081,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 219,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@ -7145,7 +7145,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 221,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@ -7222,7 +7222,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 223,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@ -7277,7 +7277,7 @@
"tags": [
"teams"
],
"description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.",
"description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.",
"responses": {
"200": {
"description": "Memberships List",
@ -7288,7 +7288,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 225,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@ -7373,7 +7373,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 224,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
@ -7479,7 +7479,7 @@
"tags": [
"teams"
],
"description": "Get a team member by the membership unique id. All team members have read access for this resource.",
"description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.",
"responses": {
"200": {
"description": "Membership",
@ -7490,7 +7490,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 226,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@ -7562,7 +7562,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 227,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@ -7650,7 +7650,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 229,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@ -7724,7 +7724,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 228,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@ -7822,7 +7822,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 220,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@ -7885,7 +7885,7 @@
},
"x-appwrite": {
"method": "updatePrefs",
"weight": 222,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@ -9445,12 +9445,12 @@
},
"userName": {
"type": "string",
"description": "User name.",
"description": "User name. Hide this attribute by toggling membership privacy in the Console.",
"x-example": "John Doe"
},
"userEmail": {
"type": "string",
"description": "User email address.",
"description": "User email address. Hide this attribute by toggling membership privacy in the Console.",
"x-example": "john@appwrite.io"
},
"teamId": {
@ -9480,7 +9480,7 @@
},
"mfa": {
"type": "boolean",
"description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.",
"description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.",
"x-example": false
},
"roles": {
@ -10008,7 +10008,7 @@
"name": {
"type": "string",
"description": "Target Name.",
"x-example": "Aegon apple token"
"x-example": "Apple iPhone 12"
},
"userId": {
"type": "string",
@ -10030,6 +10030,11 @@
"type": "string",
"description": "The target identifier.",
"x-example": "token"
},
"expired": {
"type": "boolean",
"description": "Is the target expired.",
"x-example": false
}
},
"required": [
@ -10039,7 +10044,8 @@
"name",
"userId",
"providerType",
"identifier"
"identifier",
"expired"
]
}
},

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -111,6 +111,9 @@ App::post('/v1/projects')
'personalDataCheck' => false,
'mockNumbers' => [],
'sessionAlerts' => false,
'membershipsUserName' => false,
'membershipsUserEmail' => false,
'membershipsMfa' => false,
];
foreach ($auth as $method) {
@ -648,6 +651,41 @@ App::patch('/v1/projects/:projectId/auth/session-alerts')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/memberships-privacy')
->desc('Update project memberships privacy attributes')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateMembershipsPrivacy')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('userName', true, new Boolean(true), 'Set to true to show userName to members of a team.')
->param('userEmail', true, new Boolean(true), 'Set to true to show email to members of a team.')
->param('mfa', true, new Boolean(true), 'Set to true to show mfa to members of a team.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $userName, bool $userEmail, bool $mfa, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
$auths['membershipsUserName'] = $userName;
$auths['membershipsUserEmail'] = $userEmail;
$auths['membershipsMfa'] = $mfa;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/limit')
->desc('Update project users limit')
->groups(['api', 'projects'])

View file

@ -727,9 +727,9 @@ App::get('/v1/teams/:teamId/memberships')
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (string $teamId, array $queries, string $search, Response $response, Database $dbForProject) {
->action(function (string $teamId, array $queries, string $search, Response $response, Document $project, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@ -790,27 +790,48 @@ App::get('/v1/teams/:teamId/memberships')
$memberships = array_filter($memberships, fn (Document $membership) => !empty($membership->getAttribute('userId')));
$memberships = array_map(function ($membership) use ($dbForProject, $team) {
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
$membershipsPrivacy = [
'userName' => $project->getAttribute('auths', [])['membershipsUserName'] ?? true,
'userEmail' => $project->getAttribute('auths', [])['membershipsUserEmail'] ?? true,
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
];
$mfa = $user->getAttribute('mfa', false);
if ($mfa) {
$totp = TOTP::getAuthenticatorFromUser($user);
$totpEnabled = $totp && $totp->getAttribute('verified', false);
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
$mfa = false;
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
return $privacy || $isPrivilegedUser || $isAppUser;
}, $membershipsPrivacy);
$memberships = array_map(function ($membership) use ($dbForProject, $team, $membershipsPrivacy) {
if ($membershipsPrivacy['mfa']) {
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
$mfa = $user->getAttribute('mfa', false);
if ($mfa) {
$totp = TOTP::getAuthenticatorFromUser($user);
$totpEnabled = $totp && $totp->getAttribute('verified', false);
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
$mfa = false;
}
}
$membership->setAttribute('mfa', $mfa);
}
$membership
->setAttribute('mfa', $mfa)
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email'))
;
if ($membershipsPrivacy['userName']) {
$membership->setAttribute('userName', $user->getAttribute('name'));
}
if ($membershipsPrivacy['userEmail']) {
$membership->setAttribute('userEmail', $user->getAttribute('email'));
}
$membership->setAttribute('teamName', $team->getAttribute('name'));
return $membership;
}, $memberships);
@ -837,8 +858,9 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject) {
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
@ -852,27 +874,48 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
}
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
$membershipsPrivacy = [
'userName' => $project->getAttribute('auths', [])['membershipsUserName'] ?? true,
'userEmail' => $project->getAttribute('auths', [])['membershipsUserEmail'] ?? true,
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
];
$mfa = $user->getAttribute('mfa', false);
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if ($mfa) {
$totp = TOTP::getAuthenticatorFromUser($user);
$totpEnabled = $totp && $totp->getAttribute('verified', false);
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
return $privacy || $isPrivilegedUser || $isAppUser;
}, $membershipsPrivacy);
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
$mfa = false;
if ($membershipsPrivacy['mfa']) {
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
$mfa = $user->getAttribute('mfa', false);
if ($mfa) {
$totp = TOTP::getAuthenticatorFromUser($user);
$totpEnabled = $totp && $totp->getAttribute('verified', false);
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
$mfa = false;
}
}
$membership->setAttribute('mfa', $mfa);
}
$membership
->setAttribute('mfa', $mfa)
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email'))
;
if ($membershipsPrivacy['userName']) {
$membership->setAttribute('userName', $user->getAttribute('name'));
}
if ($membershipsPrivacy['userEmail']) {
$membership->setAttribute('userEmail', $user->getAttribute('email'));
}
$membership->setAttribute('teamName', $team->getAttribute('name'));
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
});

62
composer.lock generated
View file

@ -2608,16 +2608,16 @@
},
{
"name": "utopia-php/storage",
"version": "0.18.5",
"version": "0.18.6",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
"reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919"
"reference": "893ccf06e183f8ece2aed8dbf14d64d6ba036071"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/7d355c5e3ccc8ecebc0266f8ddd30088a43be919",
"reference": "7d355c5e3ccc8ecebc0266f8ddd30088a43be919",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/893ccf06e183f8ece2aed8dbf14d64d6ba036071",
"reference": "893ccf06e183f8ece2aed8dbf14d64d6ba036071",
"shasum": ""
},
"require": {
@ -2657,9 +2657,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.18.5"
"source": "https://github.com/utopia-php/storage/tree/0.18.6"
},
"time": "2024-09-04T08:57:27+00:00"
"time": "2024-11-06T09:58:50+00:00"
},
{
"name": "utopia-php/swoole",
@ -4004,16 +4004,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.5.0",
"version": "5.5.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "54e10d44fc1a84e2598d26f70d4f6f1f233e228a"
"reference": "0c70d2c566e899666f367ab7b80986beb3581e6f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/54e10d44fc1a84e2598d26f70d4f6f1f233e228a",
"reference": "54e10d44fc1a84e2598d26f70d4f6f1f233e228a",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/0c70d2c566e899666f367ab7b80986beb3581e6f",
"reference": "0c70d2c566e899666f367ab7b80986beb3581e6f",
"shasum": ""
},
"require": {
@ -4062,9 +4062,9 @@
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.5.0"
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.5.1"
},
"time": "2024-11-04T21:26:31+00:00"
"time": "2024-11-06T11:58:54+00:00"
},
{
"name": "phpdocumentor/type-resolver",
@ -5875,16 +5875,16 @@
},
{
"name": "symfony/console",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57"
"reference": "3284aafcac338b6e86fd955ee4d794cbe434151a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/bb5192af6edc797cbab5c8e8ecfea2fe5f421e57",
"reference": "bb5192af6edc797cbab5c8e8ecfea2fe5f421e57",
"url": "https://api.github.com/repos/symfony/console/zipball/3284aafcac338b6e86fd955ee4d794cbe434151a",
"reference": "3284aafcac338b6e86fd955ee4d794cbe434151a",
"shasum": ""
},
"require": {
@ -5948,7 +5948,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.1.6"
"source": "https://github.com/symfony/console/tree/v7.1.7"
},
"funding": [
{
@ -5964,7 +5964,7 @@
"type": "tidelift"
}
],
"time": "2024-10-09T08:46:59+00:00"
"time": "2024-11-05T15:34:55+00:00"
},
{
"name": "symfony/deprecation-contracts",
@ -6546,16 +6546,16 @@
},
{
"name": "symfony/process",
"version": "v7.1.6",
"version": "v7.1.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e"
"reference": "9b8a40b7289767aa7117e957573c2a535efe6585"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e",
"reference": "6aaa189ddb4ff6b5de8fa3210f2fb42c87b4d12e",
"url": "https://api.github.com/repos/symfony/process/zipball/9b8a40b7289767aa7117e957573c2a535efe6585",
"reference": "9b8a40b7289767aa7117e957573c2a535efe6585",
"shasum": ""
},
"require": {
@ -6587,7 +6587,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.1.6"
"source": "https://github.com/symfony/process/tree/v7.1.7"
},
"funding": [
{
@ -6603,7 +6603,7 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2024-11-06T09:25:12+00:00"
},
{
"name": "symfony/service-contracts",
@ -6876,16 +6876,16 @@
},
{
"name": "twig/twig",
"version": "v3.14.0",
"version": "v3.14.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72"
"reference": "f405356d20fb43603bcadc8b09bfb676cb04a379"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72",
"reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/f405356d20fb43603bcadc8b09bfb676cb04a379",
"reference": "f405356d20fb43603bcadc8b09bfb676cb04a379",
"shasum": ""
},
"require": {
@ -6939,7 +6939,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.14.0"
"source": "https://github.com/twigphp/Twig/tree/v3.14.1"
},
"funding": [
{
@ -6951,7 +6951,7 @@
"type": "tidelift"
}
],
"time": "2024-09-09T17:55:12+00:00"
"time": "2024-11-06T18:17:38+00:00"
},
{
"name": "webmozart/glob",
@ -7005,7 +7005,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {

View file

@ -1 +1 @@
Get a team member by the membership unique id. All team members have read access for this resource.
Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.

View file

@ -1 +1 @@
Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.
Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.

View file

@ -36,13 +36,13 @@ class Membership extends Model
])
->addRule('userName', [
'type' => self::TYPE_STRING,
'description' => 'User name.',
'description' => 'User name. Hide this attribute by toggling membership privacy in the Console.',
'default' => '',
'example' => 'John Doe',
])
->addRule('userEmail', [
'type' => self::TYPE_STRING,
'description' => 'User email address.',
'description' => 'User email address. Hide this attribute by toggling membership privacy in the Console.',
'default' => '',
'example' => 'john@appwrite.io',
])
@ -78,7 +78,7 @@ class Membership extends Model
])
->addRule('mfa', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Multi factor authentication status, true if the user has MFA enabled or false otherwise.',
'description' => 'Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.',
'default' => false,
'example' => false,
])

View file

@ -151,6 +151,24 @@ class Project extends Model
'default' => false,
'example' => true,
])
->addRule('authMembershipsUserName', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether or not to show user names in the teams membership response.',
'default' => false,
'example' => true,
])
->addRule('authMembershipsUserEmail', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether or not to show user emails in the teams membership response.',
'default' => false,
'example' => true,
])
->addRule('authMembershipsMfa', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Whether or not to show user MFA status in the teams membership response.',
'default' => false,
'example' => true,
])
->addRule('oAuthProviders', [
'type' => Response::MODEL_AUTH_PROVIDER,
'description' => 'List of Auth Providers.',
@ -348,6 +366,9 @@ class Project extends Model
$document->setAttribute('authPersonalDataCheck', $authValues['personalDataCheck'] ?? false);
$document->setAttribute('authMockNumbers', $authValues['mockNumbers'] ?? []);
$document->setAttribute('authSessionAlerts', $authValues['sessionAlerts'] ?? false);
$document->setAttribute('authMembershipsUserName', $authValues['membershipsUserName'] ?? true);
$document->setAttribute('authMembershipsUserEmail', $authValues['membershipsUserEmail'] ?? true);
$document->setAttribute('authMembershipsMfa', $authValues['membershipsMfa'] ?? true);
foreach ($auth as $index => $method) {
$key = $method['key'];

View file

@ -30,8 +30,8 @@ trait TeamsBaseClient
$this->assertIsInt($response['body']['total']);
$this->assertNotEmpty($response['body']['memberships'][0]['$id']);
$this->assertFalse($response['body']['memberships'][0]['mfa']);
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
$this->assertArrayHasKey('userName', $response['body']['memberships'][0]);
$this->assertArrayHasKey('userEmail', $response['body']['memberships'][0]);
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
@ -96,8 +96,8 @@ trait TeamsBaseClient
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['total']);
$this->assertNotEmpty($response['body']['memberships'][0]);
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
$this->assertArrayHasKey('userName', $response['body']['memberships'][0]);
$this->assertArrayHasKey('userEmail', $response['body']['memberships'][0]);
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
@ -112,8 +112,8 @@ trait TeamsBaseClient
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['total']);
$this->assertNotEmpty($response['body']['memberships'][0]);
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
$this->assertArrayHasKey('userName', $response['body']['memberships'][0]);
$this->assertArrayHasKey('userEmail', $response['body']['memberships'][0]);
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
@ -157,8 +157,8 @@ trait TeamsBaseClient
$this->assertNotEmpty($response['body']['$id']);
$this->assertFalse($response['body']['mfa']);
$this->assertNotEmpty($response['body']['userId']);
$this->assertNotEmpty($response['body']['userName']);
$this->assertNotEmpty($response['body']['userEmail']);
$this->assertArrayHasKey('userName', $response['body']);
$this->assertArrayHasKey('userEmail', $response['body']);
$this->assertNotEmpty($response['body']['teamId']);
$this->assertNotEmpty($response['body']['teamName']);
$this->assertCount(1, $response['body']['roles']);

View file

@ -29,7 +29,7 @@ trait TeamsBaseServer
* Test for FAILURE
*/
return [];
return $data;
}
/**
@ -60,6 +60,67 @@ trait TeamsBaseServer
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['joined'])); // is null in DB
$this->assertEquals(true, $response['body']['confirm']);
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/memberships-privacy', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => 'console',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
]), [
'userName' => false,
'userEmail' => false,
'mfa' => false,
]);
$this->assertEquals(200, $response['headers']['status-code']);
/**
* Test that sensitive fields are not hidden, as we are on console
*/
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['total']);
$this->assertNotEmpty($response['body']['memberships'][0]['$id']);
// Assert that sensitive fields are present
$this->assertNotEmpty($response['body']['memberships'][0]['userName']);
$this->assertNotEmpty($response['body']['memberships'][0]['userEmail']);
$this->assertArrayHasKey('mfa', $response['body']['memberships'][0]);
/**
* Update project settings to show sensitive fields
*/
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/memberships-privacy', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => 'console',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
]), [
'userName' => true,
'userEmail' => true,
'mfa' => true,
]);
$this->assertEquals(200, $response['headers']['status-code']);
/**
* Test that sensitive fields are shown
*/
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['total']);
$this->assertNotEmpty($response['body']['memberships'][0]['$id']);
// Assert that sensitive fields are present
$this->assertNotEmpty($response['body']['memberships'][0]['userName']);
$this->assertNotEmpty($response['body']['memberships'][0]['userEmail']);
$this->assertArrayHasKey('mfa', $response['body']['memberships'][0]);
/**
* Test for FAILURE
*/

View file

@ -14,6 +14,77 @@ class TeamsCustomClientTest extends Scope
use ProjectCustom;
use SideClient;
/**
* @depends testGetTeamMemberships
*/
public function testGetMembershipPrivacy($data)
{
$teamUid = $data['teamUid'] ?? '';
$projectId = $this->getProject()['$id'];
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/auth/memberships-privacy', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => 'console',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
]), [
'userName' => false,
'userEmail' => false,
'mfa' => false,
]);
$this->assertEquals(200, $response['headers']['status-code']);
/**
* Test that sensitive fields are hidden
*/
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['total']);
$this->assertNotEmpty($response['body']['memberships'][0]['$id']);
// Assert that sensitive fields are not present
$this->assertEmpty($response['body']['memberships'][0]['userName']);
$this->assertEmpty($response['body']['memberships'][0]['userEmail']);
$this->assertFalse($response['body']['memberships'][0]['mfa']);
/**
* Update project settings to show sensitive fields
*/
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/auth/memberships-privacy', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => 'console',
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
]), [
'userName' => true,
'userEmail' => true,
'mfa' => true,
]);
$this->assertEquals(200, $response['headers']['status-code']);
/**
* Test that sensitive fields are shown
*/
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['total']);
$this->assertNotEmpty($response['body']['memberships'][0]['$id']);
// Assert that sensitive fields are present
$this->assertNotEmpty($response['body']['memberships'][0]['userName']);
$this->assertNotEmpty($response['body']['memberships'][0]['userEmail']);
$this->assertArrayHasKey('mfa', $response['body']['memberships'][0]);
}
/**
* @depends testUpdateTeamMembership
*/