Merge branch 'master' of github.com:appwrite/appwrite into feature--flutter-support

This commit is contained in:
Eldad Fux
2020-04-08 16:50:31 +03:00
13 changed files with 259 additions and 54 deletions
+7 -2
View File
@@ -55,6 +55,10 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $
$route = $utopia->match($request);
if(!empty($route->getLabel('sdk.platform', [])) && empty($project->getId())) {
throw new Exception('Missing or unknown project ID', 400);
}
$referrer = $request->getServer('HTTP_REFERER', '');
$origin = parse_url($request->getServer('HTTP_ORIGIN', $referrer), PHP_URL_HOST);
$protocol = parse_url($request->getServer('HTTP_ORIGIN', $referrer), PHP_URL_SCHEME);
@@ -66,8 +70,9 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $
$selfDomain = new Domain(Config::getParam('domain'));
$endDomain = new Domain($origin);
Config::setParam('domainVerification', ($selfDomain->getRegisterable() === $endDomain->getRegisterable()));
Config::setParam('domainVerification',
($selfDomain->getRegisterable() === $endDomain->getRegisterable()));
/*
* Security Headers
*
+22 -6
View File
@@ -29,6 +29,9 @@ use GeoIp2\Database\Reader;
include_once __DIR__ . '/../shared/api.php';
$oauthDefaultSuccess = Config::getParam('protocol').'://'.Config::getParam('domain').'/auth/oauth2/success';
$oauthDefaultFailure = Config::getParam('protocol').'://'.Config::getParam('domain').'/auth/oauth2/failure';
$oauth2Keys = [];
$utopia->init(function() use (&$oauth2Keys) {
@@ -247,8 +250,8 @@ $utopia->get('/v1/account/sessions/oauth2/:provider')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->param('provider', '', function () { return new WhiteList(array_keys(Config::getParam('providers'))); }, 'OAuth2 Provider. Currently, supported providers are: ' . implode(', ', array_keys(array_filter(Config::getParam('providers'), function($node) {return (!$node['mock']);}))).'.')
->param('success', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt.')
->param('failure', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt.')
->param('success', $oauthDefaultSuccess, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt.', true)
->param('failure', $oauthDefaultFailure, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt.', true)
->action(
function ($provider, $success, $failure) use ($response, $request, $project) {
$protocol = Config::getParam('protocol');
@@ -275,7 +278,10 @@ $utopia->get('/v1/account/sessions/oauth2/:provider')
$oauth2 = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure]);
$response->redirect($oauth2->getLoginURL());
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($oauth2->getLoginURL());
}
);
@@ -292,8 +298,12 @@ $utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
function ($projectId, $provider, $code, $state) use ($response) {
$domain = Config::getParam('domain');
$protocol = Config::getParam('protocol');
$response->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?'
.http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?'
.http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state]));
}
);
@@ -309,7 +319,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.')
->param('state', '', function () { return new Text(2048); }, 'OAuth2 state params.', true)
->action(
function ($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit) {
function ($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit, $oauthDefaultSuccess) {
$protocol = Config::getParam('protocol');
$callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId();
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
@@ -469,7 +479,13 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect')
;
}
if($state['success'] === $oauthDefaultSuccess) { // Add keys for non-web platforms
$state['success'] = $state['success'].'/?end=true';
}
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null)
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE)
->redirect($state['success'])
+3 -39
View File
@@ -10,6 +10,7 @@ use Utopia\Validator\URL;
use Utopia\Cache\Cache;
use Utopia\Cache\Adapter\Filesystem;
use Appwrite\Resize\Resize;
use Appwrite\URL\URL as URLParse;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
@@ -265,7 +266,7 @@ $utopia->get('/v1/avatars/favicon')
$href = $link->getAttribute('href');
$rel = $link->getAttribute('rel');
$sizes = $link->getAttribute('sizes');
$absolute = unparse_url(array_merge(parse_url($url), parse_url($href)));
$absolute = URLParse::unparse(array_merge(parse_url($url), parse_url($href)));
switch (strtolower($rel)) {
case 'icon':
@@ -381,41 +382,4 @@ $utopia->get('/v1/avatars/qr')
->send('', $writer->writeString($text))
;
}
);
function unparse_url($parsed_url, $ommit = array())
{
if (isset($parsed_url['path']) && mb_substr($parsed_url['path'], 0, 1) !== '/') {
$parsed_url['path'] = '/'.$parsed_url['path'];
}
$p = array();
$p['scheme'] = isset($parsed_url['scheme']) ? $parsed_url['scheme'].'://' : '';
$p['host'] = isset($parsed_url['host']) ? $parsed_url['host'] : '';
$p['port'] = isset($parsed_url['port']) ? ':'.$parsed_url['port'] : '';
$p['user'] = isset($parsed_url['user']) ? $parsed_url['user'] : '';
$p['pass'] = isset($parsed_url['pass']) ? ':'.$parsed_url['pass'] : '';
$p['pass'] = ($p['user'] || $p['pass']) ? $p['pass'].'@' : '';
$p['path'] = isset($parsed_url['path']) ? $parsed_url['path'] : '';
$p['query'] = isset($parsed_url['query']) ? '?'.$parsed_url['query'] : '';
$p['fragment'] = isset($parsed_url['fragment']) ? '#'.$parsed_url['fragment'] : '';
if ($ommit) {
foreach ($ommit as $key) {
if (isset($p[ $key ])) {
$p[ $key ] = '';
}
}
}
return $p['scheme'].$p['user'].$p['pass'].$p['host'].$p['port'].$p['path'].$p['query'].$p['fragment'];
}
);
+31
View File
@@ -108,6 +108,37 @@ $utopia->get('/auth/recovery/reset')
->setParam('body', $page);
});
$utopia->get('/auth/oauth2/success')
->desc('Registration page')
->label('permission', 'public')
->label('scope', 'home')
->action(function () use ($layout) {
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
$layout
->setParam('title', APP_NAME)
->setParam('body', $page)
->setParam('header', [])
->setParam('footer', [])
;
});
$utopia->get('/auth/oauth2/failure')
->desc('Registration page')
->label('permission', 'public')
->label('scope', 'home')
->action(function () use ($layout) {
$page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml');
$layout
->setParam('title', APP_NAME)
->setParam('body', $page)
->setParam('header', [])
->setParam('footer', [])
;
});
$utopia->get('/error/:code')
->desc('Error page')
->label('permission', 'public')
+1 -1
View File
@@ -32,7 +32,7 @@ const APP_EMAIL_SECURITY = 'security@localhost.test'; // Default security email
const APP_USERAGENT = APP_NAME.'-Server v%s. Please report abuse at %s';
const APP_MODE_ADMIN = 'admin';
const APP_PAGING_LIMIT = 15;
const APP_CACHE_BUSTER = 55;
const APP_CACHE_BUSTER = 56;
const APP_VERSION_STABLE = '0.5.3';
const APP_STORAGE_UPLOADS = '/storage/uploads';
const APP_STORAGE_CACHE = '/storage/cache';
+1 -1
View File
@@ -6,7 +6,7 @@ $projectName = $this->getParam('projectName', '');
$projectURL = $this->getParam('projectURL', '');
?>
<section class="zone large margin-top-xl">
<section class="zone large margin-top padding">
<h1 class="margin-bottom">Error <?php echo $code; ?></h1>
<p><?php echo $message; ?></p>
+13
View File
@@ -0,0 +1,13 @@
<div class="zone large padding margin-top" id="message" style="display: none">
<h1 class="margin-bottom">Missing Redirect URL</h1>
<p>Your OAuth login flow is missing a redirect URL. Please check the <a href="https://<?php echo APP_DOMAIN; ?>/docs/account?platform=web&language=javascript#createOAuth2Session">OAuth docs</a> and send request for new session with a valid callback URL.</p>
</div>
<script>
setTimeout(function () {
document.getElementById('message').style.display = 'block';
}, 25);
window.location = 'appwrite-callback://'+window.location.search;
</script>
<hr />
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+4
View File
@@ -1,4 +1,8 @@
.padding {
padding: 30px;
}
.margin-end {
.func-margin-end(20px)!important;
}
+7 -3
View File
@@ -172,10 +172,14 @@ class MySQL extends Adapter
$st->execute();
$oldSignature = $st->fetch()['signature'];
$result = $st->fetch();
if ($signature === $oldSignature) {
return $data;
if($result && isset($result['signature'])) {
$oldSignature = $result['signature'];
if ($signature === $oldSignature) {
return $data;
}
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace Appwrite\URL;
class URL
{
/**
* Parse URL
*
* Take a URL string and split it to array parts
*
* @param $url string
*
* @return array
*/
static public function parse(string $url):array
{
$default = [
'scheme' => '',
'pass' => '',
'user' => '',
'host' => '',
'port' => null,
'path' => '',
'query' => '',
'fragment' => '',
];
return array_merge($default, parse_url($url));
}
/**
* Un-Parse URL
*
* Take URL parts and combine them to a valid string
*
* @param $url array
* @param $ommit array
*
* @return string
*/
static public function unparse(array $url, array $ommit = []):string
{
if (isset($url['path']) && mb_substr($url['path'], 0, 1) !== '/') {
$url['path'] = '/'.$url['path'];
}
$parts = [];
$parts['scheme'] = isset($url['scheme']) ? $url['scheme'].'://' : '';
$parts['host'] = isset($url['host']) ? $url['host'] : '';
$parts['port'] = isset($url['port']) ? ':'.$url['port'] : '';
$parts['user'] = isset($url['user']) ? $url['user'] : '';
$parts['pass'] = isset($url['pass']) ? ':'.$url['pass'] : '';
$parts['pass'] = ($parts['user'] || $parts['pass']) ? $parts['pass'].'@' : '';
$parts['path'] = isset($url['path']) ? $url['path'] : '';
$parts['query'] = isset($url['query']) && !empty($url['query']) ? '?'.$url['query'] : '';
$parts['fragment'] = isset($url['fragment']) ? '#'.$url['fragment'] : '';
if ($ommit) {
foreach ($ommit as $key) {
if (isset($parts[ $key ])) {
$parts[ $key ] = '';
}
}
}
return $parts['scheme'].$parts['user'].$parts['pass'].$parts['host'].$parts['port'].$parts['path'].$parts['query'].$parts['fragment'];
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace Appwrite\Tests;
use Appwrite\URL\URL;
use PHPUnit\Framework\TestCase;
class URLTest extends TestCase
{
public function testParse()
{
$url = URL::parse('https://appwrite.io:8080/path?query=string&param=value');
$this->assertIsArray($url);
$this->assertEquals('https', $url['scheme']);
$this->assertEquals('appwrite.io', $url['host']);
$this->assertEquals('8080', $url['port']);
$this->assertEquals('/path', $url['path']);
$this->assertEquals('query=string&param=value', $url['query']);
$url = URL::parse('https://appwrite.io');
$this->assertIsArray($url);
$this->assertEquals('https', $url['scheme']);
$this->assertEquals('appwrite.io', $url['host']);
$this->assertEquals(null, $url['port']);
$this->assertEquals('', $url['path']);
$this->assertEquals('', $url['query']);
}
public function testUnparse()
{
$url = URL::unparse([
'scheme' => 'https',
'host' => 'appwrite.io',
'port' => 8080,
'path' => '/path',
'query' => 'query=string&param=value',
]);
$this->assertIsString($url);
$this->assertEquals('https://appwrite.io:8080/path?query=string&param=value', $url);
$url = URL::unparse([
'scheme' => 'https',
'host' => 'appwrite.io',
'port' => null,
'path' => '/path',
'query' => 'query=string&param=value',
]);
$this->assertIsString($url);
$this->assertEquals('https://appwrite.io/path?query=string&param=value', $url);
$url = URL::unparse([
'scheme' => 'https',
'host' => 'appwrite.io',
'port' => null,
'path' => '',
'query' => '',
]);
$this->assertIsString($url);
$this->assertEquals('https://appwrite.io/', $url);
$url = URL::unparse([
'scheme' => 'https',
'host' => 'appwrite.io',
'port' => null,
'path' => '',
'fragment' => 'bottom',
]);
$this->assertIsString($url);
$this->assertEquals('https://appwrite.io/#bottom', $url);
$url = URL::unparse([
'scheme' => 'https',
'user' => 'eldad',
'pass' => 'fux',
'host' => 'appwrite.io',
'port' => null,
'path' => '',
'fragment' => 'bottom',
]);
$this->assertIsString($url);
$this->assertEquals('https://eldad:fux@appwrite.io/#bottom', $url);
}
}