1
0
Fork 0
mirror of synced 2024-06-02 19:04:49 +12:00

Updated coding standards using PHP-CS-FIXER

This commit is contained in:
eldadfux 2019-09-06 20:10:41 +03:00
parent 586aa0bbd4
commit 6a5f54615a
14 changed files with 1036 additions and 1175 deletions

View file

@ -20,17 +20,16 @@ $utopia->get('/v1/account')
->label('sdk.method', 'get') ->label('sdk.method', 'get')
->label('sdk.description', 'Get currently logged in user data as JSON object.') ->label('sdk.description', 'Get currently logged in user data as JSON object.')
->action( ->action(
function() use ($response, &$user, $providers) function () use ($response, &$user, $providers) {
{
$oauthKeys = []; $oauthKeys = [];
foreach($providers as $key => $provider) { foreach ($providers as $key => $provider) {
if(!$provider['enabled']) { if (!$provider['enabled']) {
continue; continue;
} }
$oauthKeys[] = 'oauth' . ucfirst($key); $oauthKeys[] = 'oauth'.ucfirst($key);
$oauthKeys[] = 'oauth' . ucfirst($key) . 'AccessToken'; $oauthKeys[] = 'oauth'.ucfirst($key).'AccessToken';
} }
$response->json(array_merge($user->getArrayCopy(array_merge( $response->json(array_merge($user->getArrayCopy(array_merge(
@ -52,17 +51,16 @@ $utopia->get('/v1/account/prefs')
->label('sdk.method', 'getPrefs') ->label('sdk.method', 'getPrefs')
->label('sdk.description', 'Get currently logged in user preferences key-value object.') ->label('sdk.description', 'Get currently logged in user preferences key-value object.')
->action( ->action(
function() use ($response, $user) { function () use ($response, $user) {
$prefs = $user->getAttribute('prefs', '{}'); $prefs = $user->getAttribute('prefs', '{}');
if(empty($prefs)) { if (empty($prefs)) {
$prefs = '[]'; $prefs = '[]';
} }
try { try {
$prefs = json_decode($prefs, true); $prefs = json_decode($prefs, true);
} } catch (\Exception $error) {
catch (\Exception $error) {
throw new Exception('Failed to parse prefs', 500); throw new Exception('Failed to parse prefs', 500);
} }
@ -77,20 +75,20 @@ $utopia->get('/v1/account/sessions')
->label('sdk.method', 'getSessions') ->label('sdk.method', 'getSessions')
->label('sdk.description', 'Get currently logged in user list of active sessions across different devices.') ->label('sdk.description', 'Get currently logged in user list of active sessions across different devices.')
->action( ->action(
function() use ($response, $user) { function () use ($response, $user) {
$tokens = $user->getAttribute('tokens', []); $tokens = $user->getAttribute('tokens', []);
$reader = new Reader(__DIR__ . '/../db/GeoLite2/GeoLite2-Country.mmdb'); $reader = new Reader(__DIR__.'/../db/GeoLite2/GeoLite2-Country.mmdb');
$sessions = []; $sessions = [];
$current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret); $current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
$index = 0; $index = 0;
$countries = Locale::getText('countries'); $countries = Locale::getText('countries');
foreach($tokens as $token) { /* @var $token Document */ foreach ($tokens as $token) { /* @var $token Document */
if(Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) { if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) {
continue; continue;
} }
$userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN'; $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN';
$dd = new DeviceDetector($userAgent); $dd = new DeviceDetector($userAgent);
@ -100,28 +98,27 @@ $utopia->get('/v1/account/sessions')
$dd->parse(); $dd->parse();
$sessions[$index] = [ $sessions[$index] = [
'id' => $token->getUid(), 'id' => $token->getUid(),
'OS' => $dd->getOs(), 'OS' => $dd->getOs(),
'client' => $dd->getClient(), 'client' => $dd->getClient(),
'device' => $dd->getDevice(), 'device' => $dd->getDevice(),
'brand' => $dd->getBrand(), 'brand' => $dd->getBrand(),
'model' => $dd->getModel(), 'model' => $dd->getModel(),
'ip' => $token->getAttribute('ip', ''), 'ip' => $token->getAttribute('ip', ''),
'geo' => [], 'geo' => [],
'current' => ($current == $token->getUid()) ? true : false, 'current' => ($current == $token->getUid()) ? true : false,
]; ];
try { try {
$record = $reader->country($token->getAttribute('ip', '')); $record = $reader->country($token->getAttribute('ip', ''));
$sessions[$index]['geo']['isoCode'] = strtolower($record->country->isoCode); $sessions[$index]['geo']['isoCode'] = strtolower($record->country->isoCode);
$sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} } catch (\Exception $e) {
catch(\Exception $e) {
$sessions[$index]['geo']['isoCode'] = '--'; $sessions[$index]['geo']['isoCode'] = '--';
$sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown'); $sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown');
} }
$index++; ++$index;
} }
$response->json($sessions); $response->json($sessions);
@ -135,12 +132,12 @@ $utopia->get('/v1/account/security')
->label('sdk.method', 'getSecurity') ->label('sdk.method', 'getSecurity')
->label('sdk.description', 'Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.') ->label('sdk.description', 'Get currently logged in user list of latest security activity logs. Each log returns user IP address, location and date and time of log.')
->action( ->action(
function() use ($response, $register, $project, $user) { function () use ($response, $register, $project, $user) {
$ad = new \Audit\Adapter\MySQL($register->get('db')); $ad = new \Audit\Adapter\MySQL($register->get('db'));
$ad->setNamespace('app_' . $project->getUid()); $ad->setNamespace('app_'.$project->getUid());
$au = new \Audit\Audit($ad, $user->getUid(), $user->getAttribute('type'), '', '', ''); $au = new \Audit\Audit($ad, $user->getUid(), $user->getAttribute('type'), '', '', '');
$countries = Locale::getText('countries'); $countries = Locale::getText('countries');
$logs = $au->getLogsByUserAndActions($user->getUid(), $user->getAttribute('type', 0), [ $logs = $au->getLogsByUserAndActions($user->getUid(), $user->getAttribute('type', 0), [
'auth.register', 'auth.register',
@ -159,11 +156,11 @@ $utopia->get('/v1/account/security')
'account.update.password', 'account.update.password',
]); ]);
$reader = new Reader(__DIR__ . '/../db/GeoLite2/GeoLite2-Country.mmdb'); $reader = new Reader(__DIR__.'/../db/GeoLite2/GeoLite2-Country.mmdb');
$output = []; $output = [];
foreach($logs as $i => &$log) { foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN'; $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
$dd = new DeviceDetector($log['userAgent']); $dd = new DeviceDetector($log['userAgent']);
@ -172,15 +169,15 @@ $utopia->get('/v1/account/security')
$dd->parse(); $dd->parse();
$output[$i] = [ $output[$i] = [
'event' => $log['event'], 'event' => $log['event'],
'ip' => $log['ip'], 'ip' => $log['ip'],
'time' => strtotime($log['time']), 'time' => strtotime($log['time']),
'OS' => $dd->getOs(), 'OS' => $dd->getOs(),
'client' => $dd->getClient(), 'client' => $dd->getClient(),
'device' => $dd->getDevice(), 'device' => $dd->getDevice(),
'brand' => $dd->getBrand(), 'brand' => $dd->getBrand(),
'model' => $dd->getModel(), 'model' => $dd->getModel(),
'geo' => [], 'geo' => [],
]; ];
try { try {
@ -188,10 +185,9 @@ $utopia->get('/v1/account/security')
$output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode); $output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode);
$output[$i]['geo']['country'] = $record->country->name; $output[$i]['geo']['country'] = $record->country->name;
$output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} } catch (\Exception $e) {
catch(\Exception $e) {
$output[$i]['geo']['isoCode'] = '--'; $output[$i]['geo']['isoCode'] = '--';
$output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');; $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');
} }
} }
@ -208,13 +204,12 @@ $utopia->patch('/v1/account/name')
->label('sdk.description', 'Update currently logged in user account name.') ->label('sdk.description', 'Update currently logged in user account name.')
->param('name', '', function () {return new Text(100);}, 'User name') ->param('name', '', function () {return new Text(100);}, 'User name')
->action( ->action(
function($name) use ($response, $user, $projectDB, $audit) function ($name) use ($response, $user, $projectDB, $audit) {
{
$user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [ $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [
'name' => $name, 'name' => $name,
])); ]));
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
@ -234,9 +229,8 @@ $utopia->patch('/v1/account/password')
->param('password', '', function () {return new Password();}, 'New password') ->param('password', '', function () {return new Password();}, 'New password')
->param('old-password', '', function () {return new Password();}, 'Old password') ->param('old-password', '', function () {return new Password();}, 'Old password')
->action( ->action(
function($password, $oldPassword) use ($response, $user, $projectDB, $audit) function ($password, $oldPassword) use ($response, $user, $projectDB, $audit) {
{ if (!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password
if(!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password
throw new Exception('Invalid credentials', 401); throw new Exception('Invalid credentials', 401);
} }
@ -244,7 +238,7 @@ $utopia->patch('/v1/account/password')
'password' => Auth::passwordHash($password), 'password' => Auth::passwordHash($password),
])); ]));
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
@ -264,9 +258,8 @@ $utopia->patch('/v1/account/email')
->param('email', '', function () {return new Email();}, 'Email Address') ->param('email', '', function () {return new Email();}, 'Email Address')
->param('password', '', function () {return new Password();}, 'User Password') ->param('password', '', function () {return new Password();}, 'User Password')
->action( ->action(
function($email, $password) use ($response, $user, $projectDB, $audit) function ($email, $password) use ($response, $user, $projectDB, $audit) {
{ if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
if(!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password
throw new Exception('Invalid credentials', 401); throw new Exception('Invalid credentials', 401);
} }
@ -274,12 +267,12 @@ $utopia->patch('/v1/account/email')
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'email=' . $email 'email='.$email,
] ],
]); ]);
if(!empty($profile)) { if (!empty($profile)) {
throw new Exception('User already registered', 400); throw new Exception('User already registered', 400);
} }
@ -289,7 +282,7 @@ $utopia->patch('/v1/account/email')
'email' => $email, 'email' => $email,
])); ]));
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
@ -308,13 +301,12 @@ $utopia->patch('/v1/account/prefs')
->param('prefs', '', function () {return new \Utopia\Validator\Mock();}, 'Prefs key-value JSON object string.') ->param('prefs', '', function () {return new \Utopia\Validator\Mock();}, 'Prefs key-value JSON object string.')
->label('sdk.description', 'Update currently logged in user account preferences. You can pass only the specific settings you wish to update.') ->label('sdk.description', 'Update currently logged in user account preferences. You can pass only the specific settings you wish to update.')
->action( ->action(
function($prefs) use ($response, $user, $projectDB, $audit) function ($prefs) use ($response, $user, $projectDB, $audit) {
{
$user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [ $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [
'prefs' => json_encode(array_merge(json_decode($user->getAttribute('prefs', '{}'), true), $prefs)), 'prefs' => json_encode(array_merge(json_decode($user->getAttribute('prefs', '{}'), true), $prefs)),
])); ]));
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
@ -332,19 +324,18 @@ $utopia->delete('/v1/account')
->label('sdk.method', 'delete') ->label('sdk.method', 'delete')
->label('sdk.description', 'Delete currently logged in user account.') ->label('sdk.description', 'Delete currently logged in user account.')
->action( ->action(
function() use ($response, $request, $user, $projectDB, $audit) function () use ($response, $request, $user, $projectDB, $audit) {
{
$user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [ $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [
'status' => Auth::USER_STATUS_BLOCKED, 'status' => Auth::USER_STATUS_BLOCKED,
])); ]));
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
//TODO delete all tokens or only current session? //TODO delete all tokens or only current session?
//TODO delete all user data according to GDPR. Make sure everything is backed up and backups are deleted later //TODO delete all user data according to GDPR. Make sure everything is backed up and backups are deleted later
/** /*
* Data to delete * Data to delete
* * Tokens * * Tokens
* * Memberships * * Memberships
@ -364,4 +355,4 @@ $utopia->delete('/v1/account')
->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true) ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true)
->json(array('result' => 'success')); ->json(array('result' => 'success'));
} }
); );

View file

@ -35,20 +35,20 @@ $utopia->post('/v1/auth/register')
->param('success', null, function () use ($clients) {return new Host($clients);}, 'Redirect when registration succeed', true) ->param('success', null, function () use ($clients) {return new Host($clients);}, 'Redirect when registration succeed', true)
->param('failure', null, function () use ($clients) {return new Host($clients);}, 'Redirect when registration failed', true) ->param('failure', null, function () use ($clients) {return new Host($clients);}, 'Redirect when registration failed', true)
->action( ->action(
function($email, $password, $name, $redirect, $success, $failure) use ($request, $response, $register, $audit, $projectDB, $project, $webhook) function ($email, $password, $name, $redirect, $success, $failure) use ($request, $response, $register, $audit, $projectDB, $project, $webhook) {
{
$profile = $projectDB->getCollection([ // Get user by email address $profile = $projectDB->getCollection([ // Get user by email address
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'email=' . $email 'email='.$email,
] ],
]); ]);
if(!empty($profile)) { if (!empty($profile)) {
if($failure) { if ($failure) {
$response->redirect($failure); $response->redirect($failure);
return; return;
} }
@ -79,36 +79,36 @@ $utopia->post('/v1/auth/register')
Authorization::enable(); Authorization::enable();
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
Authorization::setRole('user:' . $user->getUid()); Authorization::setRole('user:'.$user->getUid());
$user $user
->setAttribute('tokens', new Document([ ->setAttribute('tokens', new Document([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$permissions' => ['read' => ['user:' . $user->getUid()], 'write' => ['user:' . $user->getUid()]], '$permissions' => ['read' => ['user:'.$user->getUid()], 'write' => ['user:'.$user->getUid()]],
'type' => Auth::TOKEN_TYPE_CONFIRM, 'type' => Auth::TOKEN_TYPE_CONFIRM,
'secret' => Auth::hash($confirmSecret), // On way hash encryption to protect DB leak 'secret' => Auth::hash($confirmSecret), // On way hash encryption to protect DB leak
'expire' => time() + Auth::TOKEN_EXPIRATION_CONFIRM, 'expire' => time() + Auth::TOKEN_EXPIRATION_CONFIRM,
'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]),Document::SET_TYPE_APPEND) ]), Document::SET_TYPE_APPEND)
->setAttribute('tokens', new Document([ ->setAttribute('tokens', new Document([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$permissions' => ['read' => ['user:' . $user->getUid()], 'write' => ['user:' . $user->getUid()]], '$permissions' => ['read' => ['user:'.$user->getUid()], 'write' => ['user:'.$user->getUid()]],
'type' => Auth::TOKEN_TYPE_LOGIN, 'type' => Auth::TOKEN_TYPE_LOGIN,
'secret' => Auth::hash($loginSecret), // On way hash encryption to protect DB leak 'secret' => Auth::hash($loginSecret), // On way hash encryption to protect DB leak
'expire' => $expiry, 'expire' => $expiry,
'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]),Document::SET_TYPE_APPEND) ]), Document::SET_TYPE_APPEND)
; ;
$user = $projectDB->createDocument($user->getArrayCopy()); $user = $projectDB->createDocument($user->getArrayCopy());
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving tokens to DB', 500); throw new Exception('Failed saving tokens to DB', 500);
} }
@ -118,7 +118,7 @@ $utopia->post('/v1/auth/register')
$redirect['query'] = Template::mergeQuery(((isset($redirect['query'])) ? $redirect['query'] : ''), ['userId' => $user->getUid(), 'token' => $confirmSecret]); $redirect['query'] = Template::mergeQuery(((isset($redirect['query'])) ? $redirect['query'] : ''), ['userId' => $user->getUid(), 'token' => $confirmSecret]);
$redirect = Template::unParseURL($redirect); $redirect = Template::unParseURL($redirect);
$body = new Template(__DIR__ . '/../config/locale/templates/' . Locale::getText('auth.emails.confirm.body')); $body = new Template(__DIR__.'/../config/locale/templates/'.Locale::getText('auth.emails.confirm.body'));
$body $body
->setParam('{{direction}}', Locale::getText('settings.direction')) ->setParam('{{direction}}', Locale::getText('settings.direction'))
->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
@ -131,13 +131,12 @@ $utopia->post('/v1/auth/register')
$mail->addAddress($email, $name); $mail->addAddress($email, $name);
$mail->Subject = Locale::getText('auth.emails.confirm.title'); $mail->Subject = Locale::getText('auth.emails.confirm.title');
$mail->Body = $body->render(); $mail->Body = $body->render();
$mail->AltBody = strip_tags($body->render()); $mail->AltBody = strip_tags($body->render());
try { try {
$mail->send(); $mail->send();
} } catch (\Exception $error) {
catch(\Exception $error) {
// if($failure) { // if($failure) {
// $response->redirect($failure); // $response->redirect($failure);
// return; // return;
@ -160,7 +159,7 @@ $utopia->post('/v1/auth/register')
$response->addCookie(Auth::$cookieName, Auth::encodeSession($user->getUid(), $loginSecret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true); $response->addCookie(Auth::$cookieName, Auth::encodeSession($user->getUid(), $loginSecret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true);
if($success) { if ($success) {
$response->redirect($success); $response->redirect($success);
} }
@ -174,30 +173,29 @@ $utopia->post('/v1/auth/register/confirm')
->label('scope', 'public') ->label('scope', 'public')
->label('sdk.namespace', 'auth') ->label('sdk.namespace', 'auth')
->label('sdk.method', 'confirm') ->label('sdk.method', 'confirm')
->label('sdk.description', "Use this endpoint to complete the confirmation of the user account email address. Both the **userId** and **token** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the /auth/register endpoint.") ->label('sdk.description', 'Use this endpoint to complete the confirmation of the user account email address. Both the **userId** and **token** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the /auth/register endpoint.')
->label('abuse-limit', 10) ->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{param-userId}') ->label('abuse-key', 'url:{url},userId:{param-userId}')
->param('userId', '', function () {return new UID();}, 'User unique ID') ->param('userId', '', function () {return new UID();}, 'User unique ID')
->param('token', '', function () {return new Text(256);}, 'Confirmation secret token') ->param('token', '', function () {return new Text(256);}, 'Confirmation secret token')
->action( ->action(
function($userId, $token) use ($response, $request, $projectDB, $audit) function ($userId, $token) use ($response, $request, $projectDB, $audit) {
{
$profile = $projectDB->getCollection([ // Get user by email address $profile = $projectDB->getCollection([ // Get user by email address
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'$uid=' . $userId '$uid='.$userId,
] ],
]); ]);
if(empty($profile)) { if (empty($profile)) {
throw new Exception('User not found', 404); // TODO maybe hide this throw new Exception('User not found', 404); // TODO maybe hide this
} }
$token = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_CONFIRM, $token); $token = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_CONFIRM, $token);
if(!$token) { if (!$token) {
throw new Exception('Confirmation token is not valid', 401); throw new Exception('Confirmation token is not valid', 401);
} }
@ -206,11 +204,11 @@ $utopia->post('/v1/auth/register/confirm')
'confirm' => true, 'confirm' => true,
])); ]));
if(false === $profile) { if (false === $profile) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
if(!$projectDB->deleteDocument($token)) { if (!$projectDB->deleteDocument($token)) {
throw new Exception('Failed to remove token from DB', 500); throw new Exception('Failed to remove token from DB', 500);
} }
@ -230,9 +228,8 @@ $utopia->post('/v1/auth/register/confirm/resend')
->label('abuse-key', 'url:{url},userId:{param-userId}') ->label('abuse-key', 'url:{url},userId:{param-userId}')
->param('redirect', '', function () use ($clients) {return new Host($clients);}, 'Confirmation page to redirect user to your app after confirm token has been sent to user email.') ->param('redirect', '', function () use ($clients) {return new Host($clients);}, 'Confirmation page to redirect user to your app after confirm token has been sent to user email.')
->action( ->action(
function($redirect) use ($response, $request, $projectDB, $user, $register, $project) function ($redirect) use ($response, $request, $projectDB, $user, $register, $project) {
{ if ($user->getAttribute('confirm', false)) {
if($user->getAttribute('confirm', false)) {
throw new Exception('Email address is already confirmed', 400); throw new Exception('Email address is already confirmed', 400);
} }
@ -240,7 +237,7 @@ $utopia->post('/v1/auth/register/confirm/resend')
$user->setAttribute('tokens', new Document([ $user->setAttribute('tokens', new Document([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$permissions' => ['read' => ['user:' . $user->getUid()], 'write' => ['user:' . $user->getUid()]], '$permissions' => ['read' => ['user:'.$user->getUid()], 'write' => ['user:'.$user->getUid()]],
'type' => Auth::TOKEN_TYPE_CONFIRM, 'type' => Auth::TOKEN_TYPE_CONFIRM,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'expire' => time() + Auth::TOKEN_EXPIRATION_CONFIRM, 'expire' => time() + Auth::TOKEN_EXPIRATION_CONFIRM,
@ -250,7 +247,7 @@ $utopia->post('/v1/auth/register/confirm/resend')
$user = $projectDB->updateDocument($user->getArrayCopy()); $user = $projectDB->updateDocument($user->getArrayCopy());
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
@ -258,7 +255,7 @@ $utopia->post('/v1/auth/register/confirm/resend')
$redirect['query'] = Template::mergeQuery(((isset($redirect['query'])) ? $redirect['query'] : ''), ['userId' => $user->getUid(), 'token' => $secret]); $redirect['query'] = Template::mergeQuery(((isset($redirect['query'])) ? $redirect['query'] : ''), ['userId' => $user->getUid(), 'token' => $secret]);
$redirect = Template::unParseURL($redirect); $redirect = Template::unParseURL($redirect);
$body = new Template(__DIR__ . '/../config/locale/templates/' . Locale::getText('auth.emails.confirm.body')); $body = new Template(__DIR__.'/../config/locale/templates/'.Locale::getText('auth.emails.confirm.body'));
$body $body
->setParam('{{direction}}', Locale::getText('settings.direction')) ->setParam('{{direction}}', Locale::getText('settings.direction'))
->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
@ -271,13 +268,12 @@ $utopia->post('/v1/auth/register/confirm/resend')
$mail->addAddress($user->getAttribute('email'), $user->getAttribute('name')); $mail->addAddress($user->getAttribute('email'), $user->getAttribute('name'));
$mail->Subject = Locale::getText('auth.emails.confirm.title'); $mail->Subject = Locale::getText('auth.emails.confirm.title');
$mail->Body = $body->render(); $mail->Body = $body->render();
$mail->AltBody = strip_tags($body->render()); $mail->AltBody = strip_tags($body->render());
try { try {
$mail->send(); $mail->send();
} } catch (\Exception $error) {
catch(\Exception $error) {
//throw new Exception('Problem sending mail: ' . $error->getError(), 500); //throw new Exception('Problem sending mail: ' . $error->getError(), 500);
} }
@ -300,26 +296,25 @@ $utopia->post('/v1/auth/login')
->param('success', null, function () use ($clients) {return new Host($clients);}, 'URL to redirect back to your app after a successful login attempt.', true) ->param('success', null, function () use ($clients) {return new Host($clients);}, 'URL to redirect back to your app after a successful login attempt.', true)
->param('failure', null, function () use ($clients) {return new Host($clients);}, 'URL to redirect back to your app after a failed login attempt.', true) ->param('failure', null, function () use ($clients) {return new Host($clients);}, 'URL to redirect back to your app after a failed login attempt.', true)
->action( ->action(
function($email, $password, $success, $failure) use ($response, $request, $projectDB, $audit, $webhook) function ($email, $password, $success, $failure) use ($response, $request, $projectDB, $audit, $webhook) {
{
$profile = $projectDB->getCollection([ // Get user by email address $profile = $projectDB->getCollection([ // Get user by email address
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'email=' . $email 'email='.$email,
] ],
]); ]);
if(!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) { if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) {
$audit $audit
//->setParam('userId', $profile->getUid()) //->setParam('userId', $profile->getUid())
->setParam('event', 'auth.failure') ->setParam('event', 'auth.failure')
; ;
if($failure) { if ($failure) {
$response->redirect($failure); $response->redirect($failure);
return; return;
} }
@ -331,19 +326,19 @@ $utopia->post('/v1/auth/login')
$profile->setAttribute('tokens', new Document([ $profile->setAttribute('tokens', new Document([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$permissions' => ['read' => ['user:' . $profile->getUid()], 'write' => ['user:' . $profile->getUid()]], '$permissions' => ['read' => ['user:'.$profile->getUid()], 'write' => ['user:'.$profile->getUid()]],
'type' => Auth::TOKEN_TYPE_LOGIN, 'type' => Auth::TOKEN_TYPE_LOGIN,
'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
'expire' => $expiry, 'expire' => $expiry,
'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]),Document::SET_TYPE_APPEND); ]), Document::SET_TYPE_APPEND);
Authorization::setRole('user:' . $profile->getUid()); Authorization::setRole('user:'.$profile->getUid());
$profile = $projectDB->updateDocument($profile->getArrayCopy()); $profile = $projectDB->updateDocument($profile->getArrayCopy());
if(false === $profile) { if (false === $profile) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
@ -362,13 +357,12 @@ $utopia->post('/v1/auth/login')
$response $response
->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getUid(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true); ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getUid(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true);
if($success) { if ($success) {
$response->redirect($success); $response->redirect($success);
} }
$response $response
->json(array('result' => 'success')); ->json(array('result' => 'success'));
;
} }
); );
@ -381,11 +375,10 @@ $utopia->delete('/v1/auth/logout')
->label('sdk.description', 'Use this endpoint to log out the currently logged in user from his account. When succeed this endpoint will delete the user session and remove the session secret cookie.') ->label('sdk.description', 'Use this endpoint to log out the currently logged in user from his account. When succeed this endpoint will delete the user session and remove the session secret cookie.')
->label('abuse-limit', 100) ->label('abuse-limit', 100)
->action( ->action(
function() use ($response, $request, $user, $projectDB, $audit, $webhook) function () use ($response, $request, $user, $projectDB, $audit, $webhook) {
{
$token = Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret); $token = Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
if(!$projectDB->deleteDocument($token)) { if (!$projectDB->deleteDocument($token)) {
throw new Exception('Failed to remove token from DB', 500); throw new Exception('Failed to remove token from DB', 500);
} }
@ -414,23 +407,21 @@ $utopia->delete('/v1/auth/logout/:id')
->label('abuse-limit', 100) ->label('abuse-limit', 100)
->param('id', null, function () {return new UID();}, 'User specific session unique ID number. if 0 delete all sessions.') ->param('id', null, function () {return new UID();}, 'User specific session unique ID number. if 0 delete all sessions.')
->action( ->action(
function($id) use ($response, $request, $user, $projectDB, $audit) function ($id) use ($response, $request, $user, $projectDB, $audit) {
{
$tokens = $user->getAttribute('tokens', []); $tokens = $user->getAttribute('tokens', []);
foreach($tokens as $token) { /* @var $token Document */ foreach ($tokens as $token) { /* @var $token Document */
if(($id == $token->getUid() || ($id == 0)) && Auth::TOKEN_TYPE_LOGIN == $token->getAttribute('type')) { if (($id == $token->getUid() || ($id == 0)) && Auth::TOKEN_TYPE_LOGIN == $token->getAttribute('type')) {
if (!$projectDB->deleteDocument($token->getUid())) {
if(!$projectDB->deleteDocument($token->getUid())) {
throw new Exception('Failed to remove token from DB', 500); throw new Exception('Failed to remove token from DB', 500);
} }
$audit $audit
->setParam('event', 'auth.logout') ->setParam('event', 'auth.logout')
->setParam('resource', '/auth/token/' . $token->getUid()) ->setParam('resource', '/auth/token/'.$token->getUid())
; ;
if($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete cookies if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete cookies
$response->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true); $response->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true);
} }
} }
@ -451,18 +442,17 @@ $utopia->post('/v1/auth/recovery')
->param('email', '', function () {return new Email();}, 'User account email address.') ->param('email', '', function () {return new Email();}, 'User account email address.')
->param('redirect', '', function () use ($clients) {return new Host($clients);}, 'Reset page in your app to redirect user after reset token has been sent to user email.') ->param('redirect', '', function () use ($clients) {return new Host($clients);}, 'Reset page in your app to redirect user after reset token has been sent to user email.')
->action( ->action(
function($email, $redirect) use ($request, $response, $projectDB, $register, $audit, $project) function ($email, $redirect) use ($request, $response, $projectDB, $register, $audit, $project) {
{
$profile = $projectDB->getCollection([ // Get user by email address $profile = $projectDB->getCollection([ // Get user by email address
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'email=' . $email 'email='.$email,
] ],
]); ]);
if(empty($profile)) { if (empty($profile)) {
throw new Exception('User not found', 404); // TODO maybe hide this throw new Exception('User not found', 404); // TODO maybe hide this
} }
@ -470,7 +460,7 @@ $utopia->post('/v1/auth/recovery')
$profile->setAttribute('tokens', new Document([ $profile->setAttribute('tokens', new Document([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$permissions' => ['read' => ['user:' . $profile->getUid()], 'write' => ['user:' . $profile->getUid()]], '$permissions' => ['read' => ['user:'.$profile->getUid()], 'write' => ['user:'.$profile->getUid()]],
'type' => Auth::TOKEN_TYPE_RECOVERY, 'type' => Auth::TOKEN_TYPE_RECOVERY,
'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
'expire' => time() + Auth::TOKEN_EXPIRATION_RECOVERY, 'expire' => time() + Auth::TOKEN_EXPIRATION_RECOVERY,
@ -478,11 +468,11 @@ $utopia->post('/v1/auth/recovery')
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]), Document::SET_TYPE_APPEND); ]), Document::SET_TYPE_APPEND);
Authorization::setRole('user:' . $profile->getUid()); Authorization::setRole('user:'.$profile->getUid());
$profile = $projectDB->updateDocument($profile->getArrayCopy()); $profile = $projectDB->updateDocument($profile->getArrayCopy());
if(false === $profile) { if (false === $profile) {
throw new Exception('Failed to save user to DB', 500); throw new Exception('Failed to save user to DB', 500);
} }
@ -490,7 +480,7 @@ $utopia->post('/v1/auth/recovery')
$redirect['query'] = Template::mergeQuery(((isset($redirect['query'])) ? $redirect['query'] : ''), ['userId' => $profile->getUid(), 'token' => $secret]); $redirect['query'] = Template::mergeQuery(((isset($redirect['query'])) ? $redirect['query'] : ''), ['userId' => $profile->getUid(), 'token' => $secret]);
$redirect = Template::unParseURL($redirect); $redirect = Template::unParseURL($redirect);
$body = new Template(__DIR__ . '/../config/locale/templates/' . Locale::getText('auth.emails.recovery.body')); $body = new Template(__DIR__.'/../config/locale/templates/'.Locale::getText('auth.emails.recovery.body'));
$body $body
->setParam('{{direction}}', Locale::getText('settings.direction')) ->setParam('{{direction}}', Locale::getText('settings.direction'))
->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
@ -503,13 +493,12 @@ $utopia->post('/v1/auth/recovery')
$mail->addAddress($profile->getAttribute('email', ''), $profile->getAttribute('name', '')); $mail->addAddress($profile->getAttribute('email', ''), $profile->getAttribute('name', ''));
$mail->Subject = Locale::getText('auth.emails.recovery.title'); $mail->Subject = Locale::getText('auth.emails.recovery.title');
$mail->Body = $body->render(); $mail->Body = $body->render();
$mail->AltBody = strip_tags($body->render()); $mail->AltBody = strip_tags($body->render());
try { try {
$mail->send(); $mail->send();
} } catch (\Exception $error) {
catch(\Exception $error) {
//throw new Exception('Problem sending mail: ' . $error->getError(), 500); //throw new Exception('Problem sending mail: ' . $error->getError(), 500);
} }
@ -535,9 +524,8 @@ $utopia->put('/v1/auth/recovery/reset')
->param('password-a', '', function () {return new Password();}, 'New password.') ->param('password-a', '', function () {return new Password();}, 'New password.')
->param('password-b', '', function () {return new Password();}, 'New password again.') ->param('password-b', '', function () {return new Password();}, 'New password again.')
->action( ->action(
function($userId, $token, $passwordA, $passwordB) use ($response, $projectDB, $audit) function ($userId, $token, $passwordA, $passwordB) use ($response, $projectDB, $audit) {
{ if ($passwordA !== $passwordB) {
if($passwordA !== $passwordB) {
throw new Exception('Passwords must match', 400); throw new Exception('Passwords must match', 400);
} }
@ -545,22 +533,22 @@ $utopia->put('/v1/auth/recovery/reset')
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'$uid=' . $userId '$uid='.$userId,
] ],
]); ]);
if(empty($profile)) { if (empty($profile)) {
throw new Exception('User not found', 404); // TODO maybe hide this throw new Exception('User not found', 404); // TODO maybe hide this
} }
$token = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_RECOVERY, $token); $token = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_RECOVERY, $token);
if(!$token) { if (!$token) {
throw new Exception('Recovery token is not valid', 401); throw new Exception('Recovery token is not valid', 401);
} }
Authorization::setRole('user:' . $profile->getUid()); Authorization::setRole('user:'.$profile->getUid());
$profile = $projectDB->updateDocument(array_merge($profile->getArrayCopy(), [ $profile = $projectDB->updateDocument(array_merge($profile->getArrayCopy(), [
'password' => Auth::passwordHash($passwordA), 'password' => Auth::passwordHash($passwordA),
@ -568,11 +556,11 @@ $utopia->put('/v1/auth/recovery/reset')
'confirm' => true, 'confirm' => true,
])); ]));
if(false === $profile) { if (false === $profile) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
if(!$projectDB->deleteDocument($token)) { if (!$projectDB->deleteDocument($token)) {
throw new Exception('Failed to remove token from DB', 500); throw new Exception('Failed to remove token from DB', 500);
} }
@ -587,7 +575,7 @@ $utopia->put('/v1/auth/recovery/reset')
$utopia->get('/v1/auth/oauth/:provider') $utopia->get('/v1/auth/oauth/:provider')
->desc('OAuth Login') ->desc('OAuth Login')
->label('error', __DIR__ . '/../views/general/error.phtml') ->label('error', __DIR__.'/../views/general/error.phtml')
->label('scope', 'auth') ->label('scope', 'auth')
->label('sdk.namespace', 'auth') ->label('sdk.namespace', 'auth')
->label('sdk.method', 'oauth') ->label('sdk.method', 'oauth')
@ -597,26 +585,25 @@ $utopia->get('/v1/auth/oauth/:provider')
->param('success', '', function () use ($clients) {return new Host($clients);}, 'URL to redirect back to your app after a successful login attempt.', true) ->param('success', '', function () use ($clients) {return new Host($clients);}, 'URL to redirect back to your app after a successful login attempt.', true)
->param('failure', '', function () use ($clients) {return new Host($clients);}, 'URL to redirect back to your app after a failed login attempt.', true) ->param('failure', '', function () use ($clients) {return new Host($clients);}, 'URL to redirect back to your app after a failed login attempt.', true)
->action( ->action(
function($provider, $success, $failure) use ($response, $request, $project) function ($provider, $success, $failure) use ($response, $request, $project) {
{ $callback = $request->getServer('REQUEST_SCHEME', 'https').'://'.$request->getServer('HTTP_HOST').'/v1/auth/oauth/callback/'.$provider.'/'.$project->getUid();
$callback = $request->getServer('REQUEST_SCHEME', 'https') . '://' . $request->getServer('HTTP_HOST') . '/v1/auth/oauth/callback/' . $provider . '/' . $project->getUid(); $appId = $project->getAttribute('usersOauth'.ucfirst($provider).'Appid', '');
$appId = $project->getAttribute('usersOauth' . ucfirst($provider) . 'Appid', ''); $appSecret = $project->getAttribute('usersOauth'.ucfirst($provider).'Secret', '{}');
$appSecret = $project->getAttribute('usersOauth' . ucfirst($provider) . 'Secret', '{}');
$appSecret = json_decode($appSecret, true); $appSecret = json_decode($appSecret, true);
if(!empty($appSecret) && isset($appSecret['version'])) { if (!empty($appSecret) && isset($appSecret['version'])) {
$key = $request->getServer('_APP_OPENSSL_KEY_V' . $appSecret['version']); $key = $request->getServer('_APP_OPENSSL_KEY_V'.$appSecret['version']);
$appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key,0, hex2bin($appSecret['iv']), hex2bin($appSecret['tag'])); $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, hex2bin($appSecret['iv']), hex2bin($appSecret['tag']));
} }
if(empty($appId) || empty($appSecret)) { if (empty($appId) || empty($appSecret)) {
throw new Exception('Provider is undefined, configure provider app ID and app secret key to continue', 412); throw new Exception('Provider is undefined, configure provider app ID and app secret key to continue', 412);
} }
$classname = 'Auth\\OAuth\\' . ucfirst($provider); $classname = 'Auth\\OAuth\\'.ucfirst($provider);
if(!class_exists($classname)) { if (!class_exists($classname)) {
throw new Exception('Provider is not supported', 501); throw new Exception('Provider is not supported', 501);
} }
@ -628,7 +615,7 @@ $utopia->get('/v1/auth/oauth/:provider')
$utopia->get('/v1/auth/oauth/callback/:provider/:projectId') $utopia->get('/v1/auth/oauth/callback/:provider/:projectId')
->desc('OAuth Callback') ->desc('OAuth Callback')
->label('error', __DIR__ . '/../views/general/error.phtml') ->label('error', __DIR__.'/../views/general/error.phtml')
->label('scope', 'auth') ->label('scope', 'auth')
->label('sdk.namespace', 'auth') ->label('sdk.namespace', 'auth')
->label('sdk.method', 'oauthCallback') ->label('sdk.method', 'oauthCallback')
@ -639,16 +626,15 @@ $utopia->get('/v1/auth/oauth/callback/:provider/:projectId')
->param('code', '', function () {return new Text(1024);}, 'OAuth code') ->param('code', '', function () {return new Text(1024);}, 'OAuth code')
->param('state', '', function () {return new Text(2048);}, 'Login state params', true) ->param('state', '', function () {return new Text(2048);}, 'Login state params', true)
->action( ->action(
function($projectId, $provider, $code, $state) use ($response, $request, $domain) function ($projectId, $provider, $code, $state) use ($response, $request, $domain) {
{ $response->redirect($request->getServer('REQUEST_SCHEME', 'https').'://'.$domain.'/v1/auth/oauth/'.$provider.'/redirect?'
$response->redirect($request->getServer('REQUEST_SCHEME', 'https') . '://' . $domain . '/v1/auth/oauth/' . $provider . '/redirect?' .http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
. http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
} }
); );
$utopia->get('/v1/auth/oauth/:provider/redirect') $utopia->get('/v1/auth/oauth/:provider/redirect')
->desc('OAuth Redirect') ->desc('OAuth Redirect')
->label('error', __DIR__ . '/../views/general/error.phtml') ->label('error', __DIR__.'/../views/general/error.phtml')
->label('webhook', 'auth.oauth') ->label('webhook', 'auth.oauth')
->label('scope', 'auth') ->label('scope', 'auth')
->label('sdk.namespace', 'auth') ->label('sdk.namespace', 'auth')
@ -660,46 +646,42 @@ $utopia->get('/v1/auth/oauth/:provider/redirect')
->param('code', '', function () {return new Text(1024);}, 'OAuth code') ->param('code', '', function () {return new Text(1024);}, 'OAuth code')
->param('state', '', function () {return new Text(2048);}, 'OAuth state params', true) ->param('state', '', function () {return new Text(2048);}, 'OAuth state params', true)
->action( ->action(
function($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit) function ($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit) {
{ $callback = $request->getServer('REQUEST_SCHEME', 'https').'://'.$request->getServer('HTTP_HOST').'/v1/auth/oauth/callback/'.$provider.'/'.$project->getUid();
$callback = $request->getServer('REQUEST_SCHEME', 'https') . '://' . $request->getServer('HTTP_HOST') . '/v1/auth/oauth/callback/' . $provider . '/' . $project->getUid(); $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; $validateURL = new URL();
$validateURL = new URL();
if(!empty($state)) { if (!empty($state)) {
try { try {
$state = array_merge($defaultState, json_decode($state, true)); $state = array_merge($defaultState, json_decode($state, true));
} } catch (\Exception $exception) {
catch (\Exception $exception) {
throw new Exception('Failed to parse login state params as passed from OAuth provider'); throw new Exception('Failed to parse login state params as passed from OAuth provider');
} }
} } else {
else {
$state = $defaultState; $state = $defaultState;
} }
if(!$validateURL->isValid($state['success'])) { if (!$validateURL->isValid($state['success'])) {
throw new Exception('Invalid redirect URL for success login', 400); throw new Exception('Invalid redirect URL for success login', 400);
} }
if(!empty($state['failure']) && !$validateURL->isValid($state['failure'])) { if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) {
throw new Exception('Invalid redirect URL for failure login', 400); throw new Exception('Invalid redirect URL for failure login', 400);
} }
$appId = $project->getAttribute('usersOauth' . ucfirst($provider) . 'Appid', ''); $appId = $project->getAttribute('usersOauth'.ucfirst($provider).'Appid', '');
$appSecret = $project->getAttribute('usersOauth' . ucfirst($provider) . 'Secret', '{}'); $appSecret = $project->getAttribute('usersOauth'.ucfirst($provider).'Secret', '{}');
$appSecret = json_decode($appSecret, true); $appSecret = json_decode($appSecret, true);
if(!empty($appSecret) && isset($appSecret['version'])) { if (!empty($appSecret) && isset($appSecret['version'])) {
$key = $request->getServer('_APP_OPENSSL_KEY_V' . $appSecret['version']); $key = $request->getServer('_APP_OPENSSL_KEY_V'.$appSecret['version']);
$appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key,0, hex2bin($appSecret['iv']), hex2bin($appSecret['tag'])); $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, hex2bin($appSecret['iv']), hex2bin($appSecret['tag']));
} }
$classname = 'Auth\\OAuth\\'.ucfirst($provider);
$classname = 'Auth\\OAuth\\' . ucfirst($provider); if (!class_exists($classname)) {
if(!class_exists($classname)) {
throw new Exception('Provider is not supported', 501); throw new Exception('Provider is not supported', 501);
} }
@ -707,8 +689,8 @@ $utopia->get('/v1/auth/oauth/:provider/redirect')
$accessToken = $oauth->getAccessToken($code); $accessToken = $oauth->getAccessToken($code);
if(empty($accessToken)) { if (empty($accessToken)) {
if(!empty($state['failure'])) { if (!empty($state['failure'])) {
$response->redirect($state['failure'], 301, 0); $response->redirect($state['failure'], 301, 0);
} }
@ -717,8 +699,8 @@ $utopia->get('/v1/auth/oauth/:provider/redirect')
$oauthID = $oauth->getUserID($accessToken); $oauthID = $oauth->getUserID($accessToken);
if(empty($oauthID)) { if (empty($oauthID)) {
if(!empty($state['failure'])) { if (!empty($state['failure'])) {
$response->redirect($state['failure'], 301, 0); $response->redirect($state['failure'], 301, 0);
} }
@ -727,7 +709,7 @@ $utopia->get('/v1/auth/oauth/:provider/redirect')
$current = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret); $current = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
if($current) { if ($current) {
$projectDB->deleteDocument($current); //throw new Exception('User already logged in', 401); $projectDB->deleteDocument($current); //throw new Exception('User already logged in', 401);
} }
@ -735,25 +717,25 @@ $utopia->get('/v1/auth/oauth/:provider/redirect')
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'oauth' . ucfirst($provider) . '=' . $oauthID 'oauth'.ucfirst($provider).'='.$oauthID,
] ],
]) : $user; ]) : $user;
if(empty($user)) { // No user logged in or with oauth provider ID, create new one or connect with account with same email if (empty($user)) { // No user logged in or with oauth provider ID, create new one or connect with account with same email
$name = $oauth->getUserName($accessToken); $name = $oauth->getUserName($accessToken);
$email = $oauth->getUserEmail($accessToken); $email = $oauth->getUserEmail($accessToken);
$user = $projectDB->getCollection([ // Get user by provider email address $user = $projectDB->getCollection([ // Get user by provider email address
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'email=' . $email 'email='.$email,
] ],
]); ]);
if(empty($user->getUid())) { // Last option -> create user alone, generate random password if (empty($user->getUid())) { // Last option -> create user alone, generate random password
Authorization::disable(); Authorization::disable();
$user = $projectDB->createDocument([ $user = $projectDB->createDocument([
@ -771,7 +753,7 @@ $utopia->get('/v1/auth/oauth/:provider/redirect')
Authorization::enable(); Authorization::enable();
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
} }
@ -783,12 +765,12 @@ $utopia->get('/v1/auth/oauth/:provider/redirect')
$expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$user $user
->setAttribute('oauth' . ucfirst($provider), $oauthID) ->setAttribute('oauth'.ucfirst($provider), $oauthID)
->setAttribute('oauth' . ucfirst($provider) . 'AccessToken', $accessToken) ->setAttribute('oauth'.ucfirst($provider).'AccessToken', $accessToken)
->setAttribute('status', Auth::USER_STATUS_ACTIVATED) ->setAttribute('status', Auth::USER_STATUS_ACTIVATED)
->setAttribute('tokens', new Document([ ->setAttribute('tokens', new Document([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$permissions' => ['read' => ['user:' . $user['$uid']], 'write' => ['user:' . $user['$uid']]], '$permissions' => ['read' => ['user:'.$user['$uid']], 'write' => ['user:'.$user['$uid']]],
'type' => Auth::TOKEN_TYPE_LOGIN, 'type' => Auth::TOKEN_TYPE_LOGIN,
'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
'expire' => $expiry, 'expire' => $expiry,
@ -797,11 +779,11 @@ $utopia->get('/v1/auth/oauth/:provider/redirect')
]), Document::SET_TYPE_APPEND) ]), Document::SET_TYPE_APPEND)
; ;
Authorization::setRole('user:' . $user->getUid()); Authorization::setRole('user:'.$user->getUid());
$user = $projectDB->updateDocument($user->getArrayCopy()); $user = $projectDB->updateDocument($user->getArrayCopy());
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
@ -817,4 +799,4 @@ $utopia->get('/v1/auth/oauth/:provider/redirect')
$response->redirect($state['success']); $response->redirect($state['success']);
} }
); );

