Merge branch 'dev' of https://framagit.org/hubzilla/core into xdev_merge

This commit is contained in:
zotlabs 2018-08-12 15:10:19 -07:00
commit 2d29095348
11 changed files with 160 additions and 28 deletions

View File

@ -4,7 +4,7 @@ namespace Zotlabs\Identity;
class OAuth2Server extends \OAuth2\Server { class OAuth2Server extends \OAuth2\Server {
public function __construct(OAuth2Storage $storage, $config = []) { public function __construct(OAuth2Storage $storage, $config = null) {
if(! is_array($config)) { if(! is_array($config)) {
$config = [ $config = [
@ -19,7 +19,8 @@ class OAuth2Server extends \OAuth2\Server {
$this->addGrantType(new \OAuth2\GrantType\ClientCredentials($storage)); $this->addGrantType(new \OAuth2\GrantType\ClientCredentials($storage));
// Add the "Authorization Code" grant type (this is where the oauth magic happens) // Add the "Authorization Code" grant type (this is where the oauth magic happens)
$this->addGrantType(new \OAuth2\GrantType\AuthorizationCode($storage)); // Need to use OpenID\GrantType to return id_token (see:https://github.com/bshaffer/oauth2-server-php/issues/443)
$this->addGrantType(new \OAuth2\OpenID\GrantType\AuthorizationCode($storage));
$keyStorage = new \OAuth2\Storage\Memory( [ $keyStorage = new \OAuth2\Storage\Memory( [
'keys' => [ 'keys' => [

View File

@ -50,20 +50,67 @@ class OAuth2Storage extends \OAuth2\Storage\Pdo {
public function getUser($username) public function getUser($username)
{ {
$x = channelx_by_nick($username); $x = channelx_by_n($username);
if(! $x) { if(! $x) {
return false; return false;
} }
return( [ return( [
'webbie' => $x['channel_address'].'@'.\App::get_hostname(),
'zothash' => $x['channel_hash'],
'username' => $x['channel_address'], 'username' => $x['channel_address'],
'user_id' => $x['channel_id'], 'user_id' => $x['channel_id'],
'name' => $x['channel_name'],
'firstName' => $x['channel_name'], 'firstName' => $x['channel_name'],
'lastName' => '', 'lastName' => '',
'password' => 'NotARealPassword' 'password' => 'NotARealPassword'
] ); ] );
} }
public function scopeExists($scope) {
// Report that the scope is valid even if it's not.
// We will only return a very small subset no matter what.
// @TODO: Truly validate the scope
// see vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php and
// vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php
// for more info.
return true;
}
public function getDefaultScope($client_id=null) {
// Do not REQUIRE a scope
// see vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php and
// for more info.
return null;
}
public function getUserClaims ($user_id, $claims) {
// Populate the CLAIMS requested (if any).
// @TODO: create a more reasonable/comprehensive list.
// @TODO: present claims on the AUTHORIZATION screen
$userClaims = Array();
$claims = explode (' ', trim($claims));
$validclaims = Array ("name","preferred_username","zothash");
$claimsmap = Array (
"zotwebbie" => 'webbie',
"zothash" => 'zothash',
"name" => 'name',
"preferred_username" => "username"
);
$userinfo = $this->getUser($user_id);
foreach ($validclaims as $validclaim) {
if (in_array($validclaim,$claims)) {
$claimkey = $claimsmap[$validclaim];
$userClaims[$validclaim] = $userinfo[$claimkey];
} else {
$userClaims[$validclaim] = $validclaim;
}
}
$userClaims["sub"]=$user_id;
return $userClaims;
}
/** /**
* plaintext passwords are bad! Override this for your application * plaintext passwords are bad! Override this for your application
* *

View File

@ -332,7 +332,7 @@ class Site {
'$register_policy' => array('register_policy', t("Does this site allow new member registration?"), get_config('system','register_policy'), "", $register_choices), '$register_policy' => array('register_policy', t("Does this site allow new member registration?"), get_config('system','register_policy'), "", $register_choices),
'$invite_only' => array('invite_only', t("Invitation only"), get_config('system','invitation_only'), t("Only allow new member registrations with an invitation code. Above register policy must be set to Yes.")), '$invite_only' => array('invite_only', t("Invitation only"), get_config('system','invitation_only'), t("Only allow new member registrations with an invitation code. Above register policy must be set to Yes.")),
'$minimum_age' => array('minimum_age', t("Minimum age"), (x(get_config('system','minimum_age'))?get_config('system','minimum_age'):13), t("Minimum age (in years) for who may register on this site.")), '$minimum_age' => array('minimum_age', t("Minimum age"), (x(get_config('system','minimum_age'))?get_config('system','minimum_age'):13), t("Minimum age (in years) for who may register on this site.")),
'$access_policy' => array('access_policy', t("Which best describes the types of account offered by this hub?"), get_config('system','access_policy'), "This is displayed on the public server site list.", $access_choices), '$access_policy' => array('access_policy', t("Which best describes the types of account offered by this hub?"), get_config('system','access_policy'), t("This is displayed on the public server site list."), $access_choices),
'$register_text' => array('register_text', t("Register text"), htmlspecialchars(get_config('system','register_text'), ENT_QUOTES, 'UTF-8'), t("Will be displayed prominently on the registration page.")), '$register_text' => array('register_text', t("Register text"), htmlspecialchars(get_config('system','register_text'), ENT_QUOTES, 'UTF-8'), t("Will be displayed prominently on the registration page.")),
'$role' => $role, '$role' => $role,
'$frontpage' => array('frontpage', t("Site homepage to show visitors (default: login box)"), get_config('system','frontpage'), t("example: 'public' to show public stream, 'page/sys/home' to show a system webpage called 'home' or 'include:home.html' to include a file.")), '$frontpage' => array('frontpage', t("Site homepage to show visitors (default: login box)"), get_config('system','frontpage'), t("example: 'public' to show public stream, 'page/sys/home' to show a system webpage called 'home' or 'include:home.html' to include a file.")),

View File

@ -60,12 +60,16 @@ class Authorize extends \Zotlabs\Web\Controller {
$request = \OAuth2\Request::createFromGlobals(); $request = \OAuth2\Request::createFromGlobals();
$response = new \OAuth2\Response(); $response = new \OAuth2\Response();
// Note, "sub" field must match type and content. $user_id is used to populate - make sure it's a string.
$channel = channelx_by_n(local_channel());
$user_id = $channel["channel_id"];
// If the client is not registered, add to the database // If the client is not registered, add to the database
if (!$client = $storage->getClientDetails($client_id)) { if (!$client = $storage->getClientDetails($client_id)) {
$client_secret = random_string(16); // Until "Dynamic Client Registration" is pursued - allow new clients to assign their own secret in the REQUEST
$client_secret = (isset($_REQUEST["client_secret"])) ? $_REQUEST["client_secret"] : random_string(16);
// Client apps are registered per channel // Client apps are registered per channel
$user_id = local_channel(); $storage->setClientDetails($client_id, $client_secret, $redirect_uri, 'authorization_code', urldecode($_REQUEST["scope"]), $user_id);
$storage->setClientDetails($client_id, $client_secret, $redirect_uri, 'authorization_code', null, $user_id);
} }
if (!$client = $storage->getClientDetails($client_id)) { if (!$client = $storage->getClientDetails($client_id)) {
@ -83,7 +87,7 @@ class Authorize extends \Zotlabs\Web\Controller {
// print the authorization code if the user has authorized your client // print the authorization code if the user has authorized your client
$is_authorized = ($_POST['authorize'] === 'allow'); $is_authorized = ($_POST['authorize'] === 'allow');
$s->handleAuthorizeRequest($request, $response, $is_authorized, local_channel()); $s->handleAuthorizeRequest($request, $response, $is_authorized, $user_id);
if ($is_authorized) { if ($is_authorized) {
$code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=') + 5, 40); $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=') + 5, 40);
logger('Authorization Code: ' . $code); logger('Authorization Code: ' . $code);

View File

@ -10,10 +10,19 @@ class Oauth2 {
if(x($_POST,'remove')){ if(x($_POST,'remove')){
check_form_security_token_redirectOnErr('/settings/oauth2', 'settings_oauth2'); check_form_security_token_redirectOnErr('/settings/oauth2', 'settings_oauth2');
$name = ((x($_POST,'name')) ? escape_tags(trim($_POST['name'])) : '');
logger("REMOVE! ".$name." uid: ".local_channel());
$key = $_POST['remove']; $key = $_POST['remove'];
q("DELETE FROM tokens WHERE id='%s' AND uid=%d", q("DELETE FROM oauth_authorization_codes WHERE client_id='%s' AND user_id=%d",
dbesc($key), dbesc($name),
intval(local_channel())
);
q("DELETE FROM oauth_access_tokens WHERE client_id='%s' AND user_id=%d",
dbesc($name),
intval(local_channel())
);
q("DELETE FROM oauth_refresh_tokens WHERE client_id='%s' AND user_id=%d",
dbesc($name),
intval(local_channel()) intval(local_channel())
); );
goaway(z_root()."/settings/oauth2/"); goaway(z_root()."/settings/oauth2/");
@ -45,14 +54,15 @@ class Oauth2 {
grant_types = '%s', grant_types = '%s',
scope = '%s', scope = '%s',
user_id = %d user_id = %d
WHERE client_id='%s'", WHERE client_id='%s' and user_id = %s",
dbesc($name), dbesc($name),
dbesc($secret), dbesc($secret),
dbesc($redirect), dbesc($redirect),
dbesc($grant), dbesc($grant),
dbesc($scope), dbesc($scope),
intval(local_channel()), intval(local_channel()),
dbesc($name)); dbesc($name),
intval(local_channel()));
} else { } else {
$r = q("INSERT INTO oauth_clients (client_id, client_secret, redirect_uri, grant_types, scope, user_id) $r = q("INSERT INTO oauth_clients (client_id, client_secret, redirect_uri, grant_types, scope, user_id)
VALUES ('%s','%s','%s','%s','%s',%d)", VALUES ('%s','%s','%s','%s','%s',%d)",
@ -128,6 +138,18 @@ class Oauth2 {
dbesc(argv(3)), dbesc(argv(3)),
intval(local_channel()) intval(local_channel())
); );
$r = q("DELETE FROM oauth_access_tokens WHERE client_id = '%s' AND user_id = %d",
dbesc(argv(3)),
intval(local_channel())
);
$r = q("DELETE FROM oauth_authorization_codes WHERE client_id = '%s' AND user_id = %d",
dbesc(argv(3)),
intval(local_channel())
);
$r = q("DELETE FROM oauth_refresh_tokens WHERE client_id = '%s' AND user_id = %d",
dbesc(argv(3)),
intval(local_channel())
);
goaway(z_root()."/settings/oauth2/"); goaway(z_root()."/settings/oauth2/");
return; return;
} }
@ -135,7 +157,8 @@ class Oauth2 {
$r = q("SELECT oauth_clients.*, oauth_access_tokens.access_token as oauth_token, (oauth_clients.user_id = %d) AS my $r = q("SELECT oauth_clients.*, oauth_access_tokens.access_token as oauth_token, (oauth_clients.user_id = %d) AS my
FROM oauth_clients FROM oauth_clients
LEFT JOIN oauth_access_tokens ON oauth_clients.client_id=oauth_access_tokens.client_id LEFT JOIN oauth_access_tokens ON oauth_clients.client_id=oauth_access_tokens.client_id AND
oauth_clients.user_id=oauth_access_tokens.user_id
WHERE oauth_clients.user_id IN (%d,0)", WHERE oauth_clients.user_id IN (%d,0)",
intval(local_channel()), intval(local_channel()),
intval(local_channel()) intval(local_channel())

View File

@ -27,11 +27,11 @@ class Token extends \Zotlabs\Web\Controller {
$_SERVER['PHP_AUTH_PW'] = $password; $_SERVER['PHP_AUTH_PW'] = $password;
} }
} }
$storage = new OAuth2Storage(\DBA::$dba->db);
$s = new \Zotlabs\Identity\OAuth2Server(new OAuth2Storage(\DBA::$dba->db)); $s = new \Zotlabs\Identity\OAuth2Server($storage);
$request = \OAuth2\Request::createFromGlobals(); $request = \OAuth2\Request::createFromGlobals();
$s->handleTokenRequest($request)->send(); $response = $s->handleTokenRequest($request);
$response->send();
killme(); killme();
} }

View File

@ -0,0 +1,17 @@
<?php
namespace Zotlabs\Module;
use Zotlabs\Identity\OAuth2Storage;
class Userinfo extends \Zotlabs\Web\Controller {
function init() {
$s = new \Zotlabs\Identity\OAuth2Server(new OAuth2Storage(\DBA::$dba->db));
$request = \OAuth2\Request::createFromGlobals();
$s->handleUserInfoRequest($request)->send();
killme();
}
}

View File

@ -74,8 +74,11 @@ function markdown_to_bb($s, $use_zrl = false, $options = []) {
// Convert everything that looks like a link to a link // Convert everything that looks like a link to a link
if($use_zrl) { if($use_zrl) {
$s = str_replace(['[img', '/img]'], ['[zmg', '/zmg]'], $s); if (strpos($s,'[/img]') !== false) {
$s = preg_replace("/([^\]\=\{]|^)(https?\:\/\/)([a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]+)/ismu", '$1[zrl=$2$3]$2$3[/zrl]',$s); $s = preg_replace_callback("/\[img\](.*?)\[\/img\]/ism", 'use_zrl_cb_img', $s);
$s = preg_replace_callback("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", 'use_zrl_cb_img_x', $s);
}
$s = preg_replace_callback("/([^\]\=\{]|^)(https?\:\/\/)([a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]+)/ismu", 'use_zrl_cb_link',$s);
} }
else { else {
$s = preg_replace("/([^\]\=\{]|^)(https?\:\/\/)([a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]+)/ismu", '$1[url=$2$3]$2$3[/url]',$s); $s = preg_replace("/([^\]\=\{]|^)(https?\:\/\/)([a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]+)/ismu", '$1[url=$2$3]$2$3[/url]',$s);
@ -96,6 +99,41 @@ function markdown_to_bb($s, $use_zrl = false, $options = []) {
return $s; return $s;
} }
function use_zrl_cb_link($match) {
$res = '';
$is_zid = is_matrix_url(trim($match[0]));
if($is_zid)
$res = $match[1] . '[zrl=' . $match[2] . $match[3] . ']' . $match[2] . $match[3] . '[/zrl]';
else
$res = $match[1] . '[url=' . $match[2] . $match[3] . ']' . $match[2] . $match[3] . '[/url]';
return $res;
}
function use_zrl_cb_img($match) {
$res = '';
$is_zid = is_matrix_url(trim($match[1]));
if($is_zid)
$res = '[zmg]' . $match[1] . '[/zmg]';
else
$res = $match[0];
return $res;
}
function use_zrl_cb_img_x($match) {
$res = '';
$is_zid = is_matrix_url(trim($match[3]));
if($is_zid)
$res = '[zmg=' . $match[1] . 'x' . $match[2] . ']' . $match[3] . '[/zmg]';
else
$res = $match[0];
return $res;
}
/** /**
* @brief * @brief

View File

@ -175,7 +175,7 @@ function nav($template = 'default') {
$search_form_action = 'network'; $search_form_action = 'network';
break; break;
case 'channel': case 'channel':
$search_form_action = 'channel'; $search_form_action = 'channel/' . App::$profile['channel_address'];
break; break;
default: default:
$search_form_action = 'search'; $search_form_action = 'search';

View File

@ -89,9 +89,10 @@ web server platforms.
php.ini file - and with no hosting provider restrictions on the use of php.ini file - and with no hosting provider restrictions on the use of
exec() and proc_open(). exec() and proc_open().
- curl, gd (with at least jpeg and png support), mysqli, mbstring, xml, zip - curl, gd (with at least jpeg and png support), mysqli, mbstring, xml,
and openssl extensions. The imagick extension MAY be used instead of gd, xmlreader (FreeBSD), zip and openssl extensions. The imagick extension MAY be used
but is not required and MAY also be disabled via configuration option. instead of gd, but is not required and MAY also be disabled via
configuration option.
- some form of email server or email gateway such that PHP mail() works. - some form of email server or email gateway such that PHP mail() works.

View File

@ -4,8 +4,6 @@
</div> </div>
<div class="section-content-tools-wrapper"> <div class="section-content-tools-wrapper">
<form action="settings/oauth2" method="post" autocomplete="off">
<input type='hidden' name='form_security_token' value='{{$form_security_token}}'>
<div id="profile-edit-links"> <div id="profile-edit-links">
<ul> <ul>
@ -16,6 +14,9 @@
</div> </div>
{{foreach $apps as $app}} {{foreach $apps as $app}}
<form action="settings/oauth2" method="post" autocomplete="off">
<input type='hidden' name='form_security_token' value='{{$form_security_token}}'>
<input type='hidden' name='name' value='{{$app.client_id}}'>
<div class='oauthapp'> <div class='oauthapp'>
{{if $app.client_id}}<h4>{{$app.client_id}}</h4>{{else}}<h4>{{$noname}}</h4>{{/if}} {{if $app.client_id}}<h4>{{$app.client_id}}</h4>{{else}}<h4>{{$noname}}</h4>{{/if}}
{{if $app.my}} {{if $app.my}}
@ -28,8 +29,8 @@
<a href="{{$baseurl}}/settings/oauth2/delete/{{$app.client_id}}?t={{$form_security_token}}" title="{{$delete}}"><i class="fa fa-trash-o btn btn-outline-secondary"></i></a> <a href="{{$baseurl}}/settings/oauth2/delete/{{$app.client_id}}?t={{$form_security_token}}" title="{{$delete}}"><i class="fa fa-trash-o btn btn-outline-secondary"></i></a>
{{/if}} {{/if}}
</div> </div>
</form>
{{/foreach}} {{/foreach}}
</form>
</div> </div>
</div> </div>