View file

@ -16,21 +16,20 @@ use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer; use BaconQrCode\Writer;
$types = [ $types = [
'browsers' => include __DIR__ . '/../config/avatars/browsers.php', 'browsers' => include __DIR__.'/../config/avatars/browsers.php',
'credit-cards' => include __DIR__ . '/../config/avatars/credit-cards.php', 'credit-cards' => include __DIR__.'/../config/avatars/credit-cards.php',
'flags' => include __DIR__ . '/../config/avatars/flags.php', 'flags' => include __DIR__.'/../config/avatars/flags.php',
]; ];
$avatarCallback = function($type, $code, $width, $height, $quality) use ($types, $response, $request) $avatarCallback = function ($type, $code, $width, $height, $quality) use ($types, $response, $request) {
{
$code = strtolower($code); $code = strtolower($code);
$type = strtolower($type); $type = strtolower($type);
if(!array_key_exists($type, $types)) { if (!array_key_exists($type, $types)) {
throw new Exception('Avatar set not found', 404); throw new Exception('Avatar set not found', 404);
} }
if(!array_key_exists($code, $types[$type])) { if (!array_key_exists($code, $types[$type])) {
throw new Exception('Avatar not found', 404); throw new Exception('Avatar not found', 404);
} }
@ -38,20 +37,20 @@ $avatarCallback = function($type, $code, $width, $height, $quality) use ($types,
throw new Exception('Imagick extension is missing', 500); throw new Exception('Imagick extension is missing', 500);
} }
$output = 'png'; $output = 'png';
$date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache $date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = md5('/v1/avatars/:type/:code-' . $code .$width . $height . $quality . $output); $key = md5('/v1/avatars/:type/:code-'.$code.$width.$height.$quality.$output);
$path = $types[$type][$code]; $path = $types[$type][$code];
$type = 'png'; $type = 'png';
if (!file_exists($path)) { if (!file_exists($path)) {
throw new Exception('File not found in ' . $path, 404); throw new Exception('File not found in '.$path, 404);
} }
$cache = new Cache(new Filesystem('/storage/cache/app-0')); // Limit file number or size $cache = new Cache(new Filesystem('/storage/cache/app-0')); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */); $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
if($data) { if ($data) {
//$output = (empty($output)) ? $type : $output; //$output = (empty($output)) ? $type : $output;
$response $response
@ -64,7 +63,7 @@ $avatarCallback = function($type, $code, $width, $height, $quality) use ($types,
$resize = new Resize(file_get_contents($path)); $resize = new Resize(file_get_contents($path));
$resize->crop((int)$width, (int)$height); $resize->crop((int) $width, (int) $height);
$output = (empty($output)) ? $type : $output; $output = (empty($output)) ? $type : $output;
@ -88,7 +87,7 @@ $avatarCallback = function($type, $code, $width, $height, $quality) use ($types,
$utopia->get('/v1/avatars/credit-cards/:code') $utopia->get('/v1/avatars/credit-cards/:code')
->desc('Get Credit Card Icon') ->desc('Get Credit Card Icon')
->param('code', '', function () use ($types) {return new WhiteList(array_keys($types['credit-cards'])); }, 'Credit Card Code. Possible values: ' . implode(', ', array_keys($types['credit-cards'])) . '.') ->param('code', '', function () use ($types) {return new WhiteList(array_keys($types['credit-cards'])); }, 'Credit Card Code. Possible values: '.implode(', ', array_keys($types['credit-cards'])).'.')
->param('width', 100, function () {return new Range(0, 2000);}, 'Image width. Pass an integer between 0 to 2000. Defaults to 100', true) ->param('width', 100, function () {return new Range(0, 2000);}, 'Image width. Pass an integer between 0 to 2000. Defaults to 100', true)
->param('height', 100, function () {return new Range(0, 2000);}, 'Image height. Pass an integer between 0 to 2000. Defaults to 100', true) ->param('height', 100, function () {return new Range(0, 2000);}, 'Image height. Pass an integer between 0 to 2000. Defaults to 100', true)
->param('quality', 100, function () {return new Range(0, 100);}, 'Image quality. Pass an integer between 0 to 100. Defaults to 100', true) ->param('quality', 100, function () {return new Range(0, 100);}, 'Image quality. Pass an integer between 0 to 100. Defaults to 100', true)
@ -132,17 +131,16 @@ $utopia->get('/v1/avatars/image')
->label('sdk.method', 'getImage') ->label('sdk.method', 'getImage')
->label('sdk.description', 'Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in cases, you want to make sure a 3rd party image is properly served using a TLS protocol.') ->label('sdk.description', 'Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in cases, you want to make sure a 3rd party image is properly served using a TLS protocol.')
->action( ->action(
function($url, $width, $height) use ($response, $request, $version) function ($url, $width, $height) use ($response, $request, $version) {
{ $quality = 80;
$quality = 80; $output = 'png';
$output = 'png'; $date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache $key = md5('/v2/avatars/images-'.$url.'-'.$width.'/'.$height.'/'.$quality);
$key = md5('/v2/avatars/images-' . $url . '-' . $width . '/' . $height . '/' . $quality); $type = 'png';
$type = 'png'; $cache = new Cache(new Filesystem('/storage/cache/app-0')); // Limit file number or size
$cache = new Cache(new Filesystem('/storage/cache/app-0')); // Limit file number or size $data = $cache->load($key, 60 * 60 * 24 * 7 /* 1 week */);
$data = $cache->load($key, 60 * 60 * 24 * 7 /* 1 week */);
if($data) { if ($data) {
$response $response
->setContentType('image/png') ->setContentType('image/png')
->addHeader('Expires', $date) ->addHeader('Expires', $date)
@ -157,18 +155,17 @@ $utopia->get('/v1/avatars/image')
$fetch = @file_get_contents($url, false); $fetch = @file_get_contents($url, false);
if(!$fetch) { if (!$fetch) {
throw new Exception('Image not found', 404); throw new Exception('Image not found', 404);
} }
try { try {
$resize = new Resize($fetch); $resize = new Resize($fetch);
} } catch (\Exception $exception) {
catch (\Exception $exception) {
throw new Exception('Unable to parse image', 500); throw new Exception('Unable to parse image', 500);
} }
$resize->crop((int)$width, (int)$height); $resize->crop((int) $width, (int) $height);
$output = (empty($output)) ? $type : $output; $output = (empty($output)) ? $type : $output;
@ -199,19 +196,18 @@ $utopia->get('/v1/avatars/favicon')
->label('sdk.method', 'getFavicon') ->label('sdk.method', 'getFavicon')
->label('sdk.description', 'Use this endpoint to fetch the favorite icon (AKA favicon) of a any remote website URL.') ->label('sdk.description', 'Use this endpoint to fetch the favorite icon (AKA favicon) of a any remote website URL.')
->action( ->action(
function($url) use ($response, $request, $version) function ($url) use ($response, $request, $version) {
{ $width = 56;
$width = 56; $height = 56;
$height = 56; $quality = 80;
$quality = 80; $output = 'png';
$output = 'png'; $date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache $key = md5('/v2/avatars/favicon-'.$url);
$key = md5('/v2/avatars/favicon-' . $url); $type = 'png';
$type = 'png'; $cache = new Cache(new Filesystem('/storage/cache/app-0')); // Limit file number or size
$cache = new Cache(new Filesystem('/storage/cache/app-0')); // Limit file number or size $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
if($data) { if ($data) {
$response $response
->setContentType('image/png') ->setContentType('image/png')
->addHeader('Expires', $date) ->addHeader('Expires', $date)
@ -234,11 +230,11 @@ $utopia->get('/v1/avatars/favicon')
CURLOPT_USERAGENT => sprintf(APP_USERAGENT, $version), CURLOPT_USERAGENT => sprintf(APP_USERAGENT, $version),
]); ]);
$html = curl_exec($curl); $html = curl_exec($curl);
curl_close($curl); curl_close($curl);
if(!$html) { if (!$html) {
throw new Exception('Failed to fetch remote URL', 404); throw new Exception('Failed to fetch remote URL', 404);
} }
@ -246,16 +242,16 @@ $utopia->get('/v1/avatars/favicon')
$doc->strictErrorChecking = false; $doc->strictErrorChecking = false;
@$doc->loadHTML($html); @$doc->loadHTML($html);
$links = $doc->getElementsByTagName('link'); $links = $doc->getElementsByTagName('link');
$outputHref = ''; $outputHref = '';
$outputExt = ''; $outputExt = '';
$space = 0; $space = 0;
foreach ($links as $link) { /* @var $link DOMElement */ foreach ($links as $link) { /* @var $link DOMElement */
$href = $link->getAttribute('href'); $href = $link->getAttribute('href');
$rel = $link->getAttribute('rel'); $rel = $link->getAttribute('rel');
$sizes = $link->getAttribute('sizes'); $sizes = $link->getAttribute('sizes');
$absolute = unparse_url(array_merge(parse_url($url), parse_url($href))); $absolute = unparse_url(array_merge(parse_url($url), parse_url($href)));
switch (strtolower($rel)) { switch (strtolower($rel)) {
case 'icon': case 'icon':
@ -270,13 +266,13 @@ $utopia->get('/v1/avatars/favicon')
case 'jpeg': case 'jpeg':
$size = explode('x', strtolower($sizes)); $size = explode('x', strtolower($sizes));
$sizeWidth = (isset($size[0])) ? (int)$size[0] : 0; $sizeWidth = (isset($size[0])) ? (int) $size[0] : 0;
$sizeHeight = (isset($size[1])) ? (int)$size[1] : 0; $sizeHeight = (isset($size[1])) ? (int) $size[1] : 0;
if(($sizeWidth * $sizeHeight) >= $space) { if (($sizeWidth * $sizeHeight) >= $space) {
$space = $sizeWidth * $sizeHeight; $space = $sizeWidth * $sizeHeight;
$outputHref = $absolute; $outputHref = $absolute;
$outputExt = $ext; $outputExt = $ext;
} }
break; break;
@ -286,17 +282,17 @@ $utopia->get('/v1/avatars/favicon')
} }
} }
if(empty($outputHref) || empty($outputExt)) { if (empty($outputHref) || empty($outputExt)) {
$default = parse_url($url); $default = parse_url($url);
$outputHref = $default['scheme'] . '://' . $default['host'] . '/favicon.ico'; $outputHref = $default['scheme'].'://'.$default['host'].'/favicon.ico';
$outputExt = 'ico'; $outputExt = 'ico';
} }
if('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files if ('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files
$data = @file_get_contents($outputHref, false); $data = @file_get_contents($outputHref, false);
if(empty($data) || (mb_substr($data, 0, 5) === '<html') || mb_substr($data, 0, 5) === '<!doc') { if (empty($data) || (mb_substr($data, 0, 5) === '<html') || mb_substr($data, 0, 5) === '<!doc') {
throw new Exception('Favicon not found', 404); throw new Exception('Favicon not found', 404);
} }
@ -312,13 +308,13 @@ $utopia->get('/v1/avatars/favicon')
$fetch = @file_get_contents($outputHref, false); $fetch = @file_get_contents($outputHref, false);
if(!$fetch) { if (!$fetch) {
throw new Exception('Icon not found', 404); throw new Exception('Icon not found', 404);
} }
$resize = new Resize($fetch); $resize = new Resize($fetch);
$resize->crop((int)$width, (int)$height); $resize->crop((int) $width, (int) $height);
$output = (empty($output)) ? $type : $output; $output = (empty($output)) ? $type : $output;
@ -352,8 +348,7 @@ $utopia->get('/v1/avatars/qr')
->label('sdk.method', 'getQR') ->label('sdk.method', 'getQR')
->label('sdk.description', 'Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.') ->label('sdk.description', 'Converts a given plain text to a QR code image. You can use the query parameters to change the size and style of the resulting image.')
->action( ->action(
function($text, $size, $margin, $download) use ($response) function ($text, $size, $margin, $download) use ($response) {
{
$renderer = new ImageRenderer( $renderer = new ImageRenderer(
new RendererStyle($size, $margin), new RendererStyle($size, $margin),
new ImagickImageBackEnd('png', 100) new ImagickImageBackEnd('png', 100)
@ -361,12 +356,12 @@ $utopia->get('/v1/avatars/qr')
$writer = new Writer($renderer); $writer = new Writer($renderer);
if($download) { if ($download) {
$response->addHeader('Content-Disposition', 'attachment; filename="qr.png"'); $response->addHeader('Content-Disposition', 'attachment; filename="qr.png"');
} }
$response $response
->addHeader('Expires', date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->addHeader('Expires', date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
->setContentType('image/png') ->setContentType('image/png')
->send('', $writer->writeString($text)) ->send('', $writer->writeString($text))
; ;
@ -374,43 +369,39 @@ $utopia->get('/v1/avatars/qr')
} }
); );
function unparse_url($parsed_url, $ommit = array())
function unparse_url( $parsed_url , $ommit = array( ) )
{ {
if(isset($parsed_url['path']) && mb_substr($parsed_url['path'], 0, 1) !== '/') { if (isset($parsed_url['path']) && mb_substr($parsed_url['path'], 0, 1) !== '/') {
$parsed_url['path'] = '/' . $parsed_url['path']; $parsed_url['path'] = '/'.$parsed_url['path'];
} }
$p = array(); $p = array();
$p['scheme'] = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : ''; $p['scheme'] = isset($parsed_url['scheme']) ? $parsed_url['scheme'].'://' : '';
$p['host'] = isset( $parsed_url['host'] ) ? $parsed_url['host'] : ''; $p['host'] = isset($parsed_url['host']) ? $parsed_url['host'] : '';
$p['port'] = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : ''; $p['port'] = isset($parsed_url['port']) ? ':'.$parsed_url['port'] : '';
$p['user'] = isset( $parsed_url['user'] ) ? $parsed_url['user'] : ''; $p['user'] = isset($parsed_url['user']) ? $parsed_url['user'] : '';
$p['pass'] = isset( $parsed_url['pass'] ) ? ':' . $parsed_url['pass'] : ''; $p['pass'] = isset($parsed_url['pass']) ? ':'.$parsed_url['pass'] : '';
$p['pass'] = ( $p['user'] || $p['pass'] ) ? $p['pass']."@" : ''; $p['pass'] = ($p['user'] || $p['pass']) ? $p['pass'].'@' : '';
$p['path'] = isset( $parsed_url['path'] ) ? $parsed_url['path'] : ''; $p['path'] = isset($parsed_url['path']) ? $parsed_url['path'] : '';
$p['query'] = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : ''; $p['query'] = isset($parsed_url['query']) ? '?'.$parsed_url['query'] : '';
$p['fragment'] = isset( $parsed_url['fragment'] ) ? '#' . $parsed_url['fragment'] : ''; $p['fragment'] = isset($parsed_url['fragment']) ? '#'.$parsed_url['fragment'] : '';
if ( $ommit ) if ($ommit) {
{ foreach ($ommit as $key) {
foreach ( $ommit as $key ) if (isset($p[ $key ])) {
{
if ( isset( $p[ $key ] ) )
{
$p[ $key ] = ''; $p[ $key ] = '';
} }
} }
} }
return $p['scheme'].$p['user'].$p['pass'].$p['host'].$p['port'].$p['path'].$p['query'].$p['fragment']; return $p['scheme'].$p['user'].$p['pass'].$p['host'].$p['port'].$p['path'].$p['query'].$p['fragment'];
} }

View file

@ -1,4 +1,5 @@
<?php <?php
include_once 'shared/web.php'; include_once 'shared/web.php';
global $utopia, $response, $request, $layout, $version, $providers; global $utopia, $response, $request, $layout, $version, $providers;
@ -14,14 +15,14 @@ $utopia->init(function () use ($layout, $utopia) {
; ;
}); });
$utopia->shutdown(function() use ($utopia, $response, $request, $layout, $version) { $utopia->shutdown(function () use ($utopia, $response, $request, $layout, $version) {
$header = new View(__DIR__ . '/../views/console/comps/header.phtml'); $header = new View(__DIR__.'/../views/console/comps/header.phtml');
$footer = new View(__DIR__ . '/../views/console/comps/footer.phtml'); $footer = new View(__DIR__.'/../views/console/comps/footer.phtml');
$footer $footer
->setParam('home', $request->getServer('_APP_HOME', '')) ->setParam('home', $request->getServer('_APP_HOME', ''))
; ;
$layout $layout
->setParam('header', [$header]) ->setParam('header', [$header])
->setParam('footer', [$footer]) ->setParam('footer', [$footer])
@ -42,50 +43,47 @@ $utopia->get('/error/:code')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'home') ->label('scope', 'home')
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false) ->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
->action(function($code) use ($layout) ->action(function ($code) use ($layout) {
{ $page = new View(__DIR__.'/../views/error.phtml');
$page = new View(__DIR__ . '/../views/error.phtml');
$page $page
->setParam('code', $code) ->setParam('code', $code)
; ;
$layout $layout
->setParam('title', APP_NAME . ' - Error') ->setParam('title', APP_NAME.' - Error')
->setParam('body', $page); ->setParam('body', $page);
}); });
$utopia->get('/console') $utopia->get('/console')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function() use ($layout, $request) ->action(function () use ($layout, $request) {
{ $page = new View(__DIR__.'/../views/console/index.phtml');
$page = new View(__DIR__ . '/../views/console/index.phtml');
$page $page
->setParam('home', $request->getServer('_APP_HOME', '')) ->setParam('home', $request->getServer('_APP_HOME', ''))
; ;
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
$utopia->get('/console/account') $utopia->get('/console/account')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function() use ($layout) ->action(function () use ($layout) {
{ $page = new View(__DIR__.'/../views/console/account/index.phtml');
$page = new View(__DIR__ . '/../views/console/account/index.phtml');
$cc = new View(__DIR__ . '/../views/console/forms/credit-card.phtml'); $cc = new View(__DIR__.'/../views/console/forms/credit-card.phtml');
$page $page
->setParam('cc', $cc) ->setParam('cc', $cc)
; ;
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.account.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.account.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -93,12 +91,11 @@ $utopia->get('/console/notifications')
->desc('Platform console notifications') ->desc('Platform console notifications')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function() use ($layout) ->action(function () use ($layout) {
{ $page = new View(__DIR__.'/../views/v1/console/notifications/index.phtml');
$page = new View(__DIR__ . '/../views/v1/console/notifications/index.phtml');
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.notifications.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.notifications.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -106,12 +103,11 @@ $utopia->get('/console/home')
->desc('Platform console project home') ->desc('Platform console project home')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function() use ($layout) ->action(function () use ($layout) {
{ $page = new View(__DIR__.'/../views/console/home/index.phtml');
$page = new View(__DIR__ . '/../views/console/home/index.phtml');
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.home.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.home.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -120,10 +116,10 @@ $utopia->get('/console/settings')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function () use ($layout) { ->action(function () use ($layout) {
$page = new View(__DIR__ . '/../views/console/settings/index.phtml'); $page = new View(__DIR__.'/../views/console/settings/index.phtml');
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.settings.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.settings.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -132,10 +128,10 @@ $utopia->get('/console/webhooks')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function () use ($layout) { ->action(function () use ($layout) {
$page = new View(__DIR__ . '/../views/console/webhooks/index.phtml'); $page = new View(__DIR__.'/../views/console/webhooks/index.phtml');
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.webhooks.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.webhooks.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -144,10 +140,10 @@ $utopia->get('/console/keys')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function () use ($layout) { ->action(function () use ($layout) {
$page = new View(__DIR__ . '/../views/console/keys/index.phtml'); $page = new View(__DIR__.'/../views/console/keys/index.phtml');
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.keys.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.keys.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -156,10 +152,10 @@ $utopia->get('/console/tasks')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function () use ($layout) { ->action(function () use ($layout) {
$page = new View(__DIR__ . '/../views/console/tasks/index.phtml'); $page = new View(__DIR__.'/../views/console/tasks/index.phtml');
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.tasks.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.tasks.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -167,12 +163,11 @@ $utopia->get('/console/database')
->desc('Platform console project settings') ->desc('Platform console project settings')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function() use ($layout) ->action(function () use ($layout) {
{ $page = new View(__DIR__.'/../views/console/database/index.phtml');
$page = new View(__DIR__ . '/../views/console/database/index.phtml');
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.database.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.database.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -181,22 +176,21 @@ $utopia->get('/console/database/collection')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->param('id', '', function () {return new UID();}, 'Collection unique ID.') ->param('id', '', function () {return new UID();}, 'Collection unique ID.')
->action(function($id) use ($layout, $projectDB) ->action(function ($id) use ($layout, $projectDB) {
{
$collection = $projectDB->getDocument($id, false); $collection = $projectDB->getDocument($id, false);
if(empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { if (empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
$page = new View(__DIR__ . '/../views/console/database/collection.phtml'); $page = new View(__DIR__.'/../views/console/database/collection.phtml');
$page $page
->setParam('collection', $collection->getArrayCopy()) ->setParam('collection', $collection->getArrayCopy())
; ;
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.database.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.database.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -204,12 +198,11 @@ $utopia->get('/console/storage')
->desc('Platform console project settings') ->desc('Platform console project settings')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function() use ($layout) ->action(function () use ($layout) {
{ $page = new View(__DIR__.'/../views/console/storage/index.phtml');
$page = new View(__DIR__ . '/../views/console/storage/index.phtml');
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.storage.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.storage.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -217,14 +210,13 @@ $utopia->get('/console/users')
->desc('Platform console project settings') ->desc('Platform console project settings')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function() use ($layout, $providers) ->action(function () use ($layout, $providers) {
{ $page = new View(__DIR__.'/../views/console/users/index.phtml');
$page = new View(__DIR__ . '/../views/console/users/index.phtml');
$page->setParam('providers', $providers); $page->setParam('providers', $providers);
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.users.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.users.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -232,11 +224,10 @@ $utopia->get('/console/users/view')
->desc('Platform console project user') ->desc('Platform console project user')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'console') ->label('scope', 'console')
->action(function() use ($layout, $providers) ->action(function () use ($layout, $providers) {
{ $page = new View(__DIR__.'/../views/console/users/view.phtml');
$page = new View(__DIR__ . '/../views/console/users/view.phtml');
$layout $layout
->setParam('title', APP_NAME . ' - ' . Locale::getText('console.users.title')) ->setParam('title', APP_NAME.' - '.Locale::getText('console.users.title'))
->setParam('body', $page); ->setParam('body', $page);
}); });

View file

@ -25,8 +25,7 @@ $isDev = (App::ENV_TYPE_PRODUCTION !== $utopia->getEnv());
$utopia $utopia
->shutdown( ->shutdown(
function() use ($request, $response, $projectDB, $register, &$output) function () use ($request, $response, $projectDB, $register, &$output) {
{
/*$lastModified = $projectDB->lastModified(); /*$lastModified = $projectDB->lastModified();
$etag = md5(json_encode($output)); $etag = md5(json_encode($output));
$ifNotModelledSince = strtotime($request->getServer('HTTP_IF_MODIFIED_SINCE', 'now')); $ifNotModelledSince = strtotime($request->getServer('HTTP_IF_MODIFIED_SINCE', 'now'));
@ -64,11 +63,10 @@ $utopia->get('/v1/database')
->label('sdk.description', 'Get a list of all the user collections. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project collections. [Learn more about different API modes](/docs/modes).') ->label('sdk.description', 'Get a list of all the user collections. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project collections. [Learn more about different API modes](/docs/modes).')
->param('search', '', function () {return new Text(256);}, 'Search term to filter your list results.', true) ->param('search', '', function () {return new Text(256);}, 'Search term to filter your list results.', true)
->param('limit', 25, function () {return new Range(0, 100);}, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('limit', 25, function () {return new Range(0, 100);}, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0 , function () {return new Range(0, 40000);}, 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('offset', 0, function () {return new Range(0, 40000);}, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('orderType', 'ASC', function () {return new WhiteList(['ASC', 'DESC']);}, 'Order result by ASC or DESC order.', true) ->param('orderType', 'ASC', function () {return new WhiteList(['ASC', 'DESC']);}, 'Order result by ASC or DESC order.', true)
->action( ->action(
function($search, $limit, $offset, $orderType) use ($response, $projectDB) function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
{
/*$vl = new Structure($projectDB); /*$vl = new Structure($projectDB);
var_dump($vl->isValid(new Document([ var_dump($vl->isValid(new Document([
@ -96,7 +94,7 @@ $utopia->get('/v1/database')
'orderCast' => 'string', 'orderCast' => 'string',
'search' => $search, 'search' => $search,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_COLLECTIONS '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS,
], ],
]); ]);
@ -112,10 +110,10 @@ $utopia->get('/v1/database/:collectionId')
->label('sdk.description', 'Get collection by its unique ID. This endpoint response returns a JSON object with the collection metadata.') ->label('sdk.description', 'Get collection by its unique ID. This endpoint response returns a JSON object with the collection metadata.')
->param('collectionId', '', function () {return new UID();}, 'Collection unique ID.') ->param('collectionId', '', function () {return new UID();}, 'Collection unique ID.')
->action( ->action(
function($collectionId) use ($response, $projectDB) { function ($collectionId) use ($response, $projectDB) {
$collection = $projectDB->getDocument($collectionId, false); $collection = $projectDB->getDocument($collectionId, false);
if(empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { if (empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
@ -135,14 +133,13 @@ $utopia->post('/v1/database')
->param('write', [], function () {return new ArrayList(new Text(64));}, 'An array of write permissions. [Learn more about permissions and roles](/docs/permissions).', true) ->param('write', [], function () {return new ArrayList(new Text(64));}, 'An array of write permissions. [Learn more about permissions and roles](/docs/permissions).', true)
->param('rules', [], function () use ($projectDB) {return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES]));}, 'Array of collection structure rules. Each rule define a collection field name, data type and validation', true) ->param('rules', [], function () use ($projectDB) {return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES]));}, 'Array of collection structure rules. Each rule define a collection field name, data type and validation', true)
->action( ->action(
function($name, $read, $write, $rules) use ($response, $projectDB, &$output, $webhook, $audit, $isDev) function ($name, $read, $write, $rules) use ($response, $projectDB, &$output, $webhook, $audit, $isDev) {
{
try { try {
$data = $projectDB->createDocument([ $data = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS,
'name' => $name, 'name' => $name,
'dateCreated' => time(), 'dateCreated' => time(),
'dateUpdated' => time(), 'dateUpdated' => time(),
'structure' => true, 'structure' => true,
'$permissions' => [ '$permissions' => [
'read' => $read, 'read' => $read,
@ -150,14 +147,11 @@ $utopia->post('/v1/database')
], ],
'rules' => $rules, 'rules' => $rules,
]); ]);
} } catch (AuthorizationException $exception) {
catch (AuthorizationException $exception) {
throw new Exception('Unauthorized action', 401); throw new Exception('Unauthorized action', 401);
} } catch (StructureException $exception) {
catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400);
throw new Exception('Bad structure. ' . $exception->getMessage(), 400); } catch (\Exception $exception) {
}
catch (\Exception $exception) {
throw new Exception('Failed saving document to DB', 500); throw new Exception('Failed saving document to DB', 500);
} }
@ -169,11 +163,11 @@ $utopia->post('/v1/database')
$audit $audit
->setParam('event', 'database.collections.create') ->setParam('event', 'database.collections.create')
->setParam('resource', 'database/collection/' . $data['$uid']) ->setParam('resource', 'database/collection/'.$data['$uid'])
->setParam('data', $data) ->setParam('data', $data)
; ;
/** /*
* View * View
*/ */
$response $response
@ -195,11 +189,10 @@ $utopia->put('/v1/database/:collectionId')
->param('write', [], function () {return new ArrayList(new Text(64));}, 'An array of write permissions. [Learn more about permissions and roles](/docs/permissions).', true) ->param('write', [], function () {return new ArrayList(new Text(64));}, 'An array of write permissions. [Learn more about permissions and roles](/docs/permissions).', true)
->param('rules', [], function () use ($projectDB) {return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES]));}, 'Array of collection structure rules. Each rule define a collection field name, data type and validation', true) ->param('rules', [], function () use ($projectDB) {return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES]));}, 'Array of collection structure rules. Each rule define a collection field name, data type and validation', true)
->action( ->action(
function($collectionId, $name, $read, $write, $rules) use ($response, $projectDB) function ($collectionId, $name, $read, $write, $rules) use ($response, $projectDB) {
{
$collection = $projectDB->getDocument($collectionId, false); $collection = $projectDB->getDocument($collectionId, false);
if(empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { if (empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
@ -214,7 +207,7 @@ $utopia->put('/v1/database/:collectionId')
'rules' => $rules, 'rules' => $rules,
])); ]));
if(false === $collection) { if (false === $collection) {
throw new Exception('Failed saving collection to DB', 500); throw new Exception('Failed saving collection to DB', 500);
} }
@ -230,20 +223,20 @@ $utopia->delete('/v1/database/:collectionId')
->label('sdk.description', 'Delete a collection by its unique ID. Only users with write permissions have access to delete this resource.') ->label('sdk.description', 'Delete a collection by its unique ID. Only users with write permissions have access to delete this resource.')
->param('collectionId', '', function () {return new UID();}, 'Collection unique ID.') ->param('collectionId', '', function () {return new UID();}, 'Collection unique ID.')
->action( ->action(
function($collectionId) use ($response, $projectDB, $audit) { function ($collectionId) use ($response, $projectDB, $audit) {
$collection = $projectDB->getDocument($collectionId, false); $collection = $projectDB->getDocument($collectionId, false);
if(empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { if (empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
if(!$projectDB->deleteDocument($collectionId)) { if (!$projectDB->deleteDocument($collectionId)) {
throw new Exception('Failed to remove collection from DB', 500); throw new Exception('Failed to remove collection from DB', 500);
} }
$audit $audit
->setParam('event', 'database.collections.create') ->setParam('event', 'database.collections.create')
->setParam('resource', 'database/collection/' . $collection->getUid()) ->setParam('resource', 'database/collection/'.$collection->getUid())
->setParam('data', $collection->getArrayCopy()) // Audit document in case of malicious or disastrous action ->setParam('data', $collection->getArrayCopy()) // Audit document in case of malicious or disastrous action
; ;
@ -268,11 +261,10 @@ $utopia->get('/v1/database/:collectionId/documents')
->param('first', 0, function () {return new Range(0, 1);}, 'Return only first document. Pass 1 for true or 0 for false. The default value is 0.', true) ->param('first', 0, function () {return new Range(0, 1);}, 'Return only first document. Pass 1 for true or 0 for false. The default value is 0.', true)
->param('last', 0, function () {return new Range(0, 1);}, 'Return only last document. Pass 1 for true or 0 for false. The default value is 0.', true) ->param('last', 0, function () {return new Range(0, 1);}, 'Return only last document. Pass 1 for true or 0 for false. The default value is 0.', true)
->action( ->action(
function($collectionId, $filters, $offset, $limit, $orderField, $orderType, $orderCast, $search, $first, $last) use ($response, $request, $projectDB, &$output, $isDev) function ($collectionId, $filters, $offset, $limit, $orderField, $orderType, $orderCast, $search, $first, $last) use ($response, $request, $projectDB, &$output, $isDev) {
{
$collection = $projectDB->getDocument($collectionId, $isDev); $collection = $projectDB->getDocument($collectionId, $isDev);
if(is_null($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { if (is_null($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
@ -283,18 +275,17 @@ $utopia->get('/v1/database/:collectionId/documents')
'orderType' => $orderType, 'orderType' => $orderType,
'orderCast' => $orderCast, 'orderCast' => $orderCast,
'search' => $search, 'search' => $search,
'first' => (bool)$first, 'first' => (bool) $first,
'last' => (bool)$last, 'last' => (bool) $last,
'filters' => array_merge($filters, [ 'filters' => array_merge($filters, [
'$collection=' . $collectionId '$collection='.$collectionId,
]), ]),
]); ]);
if($first || $last) { if ($first || $last) {
$response->json((!empty($list) ? $list->getArrayCopy() : [])); $response->json((!empty($list) ? $list->getArrayCopy() : []));
} } else {
else { if ($isDev) {
if($isDev) {
$collection $collection
->setAttribute('debug', $projectDB->getDebug()) ->setAttribute('debug', $projectDB->getDebug())
->setAttribute('limit', $limit) ->setAttribute('limit', $limit)
@ -311,7 +302,7 @@ $utopia->get('/v1/database/:collectionId/documents')
->setAttribute('documents', $list) ->setAttribute('documents', $list)
; ;
/** /*
* View * View
*/ */
$response->json($collection->getArrayCopy(/*['$uid', '$collection', 'name', 'documents']*/[], ['rules'])); $response->json($collection->getArrayCopy(/*['$uid', '$collection', 'name', 'documents']*/[], ['rules']));
@ -328,9 +319,8 @@ $utopia->get('/v1/database/:collectionId/documents/:documentId')
->param('collectionId', null, function () {return new UID();}, 'Collection unique ID') ->param('collectionId', null, function () {return new UID();}, 'Collection unique ID')
->param('documentId', null, function () {return new UID();}, 'Document unique ID') ->param('documentId', null, function () {return new UID();}, 'Document unique ID')
->action( ->action(
function($collectionId, $documentId) use ($response, $request, $projectDB, &$output, $isDev) function ($collectionId, $documentId) use ($response, $request, $projectDB, &$output, $isDev) {
{ $document = $projectDB->getDocument($documentId, $isDev);
$document = $projectDB->getDocument($documentId, $isDev);
$collection = $projectDB->getDocument($collectionId, $isDev); $collection = $projectDB->getDocument($collectionId, $isDev);
if (empty($document->getArrayCopy()) || $document->getCollection() != $collection->getUid()) { // Check empty if (empty($document->getArrayCopy()) || $document->getCollection() != $collection->getUid()) { // Check empty
@ -341,25 +331,24 @@ $utopia->get('/v1/database/:collectionId/documents/:documentId')
$paths = explode('/', $request->getParam('q', '')); $paths = explode('/', $request->getParam('q', ''));
$paths = array_slice($paths, 6, count($paths)); $paths = array_slice($paths, 6, count($paths));
if(count($paths) > 0) { if (count($paths) > 0) {
if(count($paths) % 2 == 1) { if (count($paths) % 2 == 1) {
$output = $document->getAttribute(implode('.', $paths)); $output = $document->getAttribute(implode('.', $paths));
} } else {
else { $id = (int) array_pop($paths);
$id = (int)array_pop($paths);
$output = $document->search('$uid', $id, $document->getAttribute(implode('.', $paths))); $output = $document->search('$uid', $id, $document->getAttribute(implode('.', $paths)));
} }
$output = ($output instanceof Document) ? $output->getArrayCopy() : $output; $output = ($output instanceof Document) ? $output->getArrayCopy() : $output;
var_dump($output); var_dump($output);
if(!is_array($output)) { if (!is_array($output)) {
throw new Exception('No document found', 404); throw new Exception('No document found', 404);
} }
} }
/** /*
* View * View
*/ */
$response->json($output); $response->json($output);
@ -381,19 +370,18 @@ $utopia->post('/v1/database/:collectionId/documents')
->param('parentProperty', '', function () {return new Key();}, 'Parent document property name. Use when you want your new document to be a child of a parent document.', true) ->param('parentProperty', '', function () {return new Key();}, 'Parent document property name. Use when you want your new document to be a child of a parent document.', true)
->param('parentPropertyType', Document::SET_TYPE_ASSIGN, function () {return new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND]);}, 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true) ->param('parentPropertyType', Document::SET_TYPE_ASSIGN, function () {return new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND]);}, 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true)
->action( ->action(
function($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType) use ($response, $projectDB, &$output, $webhook, $audit, $isDev) function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType) use ($response, $projectDB, &$output, $webhook, $audit, $isDev) {
{ if (empty($data)) {
if(empty($data)) {
throw new Exception('Missing payload', 400); throw new Exception('Missing payload', 400);
} }
if(isset($data['$uid'])) { if (isset($data['$uid'])) {
throw new Exception('$uid is not allowed for creating new documents, try update instead', 400); throw new Exception('$uid is not allowed for creating new documents, try update instead', 400);
} }
$collection = $projectDB->getDocument($collectionId/*, $isDev*/); $collection = $projectDB->getDocument($collectionId/*, $isDev*/);
if(is_null($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { if (is_null($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
@ -404,14 +392,14 @@ $utopia->post('/v1/database/:collectionId/documents')
// Read parent document + validate not 404 + validate read / write permission like patch method // Read parent document + validate not 404 + validate read / write permission like patch method
// Add payload to parent document property // Add payload to parent document property
if((!empty($parentDocument)) && (!empty($parentProperty))) { if ((!empty($parentDocument)) && (!empty($parentProperty))) {
$parentDocument = $projectDB->getDocument($parentDocument); $parentDocument = $projectDB->getDocument($parentDocument);
if (empty($parentDocument->getArrayCopy())) { // Check empty if (empty($parentDocument->getArrayCopy())) { // Check empty
throw new Exception('No parent document found', 404); throw new Exception('No parent document found', 404);
} }
/** /*
* 1. Check child has valid structure, * 1. Check child has valid structure,
* 2. Check user have write permission for parent document * 2. Check user have write permission for parent document
* 3. Assign parent data (including child) to $data * 3. Assign parent data (including child) to $data
@ -422,13 +410,13 @@ $utopia->post('/v1/database/:collectionId/documents')
$structure = new Structure($projectDB); $structure = new Structure($projectDB);
if(!$structure->isValid($new)) { if (!$structure->isValid($new)) {
throw new Exception('Invalid data structure: ' . $structure->getDescription(), 400); throw new Exception('Invalid data structure: '.$structure->getDescription(), 400);
} }
$authorization = new Authorization($parentDocument, 'write'); $authorization = new Authorization($parentDocument, 'write');
if(!$authorization->isValid($new->getPermissions())) { if (!$authorization->isValid($new->getPermissions())) {
throw new Exception('Unauthorized action', 401); throw new Exception('Unauthorized action', 401);
} }
@ -440,15 +428,12 @@ $utopia->post('/v1/database/:collectionId/documents')
try { try {
$data = $projectDB->createDocument($data); $data = $projectDB->createDocument($data);
} } catch (AuthorizationException $exception) {
catch (AuthorizationException $exception) {
throw new Exception('Unauthorized action', 401); throw new Exception('Unauthorized action', 401);
} } catch (StructureException $exception) {
catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400);
throw new Exception('Bad structure. ' . $exception->getMessage(), 400); } catch (\Exception $exception) {
} throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500);
catch (\Exception $exception) {
throw new Exception('Failed saving document to DB' . $exception->getMessage(), 500);
} }
$data = $data->getArrayCopy(); $data = $data->getArrayCopy();
@ -459,11 +444,11 @@ $utopia->post('/v1/database/:collectionId/documents')
$audit $audit
->setParam('event', 'database.documents.create') ->setParam('event', 'database.documents.create')
->setParam('resource', 'database/document/' . $data['$uid']) ->setParam('resource', 'database/document/'.$data['$uid'])
->setParam('data', $data) ->setParam('data', $data)
; ;
/** /*
* View * View
*/ */
$response $response
@ -485,12 +470,11 @@ $utopia->patch('/v1/database/:collectionId/documents/:documentId')
->param('read', [], function () {return new ArrayList(new Text(64));}, 'An array of read permissions. [Learn more about permissions and roles](/docs/permissions).', true) ->param('read', [], function () {return new ArrayList(new Text(64));}, 'An array of read permissions. [Learn more about permissions and roles](/docs/permissions).', true)
->param('write', [], function () {return new ArrayList(new Text(64));}, 'An array of write permissions. [Learn more about permissions and roles](/docs/permissions).', true) ->param('write', [], function () {return new ArrayList(new Text(64));}, 'An array of write permissions. [Learn more about permissions and roles](/docs/permissions).', true)
->action( ->action(
function($collectionId, $documentId, $data, $read, $write) use ($response, $projectDB, &$output, $webhook, $audit, $isDev) function ($collectionId, $documentId, $data, $read, $write) use ($response, $projectDB, &$output, $webhook, $audit, $isDev) {
{
$collection = $projectDB->getDocument($collectionId/*, $isDev*/); $collection = $projectDB->getDocument($collectionId/*, $isDev*/);
$document = $projectDB->getDocument($documentId, $isDev); $document = $projectDB->getDocument($documentId, $isDev);
if(is_null($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { if (is_null($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
@ -500,32 +484,29 @@ $utopia->patch('/v1/database/:collectionId/documents/:documentId')
//TODO check merge read write permissions //TODO check merge read write permissions
if(!empty($read)) { // Overwrite permissions only when passed if (!empty($read)) { // Overwrite permissions only when passed
$data['$permissions']['read'] = $read; $data['$permissions']['read'] = $read;
} }
if(!empty($write)) { // Overwrite permissions only when passed if (!empty($write)) { // Overwrite permissions only when passed
$data['$permissions']['write'] = $read; $data['$permissions']['write'] = $read;
} }
$data = array_merge($document->getArrayCopy(), $data); $data = array_merge($document->getArrayCopy(), $data);
$data['$collection'] = $collection->getUid(); // Make sure user don't switch collectionID $data['$collection'] = $collection->getUid(); // Make sure user don't switch collectionID
$data['$uid'] = $document->getUid(); // Make sure user don't switch document unique ID $data['$uid'] = $document->getUid(); // Make sure user don't switch document unique ID
if(empty($data)) { if (empty($data)) {
throw new Exception('Missing payload', 400); throw new Exception('Missing payload', 400);
} }
try { try {
$data = $projectDB->updateDocument($data); $data = $projectDB->updateDocument($data);
} } catch (AuthorizationException $exception) {
catch (AuthorizationException $exception) {
throw new Exception('Unauthorized action', 401); throw new Exception('Unauthorized action', 401);
} } catch (StructureException $exception) {
catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400);
throw new Exception('Bad structure. ' . $exception->getMessage(), 400); } catch (\Exception $exception) {
}
catch (\Exception $exception) {
throw new Exception('Failed saving document to DB', 500); throw new Exception('Failed saving document to DB', 500);
} }
@ -537,11 +518,11 @@ $utopia->patch('/v1/database/:collectionId/documents/:documentId')
$audit $audit
->setParam('event', 'database.documents.patch') ->setParam('event', 'database.documents.patch')
->setParam('resource', 'database/document/' . $data['$uid']) ->setParam('resource', 'database/document/'.$data['$uid'])
->setParam('data', $data) ->setParam('data', $data)
; ;
/** /*
* View * View
*/ */
$response->json($data); $response->json($data);
@ -557,38 +538,35 @@ $utopia->delete('/v1/database/:collectionId/documents/:documentId')
->param('collectionId', null, function () {return new UID();}, 'Collection unique ID') ->param('collectionId', null, function () {return new UID();}, 'Collection unique ID')
->param('documentId', null, function () {return new UID();}, 'Document unique ID') ->param('documentId', null, function () {return new UID();}, 'Document unique ID')
->action( ->action(
function($collectionId, $documentId) use ($response, $projectDB, $audit, $isDev) { function ($collectionId, $documentId) use ($response, $projectDB, $audit, $isDev) {
$collection = $projectDB->getDocument($collectionId, $isDev); $collection = $projectDB->getDocument($collectionId, $isDev);
$document = $projectDB->getDocument($documentId, $isDev); $document = $projectDB->getDocument($documentId, $isDev);
if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty
throw new Exception('No document found', 404); throw new Exception('No document found', 404);
} }
if(is_null($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { if (is_null($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404); throw new Exception('Collection not found', 404);
} }
try { try {
$projectDB->deleteDocument($documentId); $projectDB->deleteDocument($documentId);
} } catch (AuthorizationException $exception) {
catch (AuthorizationException $exception) {
throw new Exception('Unauthorized action', 401); throw new Exception('Unauthorized action', 401);
} } catch (StructureException $exception) {
catch (StructureException $exception) { throw new Exception('Bad structure. '.$exception->getMessage(), 400);
throw new Exception('Bad structure. ' . $exception->getMessage(), 400); } catch (\Exception $exception) {
}
catch (\Exception $exception) {
throw new Exception('Failed to remove document from DB', 500); throw new Exception('Failed to remove document from DB', 500);
} }
$audit $audit
->setParam('event', 'database.documents.delete') ->setParam('event', 'database.documents.delete')
->setParam('resource', 'database/document/' . $documentId) ->setParam('resource', 'database/document/'.$documentId)
->setParam('data', $document->getArrayCopy()) // Audit document in case of malicious or disastrous action ->setParam('data', $document->getArrayCopy()) // Audit document in case of malicious or disastrous action
; ;
$response->noContent(); $response->noContent();
} }
); );

View file

@ -14,8 +14,7 @@ $utopia->get('/v1/health')
->label('sdk.method', 'getDB') ->label('sdk.method', 'getDB')
->label('docs', false) ->label('docs', false)
->action( ->action(
function() use ($response, $register) function () use ($response, $register) {
{
$response->json(array('OK')); $response->json(array('OK'));
} }
); );
@ -27,8 +26,7 @@ $utopia->get('/v1/health/db')
->label('sdk.method', 'getDB') ->label('sdk.method', 'getDB')
->label('docs', false) ->label('docs', false)
->action( ->action(
function() use ($response, $register) function () use ($response, $register) {
{
$register->get('db'); /* @var $db PDO */ $register->get('db'); /* @var $db PDO */
$response->json(array('OK')); $response->json(array('OK'));
@ -42,8 +40,7 @@ $utopia->get('/v1/health/cache')
->label('sdk.method', 'getCache') ->label('sdk.method', 'getCache')
->label('docs', false) ->label('docs', false)
->action( ->action(
function() use ($response, $register) function () use ($response, $register) {
{
$register->get('cache'); /* @var $cache Predis\Client */ $register->get('cache'); /* @var $cache Predis\Client */
$response->json(array('OK')); $response->json(array('OK'));
@ -57,9 +54,8 @@ $utopia->get('/v1/health/time')
->label('sdk.method', 'getTime') ->label('sdk.method', 'getTime')
->label('docs', false) ->label('docs', false)
->action( ->action(
function() use ($response) function () use ($response) {
{ /*
/**
* Code from: @see https://www.beliefmedia.com.au/query-ntp-time-server * Code from: @see https://www.beliefmedia.com.au/query-ntp-time-server
*/ */
$host = 'time.google.com'; // https://developers.google.com/time/ $host = 'time.google.com'; // https://developers.google.com/time/
@ -71,7 +67,7 @@ $utopia->get('/v1/health/time')
socket_connect($sock, $host, 123); socket_connect($sock, $host, 123);
/* Send request */ /* Send request */
$msg = "\010" . str_repeat("\0", 47); $msg = "\010".str_repeat("\0", 47);
socket_send($sock, $msg, strlen($msg), 0); socket_send($sock, $msg, strlen($msg), 0);
@ -89,7 +85,7 @@ $utopia->get('/v1/health/time')
$diff = ($timestamp - time()); $diff = ($timestamp - time());
if($diff > $gap || $diff < ($gap * -1)) { if ($diff > $gap || $diff < ($gap * -1)) {
throw new Exception('Server time gaps detected'); throw new Exception('Server time gaps detected');
} }
@ -104,8 +100,7 @@ $utopia->get('/v1/health/webhooks')
->label('sdk.method', 'getWebhooks') ->label('sdk.method', 'getWebhooks')
->label('docs', false) ->label('docs', false)
->action( ->action(
function() use ($response) function () use ($response) {
{
$response->json(['size' => Resque::size('webhooks')]); $response->json(['size' => Resque::size('webhooks')]);
} }
); );
@ -117,15 +112,14 @@ $utopia->get('/v1/health/storage/local')
->label('sdk.method', 'getStorageLocal') ->label('sdk.method', 'getStorageLocal')
->label('docs', false) ->label('docs', false)
->action( ->action(
function() use ($response) function () use ($response) {
{
$device = new Local(); $device = new Local();
if(!is_readable($device->getRoot())) { if (!is_readable($device->getRoot())) {
throw new Exception('Device is not readable'); throw new Exception('Device is not readable');
} }
if(!is_writable($device->getRoot())) { if (!is_writable($device->getRoot())) {
throw new Exception('Device is not writable'); throw new Exception('Device is not writable');
} }
@ -140,8 +134,7 @@ $utopia->get('/v1/health/storage/anti-virus')
->label('sdk.method', 'getStorageAntiVirus') ->label('sdk.method', 'getStorageAntiVirus')
->label('docs', false) ->label('docs', false)
->action( ->action(
function() use ($response) function () use ($response) {
{
$antiVirus = new Network('clamav', 3310); $antiVirus = new Network('clamav', 3310);
$response->json([ $response->json([
@ -158,10 +151,9 @@ $utopia->get('/v1/health/stats')
->label('sdk.method', 'getStats') ->label('sdk.method', 'getStats')
->label('docs', false) ->label('docs', false)
->action( ->action(
function() use ($request, $response, $register, $project) function () use ($request, $response, $register, $project) {
{
$device = Storage::getDevice('local'); $device = Storage::getDevice('local');
$cache = $register->get('cache'); $cache = $register->get('cache');
$cacheStats = $cache->info(); $cacheStats = $cache->info();
@ -172,9 +164,9 @@ $utopia->get('/v1/health/stats')
'version' => shell_exec('nginx -v 2>&1'), 'version' => shell_exec('nginx -v 2>&1'),
], ],
'storage' => [ 'storage' => [
'used' => $device->human($device->getDirectorySize($device->getRoot() . '/')), 'used' => $device->human($device->getDirectorySize($device->getRoot().'/')),
'partitionTotal' => $device->human($device->getPartitionTotalSpace()), 'partitionTotal' => $device->human($device->getPartitionTotalSpace()),
'partitionFree' => $device->human($device->getPartitionFreeSpace()), 'partitionFree' => $device->human($device->getPartitionFreeSpace()),
], ],
'cache' => [ 'cache' => [
'uptime' => (isset($cacheStats['uptime_in_seconds'])) ? $cacheStats['uptime_in_seconds'] : 0, 'uptime' => (isset($cacheStats['uptime_in_seconds'])) ? $cacheStats['uptime_in_seconds'] : 0,
@ -185,7 +177,7 @@ $utopia->get('/v1/health/stats')
'memory_used_human' => (isset($cacheStats['used_memory_human'])) ? $cacheStats['used_memory_human'] : 0, 'memory_used_human' => (isset($cacheStats['used_memory_human'])) ? $cacheStats['used_memory_human'] : 0,
'memory_used_peak' => (isset($cacheStats['used_memory_peak'])) ? $cacheStats['used_memory_peak'] : 0, 'memory_used_peak' => (isset($cacheStats['used_memory_peak'])) ? $cacheStats['used_memory_peak'] : 0,
'memory_used_peak_human' => (isset($cacheStats['used_memory_peak_human'])) ? $cacheStats['used_memory_peak_human'] : 0, 'memory_used_peak_human' => (isset($cacheStats['used_memory_peak_human'])) ? $cacheStats['used_memory_peak_human'] : 0,
] ],
]); ]);
} }
); );

View file

@ -11,10 +11,10 @@ $layout
->setParam('title', APP_NAME) ->setParam('title', APP_NAME)
->setParam('description', Locale::getText('general.description')) ->setParam('description', Locale::getText('general.description'))
->setParam('class', 'home') ->setParam('class', 'home')
->setParam('header', [new View(__DIR__ . '/../views/home/comps/header.phtml')]) ->setParam('header', [new View(__DIR__.'/../views/home/comps/header.phtml')])
; ;
$utopia->shutdown(function() use ($utopia, $response, $request, $layout, $version, $env) { $utopia->shutdown(function () use ($utopia, $response, $request, $layout, $version, $env) {
$response->send($layout->render()); $response->send($layout->render());
}); });
@ -22,8 +22,7 @@ $utopia->get('/')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'home') ->label('scope', 'home')
->action( ->action(
function() use ($response) function () use ($response) {
{
$response->redirect('/auth/signin'); $response->redirect('/auth/signin');
} }
); );
@ -32,12 +31,11 @@ $utopia->get('/auth/signin')
->desc('Login page') ->desc('Login page')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'home') ->label('scope', 'home')
->action(function() use ($layout) ->action(function () use ($layout) {
{ $page = new View(__DIR__.'/../views/home/auth/signin.phtml');
$page = new View(__DIR__ . '/../views/home/auth/signin.phtml');
$layout $layout
->setParam('title', Locale::getText('home.auth.signin.title') . ' - ' . APP_NAME) ->setParam('title', Locale::getText('home.auth.signin.title').' - '.APP_NAME)
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -45,12 +43,11 @@ $utopia->get('/auth/signup')
->desc('Registration page') ->desc('Registration page')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'home') ->label('scope', 'home')
->action(function() use ($layout) ->action(function () use ($layout) {
{ $page = new View(__DIR__.'/../views/home/auth/signup.phtml');
$page = new View(__DIR__ . '/../views/home/auth/signup.phtml');
$layout $layout
->setParam('title', Locale::getText('home.auth.signup.title') . ' - ' . APP_NAME) ->setParam('title', Locale::getText('home.auth.signup.title').' - '.APP_NAME)
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -58,12 +55,11 @@ $utopia->get('/auth/recovery')
->desc('Password recovery page') ->desc('Password recovery page')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'home') ->label('scope', 'home')
->action(function() use ($request, $layout) ->action(function () use ($request, $layout) {
{ $page = new View(__DIR__.'/../views/home/auth/recovery.phtml');
$page = new View(__DIR__ . '/../views/home/auth/recovery.phtml');
$layout $layout
->setParam('title', Locale::getText('home.auth.recovery.title') . ' - ' . APP_NAME) ->setParam('title', Locale::getText('home.auth.recovery.title').' - '.APP_NAME)
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -71,12 +67,11 @@ $utopia->get('/auth/confirm')
->desc('Account confirmation page') ->desc('Account confirmation page')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'home') ->label('scope', 'home')
->action(function() use ($layout) ->action(function () use ($layout) {
{ $page = new View(__DIR__.'/../views/home/auth/confirm.phtml');
$page = new View(__DIR__ . '/../views/home/auth/confirm.phtml');
$layout $layout
->setParam('title', Locale::getText('home.auth.confirm.title') . ' - ' . APP_NAME) ->setParam('title', Locale::getText('home.auth.confirm.title').' - '.APP_NAME)
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -84,12 +79,11 @@ $utopia->get('/auth/join')
->desc('Account team join page') ->desc('Account team join page')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'home') ->label('scope', 'home')
->action(function() use ($layout) ->action(function () use ($layout) {
{ $page = new View(__DIR__.'/../views/home/auth/join.phtml');
$page = new View(__DIR__ . '/../views/home/auth/join.phtml');
$layout $layout
->setParam('title', Locale::getText('home.auth.join.title') . ' - ' . APP_NAME) ->setParam('title', Locale::getText('home.auth.join.title').' - '.APP_NAME)
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -97,12 +91,11 @@ $utopia->get('/auth/recovery/reset')
->desc('Password recovery page') ->desc('Password recovery page')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'home') ->label('scope', 'home')
->action(function() use ($layout) ->action(function () use ($layout) {
{ $page = new View(__DIR__.'/../views/home/auth/recovery/reset.phtml');
$page = new View(__DIR__ . '/../views/home/auth/recovery/reset.phtml');
$layout $layout
->setParam('title', Locale::getText('home.auth.reset.title') . ' - ' . APP_NAME) ->setParam('title', Locale::getText('home.auth.reset.title').' - '.APP_NAME)
->setParam('body', $page); ->setParam('body', $page);
}); });
@ -111,15 +104,14 @@ $utopia->get('/error/:code')
->label('permission', 'public') ->label('permission', 'public')
->label('scope', 'home') ->label('scope', 'home')
->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false) ->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false)
->action(function($code) use ($layout) ->action(function ($code) use ($layout) {
{ $page = new View(__DIR__.'/../views/error.phtml');
$page = new View(__DIR__ . '/../views/error.phtml');
$page $page
->setParam('code', $code) ->setParam('code', $code)
; ;
$layout $layout
->setParam('title', 'Error' . ' - ' . APP_NAME) ->setParam('title', 'Error'.' - '.APP_NAME)
->setParam('body', $page); ->setParam('body', $page);
}); });

View file

@ -13,19 +13,18 @@ $utopia->get('/v1/locale')
->label('sdk.method', 'getLocale') ->label('sdk.method', 'getLocale')
->label('sdk.description', 'Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in supported language.') ->label('sdk.description', 'Get the current user location based on IP. Returns an object with user country code, country name, continent name, continent code, ip address and suggested currency. You can use the locale header to get the data in supported language.')
->action( ->action(
function() use ($response, $request, $utopia) function () use ($response, $request, $utopia) {
{ $eu = include __DIR__.'/../config/eu.php';
$eu = include __DIR__ . '/../config/eu.php'; $currencies = include __DIR__.'/../config/currencies.php';
$currencies = include __DIR__ . '/../config/currencies.php'; $reader = new Reader(__DIR__.'/../db/GeoLite2/GeoLite2-Country.mmdb');
$reader = new Reader(__DIR__ . '/../db/GeoLite2/GeoLite2-Country.mmdb'); $output = [];
$output = []; $ip = $request->getIP();
$ip = $request->getIP(); $time = (60 * 60 * 24 * 45); // 45 days cache
$time = (60 * 60 * 24 * 45); // 45 days cache $countries = Locale::getText('countries');
$countries = Locale::getText('countries');
$continents = Locale::getText('continents'); $continents = Locale::getText('continents');
if(App::ENV_TYPE_PRODUCTION !== $utopia->getEnv()) { if (App::ENV_TYPE_PRODUCTION !== $utopia->getEnv()) {
$ip = '79.177.241.94'; $ip = '79.177.241.94';
} }
$output['ip'] = $ip; $output['ip'] = $ip;
@ -42,14 +41,13 @@ $utopia->get('/v1/locale')
$output['eu'] = (in_array($record->country->isoCode, $eu)) ? true : false; $output['eu'] = (in_array($record->country->isoCode, $eu)) ? true : false;
foreach ($currencies as $code => $element) { foreach ($currencies as $code => $element) {
if(isset($element['locations']) && isset($element['code']) && in_array($record->country->isoCode, $element['locations'])) { if (isset($element['locations']) && isset($element['code']) && in_array($record->country->isoCode, $element['locations'])) {
$currency = $element['code']; $currency = $element['code'];
} }
} }
$output['currency'] = $currency; $output['currency'] = $currency;
} } catch (\Exception $e) {
catch(\Exception $e) {
$output['countryCode'] = '--'; $output['countryCode'] = '--';
$output['country'] = Locale::getText('locale.country.unknown'); $output['country'] = Locale::getText('locale.country.unknown');
$output['continent'] = Locale::getText('locale.country.unknown'); $output['continent'] = Locale::getText('locale.country.unknown');
@ -59,8 +57,8 @@ $utopia->get('/v1/locale')
} }
$response $response
->addHeader('Cache-Control', 'public, max-age=' . $time) ->addHeader('Cache-Control', 'public, max-age='.$time)
->addHeader('Expires', date('D, d M Y H:i:s', time() + $time) . ' GMT') // 45 days cache ->addHeader('Expires', date('D, d M Y H:i:s', time() + $time).' GMT') // 45 days cache
->json($output); ->json($output);
} }
); );
@ -72,8 +70,7 @@ $utopia->get('/v1/locale/countries')
->label('sdk.method', 'getCountries') ->label('sdk.method', 'getCountries')
->label('sdk.description', 'List of all countries. You can use the locale header to get the data in supported language.') ->label('sdk.description', 'List of all countries. You can use the locale header to get the data in supported language.')
->action( ->action(
function() use ($response, $request) function () use ($response, $request) {
{
$list = Locale::getText('countries'); /* @var $list array */ $list = Locale::getText('countries'); /* @var $list array */
asort($list); asort($list);
@ -89,14 +86,13 @@ $utopia->get('/v1/locale/countries/eu')
->label('sdk.method', 'getCountriesEU') ->label('sdk.method', 'getCountriesEU')
->label('sdk.description', 'List of all countries that are currently members of the EU. You can use the locale header to get the data in supported language. UK brexit date is currently set to 2019-10-31 and will be updated if and when needed.') ->label('sdk.description', 'List of all countries that are currently members of the EU. You can use the locale header to get the data in supported language. UK brexit date is currently set to 2019-10-31 and will be updated if and when needed.')
->action( ->action(
function() use ($response) function () use ($response) {
{ $countries = Locale::getText('countries'); /* @var $countries array */
$countries = Locale::getText('countries'); /* @var $countries array */ $eu = include __DIR__.'/../config/eu.php';
$eu = include __DIR__ . '/../config/eu.php'; $list = [];
$list = [];
foreach ($eu as $code) { foreach ($eu as $code) {
if(array_key_exists($code, $countries)) { if (array_key_exists($code, $countries)) {
$list[$code] = $countries[$code]; $list[$code] = $countries[$code];
} }
} }
@ -114,15 +110,14 @@ $utopia->get('/v1/locale/countries/phones')
->label('sdk.method', 'getCountriesPhones') ->label('sdk.method', 'getCountriesPhones')
->label('sdk.description', 'List of all countries phone codes. You can use the locale header to get the data in supported language.') ->label('sdk.description', 'List of all countries phone codes. You can use the locale header to get the data in supported language.')
->action( ->action(
function() use ($response) function () use ($response) {
{ $list = include __DIR__.'/../config/phones.php'; /* @var $list array */
$list = include __DIR__ . '/../config/phones.php'; /* @var $list array */
$countries = Locale::getText('countries'); /* @var $countries array */ $countries = Locale::getText('countries'); /* @var $countries array */
foreach ($list as $code => $name) { foreach ($list as $code => $name) {
if(array_key_exists($code, $countries)) { if (array_key_exists($code, $countries)) {
$list[$code] = $countries[$code] . ' +' . $list[$code]; $list[$code] = $countries[$code].' +'.$list[$code];
} }
} }
@ -139,10 +134,9 @@ $utopia->get('/v1/locale/currencies')
->label('sdk.method', 'getCurrencies') ->label('sdk.method', 'getCurrencies')
->label('sdk.description', 'List of all currencies, including currency symol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in supported language.') ->label('sdk.description', 'List of all currencies, including currency symol, name, plural, and decimal digits for all major and minor currencies. You can use the locale header to get the data in supported language.')
->action( ->action(
function() use ($response) function () use ($response) {
{ $currencies = include __DIR__.'/../config/currencies.php';
$currencies = include __DIR__ . '/../config/currencies.php';
$response->json($currencies); $response->json($currencies);
} }
); );

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
<?php <?php
/** /*
* Created by PhpStorm. * Created by PhpStorm.
* User: eldadfux * User: eldadfux
* Date: 11/10/2018 * Date: 11/10/2018
* Time: 18:30 * Time: 18:30
*/ */

View file

@ -11,11 +11,11 @@ $roles = [
['type' => 'admin', 'label' => Locale::getText('general.roles.admin')], ['type' => 'admin', 'label' => Locale::getText('general.roles.admin')],
]; ];
$layout = new View(__DIR__ . '/../../views/layouts/default.phtml'); $layout = new View(__DIR__.'/../../views/layouts/default.phtml');
/* AJAX check */ /* AJAX check */
if(!empty($request->getQuery('version', ''))) { if (!empty($request->getQuery('version', ''))) {
$layout->setPath(__DIR__ . '/../../views/layouts/empty.phtml'); $layout->setPath(__DIR__.'/../../views/layouts/empty.phtml');
} }
$layout $layout
@ -32,13 +32,13 @@ $layout
->setParam('env', $utopia->getEnv()) ->setParam('env', $utopia->getEnv())
; ;
$utopia->shutdown(function() use ($utopia, $response, $request, $layout, $version, $env) { $utopia->shutdown(function () use ($utopia, $response, $request, $layout, $version, $env) {
$time = (60 * 60 * 24 * 45); // 45 days cache $time = (60 * 60 * 24 * 45); // 45 days cache
$isDev = (\Utopia\App::ENV_TYPE_DEVELOPMENT == $env); $isDev = (\Utopia\App::ENV_TYPE_DEVELOPMENT == $env);
$response $response
->addHeader('Cache-Control', 'public, max-age=' . $time) ->addHeader('Cache-Control', 'public, max-age='.$time)
->addHeader('Expires', date('D, d M Y H:i:s', time() + $time) . ' GMT') // 45 days cache ->addHeader('Expires', date('D, d M Y H:i:s', time() + $time).' GMT') // 45 days cache
->addHeader('X-UA-Compatible', 'IE=Edge'); // Deny IE browsers from going into quirks mode ->addHeader('X-UA-Compatible', 'IE=Edge'); // Deny IE browsers from going into quirks mode
$route = $utopia->match($request); $route = $utopia->match($request);
@ -48,4 +48,4 @@ $utopia->shutdown(function() use ($utopia, $response, $request, $layout, $versio
->setParam('isDev', $isDev) ->setParam('isDev', $isDev)
->setParam('class', $scope) ->setParam('class', $scope)
; ;
}); });

View file

@ -22,7 +22,7 @@ use Storage\Compression\Algorithms\GZIP;
use Resize\Resize; use Resize\Resize;
use OpenSSL\OpenSSL; use OpenSSL\OpenSSL;
Storage::addDevice('local', new Local('app-' . $project->getUid())); Storage::addDevice('local', new Local('app-'.$project->getUid()));
$fileLogos = [ // Based on this list @see http://stackoverflow.com/a/4212908/2299554 $fileLogos = [ // Based on this list @see http://stackoverflow.com/a/4212908/2299554
'default' => 'default.gif', 'default' => 'default.gif',
@ -60,18 +60,18 @@ $fileLogos = [ // Based on this list @see http://stackoverflow.com/a/4212908/229
]; ];
$inputs = [ $inputs = [
'jpg' => 'image/jpeg', 'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg', 'jpeg' => 'image/jpeg',
'gif' => 'image/gif', 'gif' => 'image/gif',
'png' => 'image/png', 'png' => 'image/png',
]; ];
$outputs = [ $outputs = [
'jpg' => 'image/jpeg', 'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg', 'jpeg' => 'image/jpeg',
'gif' => 'image/gif', 'gif' => 'image/gif',
'png' => 'image/png', 'png' => 'image/png',
'webp' => 'image/webp', 'webp' => 'image/webp',
]; ];
$mimes = [ $mimes = [
@ -121,11 +121,10 @@ $utopia->get('/v1/storage/files')
->label('sdk.description', 'Get a list of all the user files. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project files. [Learn more about different API modes](/docs/modes).') ->label('sdk.description', 'Get a list of all the user files. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project files. [Learn more about different API modes](/docs/modes).')
->param('search', '', function () {return new Text(256);}, 'Search term to filter your list results.', true) ->param('search', '', function () {return new Text(256);}, 'Search term to filter your list results.', true)
->param('limit', 25, function () {return new Range(0, 100);}, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('limit', 25, function () {return new Range(0, 100);}, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0 , function () {return new Range(0, 2000);}, 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('offset', 0, function () {return new Range(0, 2000);}, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('orderType', 'ASC', function () {return new WhiteList(['ASC', 'DESC']);}, 'Order result by ASC or DESC order.', true) ->param('orderType', 'ASC', function () {return new WhiteList(['ASC', 'DESC']);}, 'Order result by ASC or DESC order.', true)
->action( ->action(
function($search, $limit, $offset, $orderType) use ($response, $projectDB) function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
{
$results = $projectDB->getCollection([ $results = $projectDB->getCollection([
'limit' => $limit, 'limit' => $limit,
'offset' => $offset, 'offset' => $offset,
@ -134,7 +133,7 @@ $utopia->get('/v1/storage/files')
'orderCast' => 'int', 'orderCast' => 'int',
'search' => $search, 'search' => $search,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_FILES '$collection='.Database::SYSTEM_COLLECTION_FILES,
], ],
]); ]);
@ -154,10 +153,10 @@ $utopia->get('/v1/storage/files/:fileId')
->label('sdk.description', 'Get file by its unique ID. This endpoint response returns a JSON object with the file metadata.') ->label('sdk.description', 'Get file by its unique ID. This endpoint response returns a JSON object with the file metadata.')
->param('fileId', '', function () {return new UID();}, 'File unique ID.') ->param('fileId', '', function () {return new UID();}, 'File unique ID.')
->action( ->action(
function($fileId) use ($response, $projectDB) { function ($fileId) use ($response, $projectDB) {
$file = $projectDB->getDocument($fileId); $file = $projectDB->getDocument($fileId);
if(empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { if (empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
throw new Exception('File not found', 404); throw new Exception('File not found', 404);
} }
@ -180,48 +179,47 @@ $utopia->get('/v1/storage/files/:fileId/preview')
//->param('storage', 'local', function () {return new WhiteList(array('local'));}, 'Selected storage device. defaults to local') //->param('storage', 'local', function () {return new WhiteList(array('local'));}, 'Selected storage device. defaults to local')
//->param('token', '', function () {return new Text(128);}, 'Preview token', true) //->param('token', '', function () {return new Text(128);}, 'Preview token', true)
->action( ->action(
function($fileId, $width, $height, $quality, $background, $output) use ($request, $response, $projectDB, $project, $inputs, $outputs, $fileLogos) function ($fileId, $width, $height, $quality, $background, $output) use ($request, $response, $projectDB, $project, $inputs, $outputs, $fileLogos) {
{
$storage = 'local'; $storage = 'local';
if (!extension_loaded('imagick')) { if (!extension_loaded('imagick')) {
throw new Exception('Imagick extension is missing', 500); throw new Exception('Imagick extension is missing', 500);
} }
if(!Storage::exists($storage)) { if (!Storage::exists($storage)) {
throw new Exception('No such storage device'); throw new Exception('No such storage device');
} }
if((strpos($request->getServer('HTTP_ACCEPT'), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support if ((strpos($request->getServer('HTTP_ACCEPT'), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support
$output = 'jpg'; $output = 'jpg';
} }
$date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)) . ' GMT'; // 45 days cache $date = date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache
$key = md5($fileId . $width . $height . $quality . $background . $storage . $output); $key = md5($fileId.$width.$height.$quality.$background.$storage.$output);
$file = $projectDB->getDocument($fileId); $file = $projectDB->getDocument($fileId);
if(empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { if (empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
throw new Exception('File not found', 404); throw new Exception('File not found', 404);
} }
$path = $file->getAttribute('path'); $path = $file->getAttribute('path');
$algorithm = $file->getAttribute('algorithm'); $algorithm = $file->getAttribute('algorithm');
$type = strtolower(pathinfo($path, PATHINFO_EXTENSION)); $type = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$cipher = $file->getAttribute('fileOpenSSLCipher'); $cipher = $file->getAttribute('fileOpenSSLCipher');
//$mimeType = $file->getAttribute('mimeType', 'unknown'); //$mimeType = $file->getAttribute('mimeType', 'unknown');
$compressor = new GZIP(); $compressor = new GZIP();
$device = Storage::getDevice('local'); $device = Storage::getDevice('local');
if (!file_exists($path)) { if (!file_exists($path)) {
throw new Exception('File not found in ' . $path, 404); throw new Exception('File not found in '.$path, 404);
} }
$cache = new Cache(new Filesystem('/storage/cache/app-' . $project->getUid())); // Limit file number or size $cache = new Cache(new Filesystem('/storage/cache/app-'.$project->getUid())); // Limit file number or size
$data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */); $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */);
if($data) { if ($data) {
$output = (empty($output)) ? $type : $output; $output = (empty($output)) ? $type : $output;
$response $response
@ -234,27 +232,27 @@ $utopia->get('/v1/storage/files/:fileId/preview')
$source = $device->read($path); $source = $device->read($path);
if(!empty($cipher)) { // Decrypt if (!empty($cipher)) { // Decrypt
$source = OpenSSL::decrypt( $source = OpenSSL::decrypt(
$source, $source,
$file->getAttribute('fileOpenSSLCipher'), $file->getAttribute('fileOpenSSLCipher'),
$request->getServer('_APP_OPENSSL_KEY_V' . $file->getAttribute('fileOpenSSLVersion')), $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
0, 0,
hex2bin($file->getAttribute('fileOpenSSLIV')), hex2bin($file->getAttribute('fileOpenSSLIV')),
hex2bin($file->getAttribute('fileOpenSSLTag')) hex2bin($file->getAttribute('fileOpenSSLTag'))
); );
} }
if(!empty($algorithm)) { if (!empty($algorithm)) {
$source = $compressor->decompress($source); $source = $compressor->decompress($source);
} }
$resize = new Resize($source); $resize = new Resize($source);
$resize->crop((int)$width, (int)$height); $resize->crop((int) $width, (int) $height);
if(!empty($background)) { if (!empty($background)) {
$resize->setBackground('#' . $background); $resize->setBackground('#'.$background);
} }
$output = (empty($output)) ? $type : $output; $output = (empty($output)) ? $type : $output;
@ -286,30 +284,29 @@ $utopia->get('/v1/storage/files/:fileId/download')
->label('sdk.description', 'Get file content by its unique ID. The endpoint response return with a \'Content-Disposition: attachment\' header that tells the browser to start downloading the file to user downloads directory.') ->label('sdk.description', 'Get file content by its unique ID. The endpoint response return with a \'Content-Disposition: attachment\' header that tells the browser to start downloading the file to user downloads directory.')
->param('fileId', '', function () {return new UID();}, 'File unique ID.') ->param('fileId', '', function () {return new UID();}, 'File unique ID.')
->action( ->action(
function($fileId) use ($response, $request, $projectDB) function ($fileId) use ($response, $request, $projectDB) {
{
$file = $projectDB->getDocument($fileId); $file = $projectDB->getDocument($fileId);
if(empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { if (empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
throw new Exception('File not found', 404); throw new Exception('File not found', 404);
} }
$path = $file->getAttribute('path', ''); $path = $file->getAttribute('path', '');
if (!file_exists($path)) { if (!file_exists($path)) {
throw new Exception('File not found in ' . $path, 404); throw new Exception('File not found in '.$path, 404);
} }
$compressor = new GZIP(); $compressor = new GZIP();
$device = Storage::getDevice('local'); $device = Storage::getDevice('local');
$source = $device->read($path); $source = $device->read($path);
if(!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt
$source = OpenSSL::decrypt( $source = OpenSSL::decrypt(
$source, $source,
$file->getAttribute('fileOpenSSLCipher'), $file->getAttribute('fileOpenSSLCipher'),
$request->getServer('_APP_OPENSSL_KEY_V' . $file->getAttribute('fileOpenSSLVersion')), $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
0, 0,
hex2bin($file->getAttribute('fileOpenSSLIV')), hex2bin($file->getAttribute('fileOpenSSLIV')),
hex2bin($file->getAttribute('fileOpenSSLTag')) hex2bin($file->getAttribute('fileOpenSSLTag'))
@ -321,8 +318,8 @@ $utopia->get('/v1/storage/files/:fileId/download')
// Response // Response
$response $response
->setContentType($file->getAttribute('mimeType')) ->setContentType($file->getAttribute('mimeType'))
->addHeader('Content-Disposition', 'attachment; filename="' . $file->getAttribute('name', '') . '"') ->addHeader('Content-Disposition', 'attachment; filename="'.$file->getAttribute('name', '').'"')
->addHeader('Expires', date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->addHeader('Expires', date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
->addHeader('X-Peak', memory_get_peak_usage()) ->addHeader('X-Peak', memory_get_peak_usage())
->send($source) ->send($source)
; ;
@ -338,48 +335,47 @@ $utopia->get('/v1/storage/files/:fileId/view')
->param('fileId', '', function () {return new UID();}, 'File unique ID.') ->param('fileId', '', function () {return new UID();}, 'File unique ID.')
->param('as', '', function () {return new WhiteList(['pdf', /*'html',*/ 'text']);}, 'Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.', true) ->param('as', '', function () {return new WhiteList(['pdf', /*'html',*/ 'text']);}, 'Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.', true)
->action( ->action(
function($fileId, $as) use ($response, $request, $projectDB, $mimes) function ($fileId, $as) use ($response, $request, $projectDB, $mimes) {
{
$file = $projectDB->getDocument($fileId); $file = $projectDB->getDocument($fileId);
if(empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { if (empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
throw new Exception('File not found', 404); throw new Exception('File not found', 404);
} }
$path = $file->getAttribute('path', ''); $path = $file->getAttribute('path', '');
if (!file_exists($path)) { if (!file_exists($path)) {
throw new Exception('File not found in ' . $path, 404); throw new Exception('File not found in '.$path, 404);
} }
$compressor = new GZIP(); $compressor = new GZIP();
$device = Storage::getDevice('local'); $device = Storage::getDevice('local');
$contentType = 'text/plain'; $contentType = 'text/plain';
if(in_array($file->getAttribute('mimeType'), $mimes)) { if (in_array($file->getAttribute('mimeType'), $mimes)) {
$contentType = $file->getAttribute('mimeType'); $contentType = $file->getAttribute('mimeType');
} }
$source = $device->read($path); $source = $device->read($path);
if(!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt
$source = OpenSSL::decrypt( $source = OpenSSL::decrypt(
$source, $source,
$file->getAttribute('fileOpenSSLCipher'), $file->getAttribute('fileOpenSSLCipher'),
$request->getServer('_APP_OPENSSL_KEY_V' . $file->getAttribute('fileOpenSSLVersion')), $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
0, 0,
hex2bin($file->getAttribute('fileOpenSSLIV')), hex2bin($file->getAttribute('fileOpenSSLIV')),
hex2bin($file->getAttribute('fileOpenSSLTag')) hex2bin($file->getAttribute('fileOpenSSLTag'))
); );
} }
$output = $compressor->decompress($source); $output = $compressor->decompress($source);
$fileName = $file->getAttribute('name', ''); $fileName = $file->getAttribute('name', '');
$contentTypes = [ $contentTypes = [
'pdf' => 'application/pdf', 'pdf' => 'application/pdf',
'text' => 'text/plain', 'text' => 'text/plain',
]; ];
$contentType = (array_key_exists($as, $contentTypes)) ? $contentTypes[$as] : $contentType; $contentType = (array_key_exists($as, $contentTypes)) ? $contentTypes[$as] : $contentType;
@ -389,8 +385,8 @@ $utopia->get('/v1/storage/files/:fileId/view')
->setContentType($contentType) ->setContentType($contentType)
->addHeader('Content-Security-Policy', 'script-src none;') ->addHeader('Content-Security-Policy', 'script-src none;')
->addHeader('X-Content-Type-Options', 'nosniff') ->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') ->addHeader('Content-Disposition', 'inline; filename="'.$fileName.'"')
->addHeader('Expires', date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->addHeader('Expires', date('D, d M Y H:i:s', time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache
->addHeader('X-Peak', memory_get_peak_usage()) ->addHeader('X-Peak', memory_get_peak_usage())
->send($output) ->send($output)
; ;
@ -409,26 +405,25 @@ $utopia->post('/v1/storage/files')
->param('write', [], function () {return new ArrayList(new Text(64));}, 'An array of write permissions. [Learn more about permissions and roles](/docs/permissions).', true) ->param('write', [], function () {return new ArrayList(new Text(64));}, 'An array of write permissions. [Learn more about permissions and roles](/docs/permissions).', true)
->param('folderId', '', function () {return new UID();}, 'Folder to associate files with.', true) ->param('folderId', '', function () {return new UID();}, 'Folder to associate files with.', true)
->action( ->action(
function($files, $read, $write, $folderId) use ($request, $response, $user, $projectDB, $audit, $usage) function ($files, $read, $write, $folderId) use ($request, $response, $user, $projectDB, $audit, $usage) {
{ $files = $request->getFiles('files');
$files = $request->getFiles('files'); $read = (empty($read)) ? ['user:'.$user->getUid()] : $read;
$read = (empty($read)) ? ['user:' . $user->getUid()] : $read; $write = (empty($write)) ? ['user:'.$user->getUid()] : $write;
$write = (empty($write)) ? ['user:' . $user->getUid()] : $write;
/** /*
* Validation * Validation
*/ */
//$fileType = new FileType(array(FileType::FILE_TYPE_PNG, FileType::FILE_TYPE_GIF, FileType::FILE_TYPE_JPEG)); //$fileType = new FileType(array(FileType::FILE_TYPE_PNG, FileType::FILE_TYPE_GIF, FileType::FILE_TYPE_JPEG));
$fileSize = new FileSize(2097152 * 2); // 4MB $fileSize = new FileSize(2097152 * 2); // 4MB
if (empty($files)) { if (empty($files)) {
throw new Exception('No files sent', 400); throw new Exception('No files sent', 400);
} }
// Make sure we handle single file and multiple files the same way // Make sure we handle single file and multiple files the same way
$files['name'] = (is_array($files['name'])) ? $files['name'] : [$files['name']]; $files['name'] = (is_array($files['name'])) ? $files['name'] : [$files['name']];
$files['tmp_name'] = (is_array($files['tmp_name'])) ? $files['tmp_name'] : [$files['tmp_name']]; $files['tmp_name'] = (is_array($files['tmp_name'])) ? $files['tmp_name'] : [$files['tmp_name']];
$files['size'] = (is_array($files['size'])) ? $files['size'] : [$files['size']]; $files['size'] = (is_array($files['size'])) ? $files['size'] : [$files['size']];
// Check if file type is allowed // Check if file type is allowed
//foreach ($files['tmp_name'] as $tmpName) { //foreach ($files['tmp_name'] as $tmpName) {
@ -446,18 +441,18 @@ $utopia->post('/v1/storage/files')
$antiVirus = new Network('clamav', 3310); $antiVirus = new Network('clamav', 3310);
/** /*
* Models * Models
*/ */
$list = []; $list = [];
$device = Storage::getDevice('local'); $device = Storage::getDevice('local');
foreach ($files['tmp_name'] as $i => $tmpName) { foreach ($files['tmp_name'] as $i => $tmpName) {
// Save to storage // Save to storage
$name = $files['name'][$i]; $name = $files['name'][$i];
$size = $device->getFileSize($tmpName); $size = $device->getFileSize($tmpName);
$path = $device->upload($tmpName, $files['name'][$i]); $path = $device->upload($tmpName, $files['name'][$i]);
$mimeType = $device->getFileMimeType($path); $mimeType = $device->getFileMimeType($path);
// Check if file size is exceeding allowed limit // Check if file size is exceeding allowed limit
if (!$antiVirus->fileScan($path)) { if (!$antiVirus->fileScan($path)) {
@ -466,45 +461,45 @@ $utopia->post('/v1/storage/files')
} }
// Compression // Compression
$compressor = new GZIP(); $compressor = new GZIP();
$data = $device->read($path); $data = $device->read($path);
$data = $compressor->compress($data); $data = $compressor->compress($data);
$key = $request->getServer('_APP_OPENSSL_KEY_V1'); $key = $request->getServer('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag); $data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag);
$sizeCompressed = (int)$device->write($path, $data); $sizeCompressed = (int) $device->write($path, $data);
$file = $projectDB->createDocument([ $file = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_FILES, '$collection' => Database::SYSTEM_COLLECTION_FILES,
'$permissions' => [ '$permissions' => [
'read' => $read, 'read' => $read,
'write' => $write, 'write' => $write,
], ],
'dateCreated' => time(), 'dateCreated' => time(),
'folderId' => $folderId, 'folderId' => $folderId,
'name' => $name, 'name' => $name,
'path' => $path, 'path' => $path,
'signature' => $device->getFileHash($path), 'signature' => $device->getFileHash($path),
'mimeType' => $mimeType, 'mimeType' => $mimeType,
'sizeOriginal' => $size, 'sizeOriginal' => $size,
'sizeCompressed' => $sizeCompressed, 'sizeCompressed' => $sizeCompressed,
'algorithm' => $compressor->getName(), 'algorithm' => $compressor->getName(),
'token' => bin2hex(random_bytes(64)), 'token' => bin2hex(random_bytes(64)),
'comment' => '', 'comment' => '',
'fileOpenSSLVersion'=> '1', 'fileOpenSSLVersion' => '1',
'fileOpenSSLCipher' => OpenSSL::CIPHER_AES_128_GCM, 'fileOpenSSLCipher' => OpenSSL::CIPHER_AES_128_GCM,
'fileOpenSSLTag' => bin2hex($tag), 'fileOpenSSLTag' => bin2hex($tag),
'fileOpenSSLIV' => bin2hex($iv), 'fileOpenSSLIV' => bin2hex($iv),
]); ]);
if(false === $file) { if (false === $file) {
throw new Exception('Failed saving file to DB', 500); throw new Exception('Failed saving file to DB', 500);
} }
$audit $audit
->setParam('event', 'storage.upload') ->setParam('event', 'storage.upload')
->setParam('resource', 'storage/file/' . $file->getUid()) ->setParam('resource', 'storage/file/'.$file->getUid())
; ;
$usage $usage
@ -532,11 +527,10 @@ $utopia->put('/v1/storage/files/:fileId')
->param('write', [], function () {return new ArrayList(new Text(64));}, 'An array of write permissions. [Learn more about permissions and roles](/docs/permissions).', true) ->param('write', [], function () {return new ArrayList(new Text(64));}, 'An array of write permissions. [Learn more about permissions and roles](/docs/permissions).', true)
->param('folderId', '', function () {return new UID();}, 'Folder to associate files with.', true) ->param('folderId', '', function () {return new UID();}, 'Folder to associate files with.', true)
->action( ->action(
function($fileId, $read, $write, $folderId) use ($response, $projectDB) function ($fileId, $read, $write, $folderId) use ($response, $projectDB) {
{
$file = $projectDB->getDocument($fileId); $file = $projectDB->getDocument($fileId);
if(empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { if (empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
throw new Exception('File not found', 404); throw new Exception('File not found', 404);
} }
@ -548,7 +542,7 @@ $utopia->put('/v1/storage/files/:fileId')
'folderId' => $folderId, 'folderId' => $folderId,
])); ]));
if(false === $file) { if (false === $file) {
throw new Exception('Failed saving file to DB', 500); throw new Exception('Failed saving file to DB', 500);
} }
@ -564,24 +558,24 @@ $utopia->delete('/v1/storage/files/:fileId')
->label('sdk.description', 'Delete a file by its unique ID. Only users with write permissions have access to delete this resource.') ->label('sdk.description', 'Delete a file by its unique ID. Only users with write permissions have access to delete this resource.')
->param('fileId', '', function () {return new UID();}, 'File unique ID.') ->param('fileId', '', function () {return new UID();}, 'File unique ID.')
->action( ->action(
function($fileId) use ($response, $projectDB, $audit, $usage) { function ($fileId) use ($response, $projectDB, $audit, $usage) {
$file = $projectDB->getDocument($fileId); $file = $projectDB->getDocument($fileId);
if(empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { if (empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
throw new Exception('File not found', 404); throw new Exception('File not found', 404);
} }
$device = Storage::getDevice('local'); $device = Storage::getDevice('local');
if($device->delete($file->getAttribute('path', ''))) { if ($device->delete($file->getAttribute('path', ''))) {
if(!$projectDB->deleteDocument($fileId)) { if (!$projectDB->deleteDocument($fileId)) {
throw new Exception('Failed to remove file from DB', 500); throw new Exception('Failed to remove file from DB', 500);
} }
} }
$audit $audit
->setParam('event', 'storage.delete') ->setParam('event', 'storage.delete')
->setParam('resource', 'storage/file/' . $file->getUid()) ->setParam('resource', 'storage/file/'.$file->getUid())
; ;
$usage $usage
@ -601,30 +595,29 @@ $utopia->get('/v1/storage/files/:fileId/scan')
->param('fileId', '', function () {return new UID();}, 'File unique ID.') ->param('fileId', '', function () {return new UID();}, 'File unique ID.')
->param('storage', 'local', function () {return new WhiteList(['local']);}) ->param('storage', 'local', function () {return new WhiteList(['local']);})
->action( ->action(
function($fileId, $storage) use ($response, $request, $projectDB) function ($fileId, $storage) use ($response, $request, $projectDB) {
{
$file = $projectDB->getDocument($fileId); $file = $projectDB->getDocument($fileId);
if(empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { if (empty($file->getUid()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) {
throw new Exception('File not found', 404); throw new Exception('File not found', 404);
} }
$path = $file->getAttribute('path', ''); $path = $file->getAttribute('path', '');
if (!file_exists($path)) { if (!file_exists($path)) {
throw new Exception('File not found in ' . $path, 404); throw new Exception('File not found in '.$path, 404);
} }
$compressor = new GZIP(); $compressor = new GZIP();
$device = Storage::getDevice($storage); $device = Storage::getDevice($storage);
$source = $device->read($path); $source = $device->read($path);
if(!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt
$source = OpenSSL::decrypt( $source = OpenSSL::decrypt(
$source, $source,
$file->getAttribute('fileOpenSSLCipher'), $file->getAttribute('fileOpenSSLCipher'),
$request->getServer('_APP_OPENSSL_KEY_V' . $file->getAttribute('fileOpenSSLVersion')), $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')),
0, 0,
hex2bin($file->getAttribute('fileOpenSSLIV')), hex2bin($file->getAttribute('fileOpenSSLIV')),
hex2bin($file->getAttribute('fileOpenSSLTag')) hex2bin($file->getAttribute('fileOpenSSLTag'))
@ -641,4 +634,4 @@ $utopia->get('/v1/storage/files/:fileId/scan')
//$response->json($antiVirus->continueScan($device->getRoot())); //$response->json($antiVirus->continueScan($device->getRoot()));
} }
); );

View file

@ -26,11 +26,10 @@ $utopia->get('/v1/teams')
->label('sdk.description', 'Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project teams. [Learn more about different API modes](/docs/modes).') ->label('sdk.description', 'Get a list of all the current user teams. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project teams. [Learn more about different API modes](/docs/modes).')
->param('search', '', function () {return new Text(256);}, 'Search term to filter your list results.', true) ->param('search', '', function () {return new Text(256);}, 'Search term to filter your list results.', true)
->param('limit', 25, function () {return new Range(0, 100);}, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('limit', 25, function () {return new Range(0, 100);}, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0 , function () {return new Range(0, 2000);}, 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('offset', 0, function () {return new Range(0, 2000);}, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('orderType', 'ASC', function () {return new WhiteList(['ASC', 'DESC']);}, 'Order result by ASC or DESC order.', true) ->param('orderType', 'ASC', function () {return new WhiteList(['ASC', 'DESC']);}, 'Order result by ASC or DESC order.', true)
->action( ->action(
function($search, $limit, $offset, $orderType) use ($response, $projectDB) function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
{
$results = $projectDB->getCollection([ $results = $projectDB->getCollection([
'limit' => $limit, 'limit' => $limit,
'offset' => $offset, 'offset' => $offset,
@ -39,7 +38,7 @@ $utopia->get('/v1/teams')
'orderCast' => 'int', 'orderCast' => 'int',
'search' => $search, 'search' => $search,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_TEAMS '$collection='.Database::SYSTEM_COLLECTION_TEAMS,
], ],
]); ]);
@ -55,11 +54,10 @@ $utopia->get('/v1/teams/:teamId')
->label('sdk.description', 'Get team by its unique ID. All team members have read access for this resource.') ->label('sdk.description', 'Get team by its unique ID. All team members have read access for this resource.')
->param('teamId', '', function () {return new UID();}, 'Team unique ID.') ->param('teamId', '', function () {return new UID();}, 'Team unique ID.')
->action( ->action(
function($teamId) use ($response, $projectDB) function ($teamId) use ($response, $projectDB) {
{
$team = $projectDB->getDocument($teamId); $team = $projectDB->getDocument($teamId);
if(empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404); throw new Exception('Team not found', 404);
} }
@ -75,11 +73,10 @@ $utopia->get('/v1/teams/:teamId/members')
->label('sdk.description', 'Get team members by the team unique ID. All team members have read access for this list of resources.') ->label('sdk.description', 'Get team members by the team unique ID. All team members have read access for this list of resources.')
->param('teamId', '', function () {return new UID();}, 'Team unique ID.') ->param('teamId', '', function () {return new UID();}, 'Team unique ID.')
->action( ->action(
function($teamId) use ($response, $projectDB) function ($teamId) use ($response, $projectDB) {
{
$team = $projectDB->getDocument($teamId); $team = $projectDB->getDocument($teamId);
if(empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404); throw new Exception('Team not found', 404);
} }
@ -87,15 +84,15 @@ $utopia->get('/v1/teams/:teamId/members')
'limit' => 50, 'limit' => 50,
'offset' => 0, 'offset' => 0,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_MEMBERSHIPS, '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'teamId=' . $teamId 'teamId='.$teamId,
] ],
]); ]);
$users = []; $users = [];
foreach ($memberships as $member) { foreach ($memberships as $member) {
if(empty($member->getAttribute('userId', null))) { if (empty($member->getAttribute('userId', null))) {
continue; continue;
} }
@ -109,9 +106,10 @@ $utopia->get('/v1/teams/:teamId/members')
} }
usort($users, function ($a, $b) { usort($users, function ($a, $b) {
if($a['joined'] === 0 || $b['joined'] === 0) { if ($a['joined'] === 0 || $b['joined'] === 0) {
return $b['joined'] - $a['joined']; return $b['joined'] - $a['joined'];
} }
return $a['joined'] - $b['joined']; return $a['joined'] - $b['joined'];
}); });
@ -128,15 +126,14 @@ $utopia->post('/v1/teams')
->param('name', null, function () {return new Text(100);}, 'Team name.') ->param('name', null, function () {return new Text(100);}, 'Team name.')
->param('roles', ['owner'], function () {return new ArrayList(new Text(128));}, 'User roles array. Use this param to set the roles in the team for the user who created the team. The default role is **owner**, a role can be any string.', true) ->param('roles', ['owner'], function () {return new ArrayList(new Text(128));}, 'User roles array. Use this param to set the roles in the team for the user who created the team. The default role is **owner**, a role can be any string.', true)
->action( ->action(
function($name, $roles) use ($response, $projectDB, $user, $mode) function ($name, $roles) use ($response, $projectDB, $user, $mode) {
{
Authorization::disable(); Authorization::disable();
$team = $projectDB->createDocument([ $team = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_TEAMS, '$collection' => Database::SYSTEM_COLLECTION_TEAMS,
'$permissions' => [ '$permissions' => [
'read' => ['team:{self}'], 'read' => ['team:{self}'],
'write' => ['team:{self}/owner'], 'write' => ['team:{self}/owner'],
], ],
'name' => $name, 'name' => $name,
'sum' => ($mode !== APP_MODE_ADMIN) ? 1 : 0, 'sum' => ($mode !== APP_MODE_ADMIN) ? 1 : 0,
@ -145,16 +142,16 @@ $utopia->post('/v1/teams')
Authorization::enable(); Authorization::enable();
if(false === $team) { if (false === $team) {
throw new Exception('Failed saving team to DB', 500); throw new Exception('Failed saving team to DB', 500);
} }
if($mode !== APP_MODE_ADMIN) { // Don't add user on admin mode if ($mode !== APP_MODE_ADMIN) { // Don't add user on admin mode
$membership = new Document([ $membership = new Document([
'$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'$permissions' => [ '$permissions' => [
'read' => ['user:' . $user->getUid(), 'team:' . $team->getUid()], 'read' => ['user:'.$user->getUid(), 'team:'.$team->getUid()],
'write' => ['user:' . $user->getUid(), 'team:' . $team->getUid() . '/owner'], 'write' => ['user:'.$user->getUid(), 'team:'.$team->getUid().'/owner'],
], ],
'userId' => $user->getUid(), 'userId' => $user->getUid(),
'teamId' => $team->getUid(), 'teamId' => $team->getUid(),
@ -170,7 +167,7 @@ $utopia->post('/v1/teams')
$user = $projectDB->updateDocument($user->getArrayCopy()); $user = $projectDB->updateDocument($user->getArrayCopy());
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
} }
@ -191,11 +188,10 @@ $utopia->put('/v1/teams/:teamId')
->param('teamId', '', function () {return new UID();}, 'Team unique ID.') ->param('teamId', '', function () {return new UID();}, 'Team unique ID.')
->param('name', null, function () {return new Text(100);}, 'Team name.') ->param('name', null, function () {return new Text(100);}, 'Team name.')
->action( ->action(
function($teamId, $name) use ($response, $projectDB) function ($teamId, $name) use ($response, $projectDB) {
{
$team = $projectDB->getDocument($teamId); $team = $projectDB->getDocument($teamId);
if(empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404); throw new Exception('Team not found', 404);
} }
@ -203,7 +199,7 @@ $utopia->put('/v1/teams/:teamId')
'name' => $name, 'name' => $name,
])); ]));
if(false === $team) { if (false === $team) {
throw new Exception('Failed saving team to DB', 500); throw new Exception('Failed saving team to DB', 500);
} }
@ -219,11 +215,10 @@ $utopia->delete('/v1/teams/:teamId')
->label('sdk.description', 'Delete team by its unique ID. Only team owners have write access for this resource.') ->label('sdk.description', 'Delete team by its unique ID. Only team owners have write access for this resource.')
->param('teamId', '', function () {return new UID();}, 'Team unique ID.') ->param('teamId', '', function () {return new UID();}, 'Team unique ID.')
->action( ->action(
function($teamId) use ($response, $projectDB) function ($teamId) use ($response, $projectDB) {
{
$team = $projectDB->getDocument($teamId); $team = $projectDB->getDocument($teamId);
if(empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404); throw new Exception('Team not found', 404);
} }
@ -231,18 +226,18 @@ $utopia->delete('/v1/teams/:teamId')
'limit' => 2000, // TODO add members limit 'limit' => 2000, // TODO add members limit
'offset' => 0, 'offset' => 0,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_MEMBERSHIPS, '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'teamId=' . $teamId 'teamId='.$teamId,
] ],
]); ]);
foreach ($memberships as $member) { foreach ($memberships as $member) {
if(!$projectDB->deleteDocument($member)) { if (!$projectDB->deleteDocument($member)) {
throw new Exception('Failed to remove membership for team from DB', 500); throw new Exception('Failed to remove membership for team from DB', 500);
} }
} }
if(!$projectDB->deleteDocument($teamId)) { if (!$projectDB->deleteDocument($teamId)) {
throw new Exception('Failed to remove team from DB', 500); throw new Exception('Failed to remove team from DB', 500);
} }
@ -264,12 +259,11 @@ $utopia->post('/v1/teams/:teamId/memberships')
->param('roles', [], function () {return new ArrayList(new Text(128));}, 'Invite roles array. Learn more about [roles and permissions](/docs/permissions).') ->param('roles', [], function () {return new ArrayList(new Text(128));}, 'Invite roles array. Learn more about [roles and permissions](/docs/permissions).')
->param('redirect', '', function () use ($clients) {return new Host($clients);}, 'Reset page to redirect user back to your app from the invitation email.') ->param('redirect', '', function () use ($clients) {return new Host($clients);}, 'Reset page to redirect user back to your app from the invitation email.')
->action( ->action(
function($teamId, $email, $name, $roles, $redirect) use ($request, $response, $register, $project, $user, $audit, $projectDB) function ($teamId, $email, $name, $roles, $redirect) use ($request, $response, $register, $project, $user, $audit, $projectDB) {
{
$name = (empty($name)) ? $email : $name; $name = (empty($name)) ? $email : $name;
$team = $projectDB->getDocument($teamId); $team = $projectDB->getDocument($teamId);
if(empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404); throw new Exception('Team not found', 404);
} }
@ -277,21 +271,21 @@ $utopia->post('/v1/teams/:teamId/memberships')
'limit' => 50, 'limit' => 50,
'offset' => 0, 'offset' => 0,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_MEMBERSHIPS, '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'teamId=' . $team->getUid() 'teamId='.$team->getUid(),
] ],
]); ]);
$invitee = $projectDB->getCollection([ // Get user by email address $invitee = $projectDB->getCollection([ // Get user by email address
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'email=' . $email 'email='.$email,
] ],
]); ]);
if(empty($invitee)) { // Create new user if no user with same email found if (empty($invitee)) { // Create new user if no user with same email found
Authorization::disable(); Authorization::disable();
@ -309,12 +303,12 @@ $utopia->post('/v1/teams/:teamId/memberships')
'confirm' => false, 'confirm' => false,
'reset' => false, 'reset' => false,
'name' => $name, 'name' => $name,
'tokens' => [] 'tokens' => [],
]); ]);
Authorization::enable(); Authorization::enable();
if(false === $invitee) { if (false === $invitee) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
} }
@ -322,16 +316,16 @@ $utopia->post('/v1/teams/:teamId/memberships')
$isOwner = false; $isOwner = false;
foreach ($memberships as $member) { foreach ($memberships as $member) {
if($member->getAttribute('userId') == $invitee->getUid()) { if ($member->getAttribute('userId') == $invitee->getUid()) {
throw new Exception('User has already been invited or is already a member of this team', 400); throw new Exception('User has already been invited or is already a member of this team', 400);
} }
if($member->getAttribute('userId') == $user->getUid() && in_array('owner', $member->getAttribute('roles', []))) { if ($member->getAttribute('userId') == $user->getUid() && in_array('owner', $member->getAttribute('roles', []))) {
$isOwner = true; $isOwner = true;
} }
} }
if(!$isOwner) { if (!$isOwner) {
throw new Exception('User is not allowed to send invitations for this team', 401); throw new Exception('User is not allowed to send invitations for this team', 401);
} }
@ -339,9 +333,9 @@ $utopia->post('/v1/teams/:teamId/memberships')
$membership = new Document([ $membership = new Document([
'$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'$permissions' => [ '$permissions' => [
'read' => ['*'], 'read' => ['*'],
'write' => ['user:' . $invitee->getUid(), 'team:' . $team->getUid() . '/owner'], 'write' => ['user:'.$invitee->getUid(), 'team:'.$team->getUid().'/owner'],
], ],
'userId' => $invitee->getUid(), 'userId' => $invitee->getUid(),
'teamId' => $team->getUid(), 'teamId' => $team->getUid(),
@ -354,7 +348,7 @@ $utopia->post('/v1/teams/:teamId/memberships')
$membership = $projectDB->createDocument($membership->getArrayCopy()); $membership = $projectDB->createDocument($membership->getArrayCopy());
if(false === $membership) { if (false === $membership) {
throw new Exception('Failed saving membership to DB', 500); throw new Exception('Failed saving membership to DB', 500);
} }
@ -362,7 +356,7 @@ $utopia->post('/v1/teams/:teamId/memberships')
$redirect['query'] = Template::mergeQuery(((isset($redirect['query'])) ? $redirect['query'] : ''), ['inviteId' => $membership->getUid(), 'teamId' => $team->getUid(), 'userId' => $invitee->getUid(), 'secret' => $secret]); $redirect['query'] = Template::mergeQuery(((isset($redirect['query'])) ? $redirect['query'] : ''), ['inviteId' => $membership->getUid(), 'teamId' => $team->getUid(), 'userId' => $invitee->getUid(), 'secret' => $secret]);
$redirect = Template::unParseURL($redirect); $redirect = Template::unParseURL($redirect);
$body = new Template(__DIR__ . '/../config/locale/templates/' . Locale::getText('auth.emails.invitation.body')); $body = new Template(__DIR__.'/../config/locale/templates/'.Locale::getText('auth.emails.invitation.body'));
$body $body
->setParam('{{direction}}', Locale::getText('settings.direction')) ->setParam('{{direction}}', Locale::getText('settings.direction'))
->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
@ -376,13 +370,12 @@ $utopia->post('/v1/teams/:teamId/memberships')
$mail->addAddress($email, $name); $mail->addAddress($email, $name);
$mail->Subject = sprintf(Locale::getText('auth.emails.invitation.title'), $team->getAttribute('name', '[TEAM-NAME]'), $project->getAttribute('name', ['[APP-NAME]'])); $mail->Subject = sprintf(Locale::getText('auth.emails.invitation.title'), $team->getAttribute('name', '[TEAM-NAME]'), $project->getAttribute('name', ['[APP-NAME]']));
$mail->Body = $body->render(); $mail->Body = $body->render();
$mail->AltBody = strip_tags($body->render()); $mail->AltBody = strip_tags($body->render());
try { try {
$mail->send(); $mail->send();
} } catch (\Exception $error) {
catch(\Exception $error) {
//throw new Exception('Problem sending mail: ' . $error->getError(), 500); //throw new Exception('Problem sending mail: ' . $error->getError(), 500);
} }
@ -407,27 +400,26 @@ $utopia->post('/v1/teams/:teamId/memberships/:inviteId/resend')
->param('inviteId', '', function () {return new UID();}, 'Invite unique ID.') ->param('inviteId', '', function () {return new UID();}, 'Invite unique ID.')
->param('redirect', '', function () use ($clients) {return new Host($clients);}, 'Reset page to redirect user back to your app from the invitation email.') ->param('redirect', '', function () use ($clients) {return new Host($clients);}, 'Reset page to redirect user back to your app from the invitation email.')
->action( ->action(
function($teamId, $inviteId, $redirect) use ($response, $register, $project, $user, $audit, $projectDB) function ($teamId, $inviteId, $redirect) use ($response, $register, $project, $user, $audit, $projectDB) {
{
$membership = $projectDB->getDocument($inviteId); $membership = $projectDB->getDocument($inviteId);
if(empty($membership->getUid()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { if (empty($membership->getUid()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) {
throw new Exception('Membership not found', 404); throw new Exception('Membership not found', 404);
} }
$team = $projectDB->getDocument($membership->getAttribute('teamId')); $team = $projectDB->getDocument($membership->getAttribute('teamId'));
if(empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404); throw new Exception('Team not found', 404);
} }
if($team->getUid() !== $teamId) { if ($team->getUid() !== $teamId) {
throw new Exception('Team IDs don\'t match', 404); throw new Exception('Team IDs don\'t match', 404);
} }
$invitee = $projectDB->getDocument($membership->getAttribute('userId')); $invitee = $projectDB->getDocument($membership->getAttribute('userId'));
if(empty($invitee->getUid()) || Database::SYSTEM_COLLECTION_USERS != $invitee->getCollection()) { if (empty($invitee->getUid()) || Database::SYSTEM_COLLECTION_USERS != $invitee->getCollection()) {
throw new Exception('User not found', 404); throw new Exception('User not found', 404);
} }
@ -435,7 +427,7 @@ $utopia->post('/v1/teams/:teamId/memberships/:inviteId/resend')
$membership = $projectDB->updateDocument(array_merge($membership->getArrayCopy(), ['secret' => Auth::hash($secret)])); $membership = $projectDB->updateDocument(array_merge($membership->getArrayCopy(), ['secret' => Auth::hash($secret)]));
if(false === $membership) { if (false === $membership) {
throw new Exception('Failed updating membership to DB', 500); throw new Exception('Failed updating membership to DB', 500);
} }
@ -443,7 +435,7 @@ $utopia->post('/v1/teams/:teamId/memberships/:inviteId/resend')
$redirect['query'] = Template::mergeQuery(((isset($redirect['query'])) ? $redirect['query'] : ''), ['inviteId' => $membership->getUid(), 'userId' => $membership->getAttribute('userId'), 'secret' => $secret]); $redirect['query'] = Template::mergeQuery(((isset($redirect['query'])) ? $redirect['query'] : ''), ['inviteId' => $membership->getUid(), 'userId' => $membership->getAttribute('userId'), 'secret' => $secret]);
$redirect = Template::unParseURL($redirect); $redirect = Template::unParseURL($redirect);
$body = new Template(__DIR__ . '/../config/locale/templates/' . Locale::getText('auth.emails.invitation.body')); $body = new Template(__DIR__.'/../config/locale/templates/'.Locale::getText('auth.emails.invitation.body'));
$body $body
->setParam('{{direction}}', Locale::getText('settings.direction')) ->setParam('{{direction}}', Locale::getText('settings.direction'))
->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]']))
@ -457,16 +449,15 @@ $utopia->post('/v1/teams/:teamId/memberships/:inviteId/resend')
$mail->addAddress($invitee->getAttribute('email'), $invitee->getAttribute('name')); $mail->addAddress($invitee->getAttribute('email'), $invitee->getAttribute('name'));
$mail->Subject = sprintf(Locale::getText('auth.emails.invitation.title'), $team->getAttribute('name', '[TEAM-NAME]'), $project->getAttribute('name', ['[APP-NAME]'])); $mail->Subject = sprintf(Locale::getText('auth.emails.invitation.title'), $team->getAttribute('name', '[TEAM-NAME]'), $project->getAttribute('name', ['[APP-NAME]']));
$mail->Body = $body->render(); $mail->Body = $body->render();
$mail->AltBody = strip_tags($body->render()); $mail->AltBody = strip_tags($body->render());
try { try {
$mail->send(); $mail->send();
} } catch (\Exception $error) {
catch(\Exception $error) {
//throw new Exception('Problem sending mail: ' . $error->getError(), 500); //throw new Exception('Problem sending mail: ' . $error->getError(), 500);
} }
$audit $audit
->setParam('userId', $user->getUid()) ->setParam('userId', $user->getUid())
->setParam('event', 'auth.invite.resend') ->setParam('event', 'auth.invite.resend')
@ -493,65 +484,68 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
->param('success', null, function () use ($clients) {return new Host($clients);}, 'Redirect when registration succeed', true) ->param('success', null, function () use ($clients) {return new Host($clients);}, 'Redirect when registration succeed', true)
->param('failure', null, function () use ($clients) {return new Host($clients);}, 'Redirect when registration failed', true) ->param('failure', null, function () use ($clients) {return new Host($clients);}, 'Redirect when registration failed', true)
->action( ->action(
function($teamId, $inviteId, $userId, $secret, $success, $failure) use ($response, $request, $user, $audit, $projectDB) function ($teamId, $inviteId, $userId, $secret, $success, $failure) use ($response, $request, $user, $audit, $projectDB) {
{
$invite = $projectDB->getDocument($inviteId); $invite = $projectDB->getDocument($inviteId);
if(empty($invite->getUid()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $invite->getCollection()) { if (empty($invite->getUid()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $invite->getCollection()) {
if($failure) { if ($failure) {
$response->redirect($failure); $response->redirect($failure);
return; return;
} }
throw new Exception('Invite not found', 404); throw new Exception('Invite not found', 404);
} }
if($invite->getAttribute('teamId')->getUid() !== $teamId) { if ($invite->getAttribute('teamId')->getUid() !== $teamId) {
throw new Exception('Team IDs don\'t match', 404); throw new Exception('Team IDs don\'t match', 404);
} }
$team = $projectDB->getDocument($teamId); $team = $projectDB->getDocument($teamId);
if(empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404); throw new Exception('Team not found', 404);
} }
if(Auth::hash($secret) !== $invite->getAttribute('secret')) { if (Auth::hash($secret) !== $invite->getAttribute('secret')) {
if($failure) { if ($failure) {
$response->redirect($failure); $response->redirect($failure);
return; return;
} }
throw new Exception('Secret key not valid', 401); throw new Exception('Secret key not valid', 401);
} }
if($userId != $invite->getAttribute('userId')) { if ($userId != $invite->getAttribute('userId')) {
if($failure) { if ($failure) {
$response->redirect($failure); $response->redirect($failure);
return; return;
} }
throw new Exception('Invite not belong to current user (' . $user->getAttribute('email') . ')', 401); throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401);
} }
if(empty($user->getUid())) { if (empty($user->getUid())) {
$user = $projectDB->getCollection([ // Get user $user = $projectDB->getCollection([ // Get user
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'$uid=' . $userId '$uid='.$userId,
] ],
]); ]);
} }
if($invite->getAttribute('userId') !== $user->getUid()) { if ($invite->getAttribute('userId') !== $user->getUid()) {
if($failure) { if ($failure) {
$response->redirect($failure); $response->redirect($failure);
return; return;
} }
throw new Exception('Invite not belong to current user (' . $user->getAttribute('email') . ')', 401); throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401);
} }
$invite // Attach user to team $invite // Attach user to team
@ -569,19 +563,19 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
$user->setAttribute('tokens', new Document([ $user->setAttribute('tokens', new Document([
'$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$collection' => Database::SYSTEM_COLLECTION_TOKENS,
'$permissions' => ['read' => ['user:' . $user->getUid()], 'write' => ['user:' . $user->getUid()]], '$permissions' => ['read' => ['user:'.$user->getUid()], 'write' => ['user:'.$user->getUid()]],
'type' => Auth::TOKEN_TYPE_LOGIN, 'type' => Auth::TOKEN_TYPE_LOGIN,
'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak
'expire' => $expiry, 'expire' => $expiry,
'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'),
'ip' => $request->getIP(), 'ip' => $request->getIP(),
]),Document::SET_TYPE_APPEND); ]), Document::SET_TYPE_APPEND);
Authorization::setRole('user:' . $userId); Authorization::setRole('user:'.$userId);
$user = $projectDB->updateDocument($user->getArrayCopy()); $user = $projectDB->updateDocument($user->getArrayCopy());
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
@ -589,7 +583,7 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
'sum' => $team->getAttribute('sum', 0) + 1, 'sum' => $team->getAttribute('sum', 0) + 1,
])); ]));
if(false === $team) { if (false === $team) {
throw new Exception('Failed saving team to DB', 500); throw new Exception('Failed saving team to DB', 500);
} }
@ -600,7 +594,7 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
$response->addCookie(Auth::$cookieName, Auth::encodeSession($user->getUid(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true); $response->addCookie(Auth::$cookieName, Auth::encodeSession($user->getUid(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true);
if($success) { if ($success) {
$response->redirect($success); $response->redirect($success);
} }
@ -613,29 +607,28 @@ $utopia->delete('/v1/teams/:teamId/memberships/:inviteId')
->label('scope', 'account') ->label('scope', 'account')
->label('sdk.namespace', 'teams') ->label('sdk.namespace', 'teams')
->label('sdk.method', 'deleteTeamMembership') ->label('sdk.method', 'deleteTeamMembership')
->label('sdk.description', "This endpoint allows a user to leave a team or for a team owner to delete the membership of any other team member.") ->label('sdk.description', 'This endpoint allows a user to leave a team or for a team owner to delete the membership of any other team member.')
->param('teamId', '', function () {return new UID();}, 'Team unique ID.') ->param('teamId', '', function () {return new UID();}, 'Team unique ID.')
->param('inviteId', '', function () {return new UID();}, 'Invite unique ID') ->param('inviteId', '', function () {return new UID();}, 'Invite unique ID')
->action( ->action(
function($teamId, $inviteId) use ($response, $projectDB, $audit) function ($teamId, $inviteId) use ($response, $projectDB, $audit) {
{
$invite = $projectDB->getDocument($inviteId); $invite = $projectDB->getDocument($inviteId);
if(empty($invite->getUid()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $invite->getCollection()) { if (empty($invite->getUid()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $invite->getCollection()) {
throw new Exception('Invite not found', 404); throw new Exception('Invite not found', 404);
} }
if($invite->getAttribute('teamId') !== $teamId) { if ($invite->getAttribute('teamId') !== $teamId) {
throw new Exception('Team IDs don\'t match', 404); throw new Exception('Team IDs don\'t match', 404);
} }
$team = $projectDB->getDocument($teamId); $team = $projectDB->getDocument($teamId);
if(empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404); throw new Exception('Team not found', 404);
} }
if(!$projectDB->deleteDocument($invite->getUid())) { if (!$projectDB->deleteDocument($invite->getUid())) {
throw new Exception('Failed to remove membership from DB', 500); throw new Exception('Failed to remove membership from DB', 500);
} }
@ -643,7 +636,7 @@ $utopia->delete('/v1/teams/:teamId/memberships/:inviteId')
'sum' => $team->getAttribute('sum', 0) - 1, 'sum' => $team->getAttribute('sum', 0) - 1,
])); ]));
if(false === $team) { if (false === $team) {
throw new Exception('Failed saving team to DB', 500); throw new Exception('Failed saving team to DB', 500);
} }
@ -654,4 +647,4 @@ $utopia->delete('/v1/teams/:teamId/memberships/:inviteId')
$response->noContent(); $response->noContent();
} }
); );

View file

@ -25,11 +25,10 @@ $utopia->get('/v1/users')
->label('sdk.description', 'Get a list of all the project users. You can use the query params to filter your results.') ->label('sdk.description', 'Get a list of all the project users. You can use the query params to filter your results.')
->param('search', '', function () {return new Text(256);}, 'Search term to filter your list results.', true) ->param('search', '', function () {return new Text(256);}, 'Search term to filter your list results.', true)
->param('limit', 25, function () {return new Range(0, 100);}, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('limit', 25, function () {return new Range(0, 100);}, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0 , function () {return new Range(0, 2000);}, 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('offset', 0, function () {return new Range(0, 2000);}, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('orderType', 'ASC', function () {return new WhiteList(['ASC', 'DESC']);}, 'Order result by ASC or DESC order.', true) ->param('orderType', 'ASC', function () {return new WhiteList(['ASC', 'DESC']);}, 'Order result by ASC or DESC order.', true)
->action( ->action(
function($search, $limit, $offset, $orderType) use ($response, $projectDB, $providers) function ($search, $limit, $offset, $orderType) use ($response, $projectDB, $providers) {
{
$results = $projectDB->getCollection([ $results = $projectDB->getCollection([
'limit' => $limit, 'limit' => $limit,
'offset' => $offset, 'offset' => $offset,
@ -38,19 +37,19 @@ $utopia->get('/v1/users')
'orderCast' => 'int', 'orderCast' => 'int',
'search' => $search, 'search' => $search,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS '$collection='.Database::SYSTEM_COLLECTION_USERS,
], ],
]); ]);
$oauthKeys = []; $oauthKeys = [];
foreach($providers as $key => $provider) { foreach ($providers as $key => $provider) {
if(!$provider['enabled']) { if (!$provider['enabled']) {
continue; continue;
} }
$oauthKeys[] = 'oauth' . ucfirst($key); $oauthKeys[] = 'oauth'.ucfirst($key);
$oauthKeys[] = 'oauth' . ucfirst($key) . 'AccessToken'; $oauthKeys[] = 'oauth'.ucfirst($key).'AccessToken';
} }
$results = array_map(function ($value) use ($oauthKeys) { /* @var $value \Database\Document */ $results = array_map(function ($value) use ($oauthKeys) { /* @var $value \Database\Document */
@ -77,23 +76,22 @@ $utopia->get('/v1/users/:userId')
->label('sdk.description', 'Get user by its unique ID.') ->label('sdk.description', 'Get user by its unique ID.')
->param('userId', '', function () {return new UID();}, 'User unique ID.') ->param('userId', '', function () {return new UID();}, 'User unique ID.')
->action( ->action(
function($userId) use ($response, $projectDB, $providers) function ($userId) use ($response, $projectDB, $providers) {
{
$user = $projectDB->getDocument($userId); $user = $projectDB->getDocument($userId);
if(empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { if (empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
throw new Exception('User not found', 404); throw new Exception('User not found', 404);
} }
$oauthKeys = []; $oauthKeys = [];
foreach($providers as $key => $provider) { foreach ($providers as $key => $provider) {
if(!$provider['enabled']) { if (!$provider['enabled']) {
continue; continue;
} }
$oauthKeys[] = 'oauth' . ucfirst($key); $oauthKeys[] = 'oauth'.ucfirst($key);
$oauthKeys[] = 'oauth' . ucfirst($key) . 'AccessToken'; $oauthKeys[] = 'oauth'.ucfirst($key).'AccessToken';
} }
$response->json(array_merge($user->getArrayCopy(array_merge( $response->json(array_merge($user->getArrayCopy(array_merge(
@ -116,23 +114,22 @@ $utopia->get('/v1/users/:userId/prefs')
->label('sdk.description', 'Get user preferences by its unique ID.') ->label('sdk.description', 'Get user preferences by its unique ID.')
->param('userId', '', function () {return new UID();}, 'User unique ID.') ->param('userId', '', function () {return new UID();}, 'User unique ID.')
->action( ->action(
function($userId) use ($response, $projectDB) { function ($userId) use ($response, $projectDB) {
$user = $projectDB->getDocument($userId); $user = $projectDB->getDocument($userId);
if(empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { if (empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
throw new Exception('User not found', 404); throw new Exception('User not found', 404);
} }
$prefs = $user->getAttribute('prefs', ''); $prefs = $user->getAttribute('prefs', '');
if(empty($prefs)) { if (empty($prefs)) {
$prefs = '[]'; $prefs = '[]';
} }
try { try {
$prefs = json_decode($prefs, true); $prefs = json_decode($prefs, true);
} } catch (\Exception $error) {
catch (\Exception $error) {
throw new Exception('Failed to parse prefs', 500); throw new Exception('Failed to parse prefs', 500);
} }
@ -148,25 +145,25 @@ $utopia->get('/v1/users/:userId/sessions')
->label('sdk.description', 'Get user sessions list by its unique ID.') ->label('sdk.description', 'Get user sessions list by its unique ID.')
->param('userId', '', function () {return new UID();}, 'User unique ID.') ->param('userId', '', function () {return new UID();}, 'User unique ID.')
->action( ->action(
function($userId) use ($response, $projectDB) { function ($userId) use ($response, $projectDB) {
$user = $projectDB->getDocument($userId); $user = $projectDB->getDocument($userId);
if(empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { if (empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
throw new Exception('User not found', 404); throw new Exception('User not found', 404);
} }
$tokens = $user->getAttribute('tokens', []); $tokens = $user->getAttribute('tokens', []);
$reader = new Reader(__DIR__ . '/../db/GeoLite2/GeoLite2-Country.mmdb'); $reader = new Reader(__DIR__.'/../db/GeoLite2/GeoLite2-Country.mmdb');
$sessions = []; $sessions = [];
$index = 0; $index = 0;
$countries = Locale::getText('countries'); $countries = Locale::getText('countries');
foreach($tokens as $token) { /* @var $token Document */ foreach ($tokens as $token) { /* @var $token Document */
if(Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) { if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) {
continue; continue;
} }
$userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN'; $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN';
$dd = new DeviceDetector($userAgent); $dd = new DeviceDetector($userAgent);
@ -176,27 +173,26 @@ $utopia->get('/v1/users/:userId/sessions')
$dd->parse(); $dd->parse();
$sessions[$index] = [ $sessions[$index] = [
'$uid' => $token->getUid(), '$uid' => $token->getUid(),
'OS' => $dd->getOs(), 'OS' => $dd->getOs(),
'client' => $dd->getClient(), 'client' => $dd->getClient(),
'device' => $dd->getDevice(), 'device' => $dd->getDevice(),
'brand' => $dd->getBrand(), 'brand' => $dd->getBrand(),
'model' => $dd->getModel(), 'model' => $dd->getModel(),
'ip' => $token->getAttribute('ip', ''), 'ip' => $token->getAttribute('ip', ''),
'geo' => [], 'geo' => [],
]; ];
try { try {
$record = $reader->country($token->getAttribute('ip', '')); $record = $reader->country($token->getAttribute('ip', ''));
$sessions[$index]['geo']['isoCode'] = strtolower($record->country->isoCode); $sessions[$index]['geo']['isoCode'] = strtolower($record->country->isoCode);
$sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} } catch (\Exception $e) {
catch(\Exception $e) {
$sessions[$index]['geo']['isoCode'] = '--'; $sessions[$index]['geo']['isoCode'] = '--';
$sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown'); $sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown');
} }
$index++; ++$index;
} }
$response->json($sessions); $response->json($sessions);
@ -211,25 +207,25 @@ $utopia->get('/v1/users/:userId/logs')
->label('sdk.description', 'Get user activity logs list by its unique ID.') ->label('sdk.description', 'Get user activity logs list by its unique ID.')
->param('userId', '', function () {return new UID();}, 'User unique ID.') ->param('userId', '', function () {return new UID();}, 'User unique ID.')
->action( ->action(
function($userId) use ($response, $register, $projectDB, $project) { function ($userId) use ($response, $register, $projectDB, $project) {
$user = $projectDB->getDocument($userId); $user = $projectDB->getDocument($userId);
if(empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { if (empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
throw new Exception('User not found', 404); throw new Exception('User not found', 404);
} }
$ad = new \Audit\Adapter\MySQL($register->get('db')); $ad = new \Audit\Adapter\MySQL($register->get('db'));
$ad->setNamespace('app_' . $project->getUid()); $ad->setNamespace('app_'.$project->getUid());
$au = new \Audit\Audit($ad, $user->getUid(), $user->getAttribute('type'), '', '', ''); $au = new \Audit\Audit($ad, $user->getUid(), $user->getAttribute('type'), '', '', '');
$countries = Locale::getText('countries'); $countries = Locale::getText('countries');
$logs = $au->getLogsByUser($user->getUid(), $user->getAttribute('type', 0)); $logs = $au->getLogsByUser($user->getUid(), $user->getAttribute('type', 0));
$reader = new Reader(__DIR__ . '/../db/GeoLite2/GeoLite2-Country.mmdb'); $reader = new Reader(__DIR__.'/../db/GeoLite2/GeoLite2-Country.mmdb');
$output = []; $output = [];
foreach($logs as $i => &$log) { foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN'; $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
$dd = new DeviceDetector($log['userAgent']); $dd = new DeviceDetector($log['userAgent']);
@ -238,15 +234,15 @@ $utopia->get('/v1/users/:userId/logs')
$dd->parse(); $dd->parse();
$output[$i] = [ $output[$i] = [
'event' => $log['event'], 'event' => $log['event'],
'ip' => $log['ip'], 'ip' => $log['ip'],
'time' => strtotime($log['time']), 'time' => strtotime($log['time']),
'OS' => $dd->getOs(), 'OS' => $dd->getOs(),
'client' => $dd->getClient(), 'client' => $dd->getClient(),
'device' => $dd->getDevice(), 'device' => $dd->getDevice(),
'brand' => $dd->getBrand(), 'brand' => $dd->getBrand(),
'model' => $dd->getModel(), 'model' => $dd->getModel(),
'geo' => [], 'geo' => [],
]; ];
try { try {
@ -254,10 +250,9 @@ $utopia->get('/v1/users/:userId/logs')
$output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode); $output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode);
$output[$i]['geo']['country'] = $record->country->name; $output[$i]['geo']['country'] = $record->country->name;
$output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} } catch (\Exception $e) {
catch(\Exception $e) {
$output[$i]['geo']['isoCode'] = '--'; $output[$i]['geo']['isoCode'] = '--';
$output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');; $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');
} }
} }
@ -275,17 +270,17 @@ $utopia->post('/v1/users')
->param('password', '', function () {return new Password();}, 'User account password.') ->param('password', '', function () {return new Password();}, 'User account password.')
->param('name', '', function () {return new Text(100);}, 'User account name.', true) ->param('name', '', function () {return new Text(100);}, 'User account name.', true)
->action( ->action(
function($email, $password, $name) use ($response, $register, $projectDB, $providers) { function ($email, $password, $name) use ($response, $register, $projectDB, $providers) {
$profile = $projectDB->getCollection([ // Get user by email address $profile = $projectDB->getCollection([ // Get user by email address
'limit' => 1, 'limit' => 1,
'first' => true, 'first' => true,
'filters' => [ 'filters' => [
'$collection=' . Database::SYSTEM_COLLECTION_USERS, '$collection='.Database::SYSTEM_COLLECTION_USERS,
'email=' . $email 'email='.$email,
] ],
]); ]);
if(!empty($profile)) { if (!empty($profile)) {
throw new Exception('User already registered', 400); throw new Exception('User already registered', 400);
} }
@ -304,16 +299,16 @@ $utopia->post('/v1/users')
'reset' => false, 'reset' => false,
'name' => $name, 'name' => $name,
]); ]);
$oauthKeys = []; $oauthKeys = [];
foreach($providers as $key => $provider) { foreach ($providers as $key => $provider) {
if(!$provider['enabled']) { if (!$provider['enabled']) {
continue; continue;
} }
$oauthKeys[] = 'oauth' . ucfirst($key); $oauthKeys[] = 'oauth'.ucfirst($key);
$oauthKeys[] = 'oauth' . ucfirst($key) . 'AccessToken'; $oauthKeys[] = 'oauth'.ucfirst($key).'AccessToken';
} }
$response $response
@ -336,13 +331,12 @@ $utopia->patch('/v1/users/:userId/status')
->label('sdk.method', 'updateUserStatus') ->label('sdk.method', 'updateUserStatus')
->label('sdk.description', 'Update user status by its unique ID.') ->label('sdk.description', 'Update user status by its unique ID.')
->param('userId', '', function () {return new UID();}, 'User unique ID.') ->param('userId', '', function () {return new UID();}, 'User unique ID.')
->param('status', '', function () {return new WhiteList([Auth::USER_STATUS_ACTIVATED, Auth::USER_STATUS_BLOCKED, Auth::USER_STATUS_UNACTIVATED]);}, 'User Status code. To activate the user pass ' . Auth::USER_STATUS_ACTIVATED . ', to blocking the user pass ' . Auth::USER_STATUS_BLOCKED . ' and for disabling the user pass ' . Auth::USER_STATUS_UNACTIVATED) ->param('status', '', function () {return new WhiteList([Auth::USER_STATUS_ACTIVATED, Auth::USER_STATUS_BLOCKED, Auth::USER_STATUS_UNACTIVATED]);}, 'User Status code. To activate the user pass '.Auth::USER_STATUS_ACTIVATED.', to blocking the user pass '.Auth::USER_STATUS_BLOCKED.' and for disabling the user pass '.Auth::USER_STATUS_UNACTIVATED)
->action( ->action(
function($userId, $status) use ($response, $projectDB) function ($userId, $status) use ($response, $projectDB) {
{
$user = $projectDB->getDocument($userId); $user = $projectDB->getDocument($userId);
if(empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { if (empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
throw new Exception('User not found', 404); throw new Exception('User not found', 404);
} }
@ -350,7 +344,7 @@ $utopia->patch('/v1/users/:userId/status')
'status' => $status, 'status' => $status,
])); ]));
if(false === $user) { if (false === $user) {
throw new Exception('Failed saving user to DB', 500); throw new Exception('Failed saving user to DB', 500);
} }
@ -369,20 +363,18 @@ $utopia->delete('/v1/users/:userId/sessions/:session')
->param('userId', '', function () {return new UID();}, 'User unique ID.') ->param('userId', '', function () {return new UID();}, 'User unique ID.')
->param('sessionId', null, function () {return new UID();}, 'User unique session ID.') ->param('sessionId', null, function () {return new UID();}, 'User unique session ID.')
->action( ->action(
function($userId, $sessionId) use ($response, $request, $projectDB) function ($userId, $sessionId) use ($response, $request, $projectDB) {
{
$user = $projectDB->getDocument($userId); $user = $projectDB->getDocument($userId);
if(empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { if (empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
throw new Exception('User not found', 404); throw new Exception('User not found', 404);
} }
$tokens = $user->getAttribute('tokens', []); $tokens = $user->getAttribute('tokens', []);
foreach($tokens as $token) { /* @var $token Document */ foreach ($tokens as $token) { /* @var $token Document */
if($sessionId == $token->getUid()) { if ($sessionId == $token->getUid()) {
if (!$projectDB->deleteDocument($token->getUid())) {
if(!$projectDB->deleteDocument($token->getUid())) {
throw new Exception('Failed to remove token from DB', 500); throw new Exception('Failed to remove token from DB', 500);
} }
} }
@ -401,22 +393,21 @@ $utopia->delete('/v1/users/:userId/sessions')
->label('abuse-limit', 100) ->label('abuse-limit', 100)
->param('userId', '', function () {return new UID();}, 'User unique ID.') ->param('userId', '', function () {return new UID();}, 'User unique ID.')
->action( ->action(
function($userId) use ($response, $request, $projectDB) function ($userId) use ($response, $request, $projectDB) {
{
$user = $projectDB->getDocument($userId); $user = $projectDB->getDocument($userId);
if(empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { if (empty($user->getUid()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) {
throw new Exception('User not found', 404); throw new Exception('User not found', 404);
} }
$tokens = $user->getAttribute('tokens', []); $tokens = $user->getAttribute('tokens', []);
foreach($tokens as $token) { /* @var $token Document */ foreach ($tokens as $token) { /* @var $token Document */
if(!$projectDB->deleteDocument($token->getUid())) { if (!$projectDB->deleteDocument($token->getUid())) {
throw new Exception('Failed to remove token from DB', 500); throw new Exception('Failed to remove token from DB', 500);
} }
} }
$response->json(array('result' => 'success')); $response->json(array('result' => 'success'));
} }
); );