cloud.php
<?php
/* ------------ google auth2: client and ResourceOwneR Authorization
see https://developers.google.com/identity/protocols/oauth2/web-server
create client/project and oauth consent screen: https://console.cloud.google.com/apis
unter Anmeldedaten (credentials) oauth clients (nach erstelle neue Anmeldedaten)
Typ=Webanwedung (auto generated?) für online und cli
nur hier bekomme ich beim editieren: Autorisierte Weiterleitungs-URIs und gebe sowohl das callback (für cli) und das online web
Typ = Desktop funktionier für cli, cannot specify redirect uri, but is not necessary for cli
und json herunterladen und als client authorisierungs config ==> $client->setAuthConfig(...)
installation for google see googledrive.php
*/
require_once 'env.php';
dtzIni();
function dtzIni() {
define("DTLTZ", new DateTimeZone(date_default_timezone_get()));
define("DTLFMT", 'Y-m-d\TH:i:s');
for ($c=0; $c<3; $c++) {
$nE = exec("date '+%Y-%m-%dT%H:%M:%S%:z=%Z'");
$nT = new DateTime();
$nS = toLocalTst($nT) . $nT->format('P=e');
$nI = toIsoTst($nT);
if (str_starts_with($nE, $nI) and str_starts_with($nS, $nI))
return dbg1("local tst matching exec $nE, local $nS, iso $nI", ", php default zone", date_default_timezone_get());
}
err("local tst mismatch exec $nE, local $nS, iso $nI", ", php default zone", date_default_timezone_get(), "retry $c");
}
const APILIB = 0;
function toLocalTst($t) {
/* DateTime constructor uses timezone in timestring, and does not ignore it as date_create_from_format(DATE_RFC7231, $t) etc..
setTimezonew will change the time digits, so its the same moment, it even knows about summertime changes in the past years
so ->getOffset() it a method of DateTime not TimeZone!!!
*/
return is_null($t) ? null : (is_string($t) ? new DateTime($t) : $t)->setTimezone(DTLTZ)->format(DTLFMT);
}
function toIsoTst($t) { # ISO 8601 date (and time) including timezone offset
return is_null($t) ? null : (is_string($t) ? new DateTime($t) : $t)->setTimezone(DTLTZ)->format('c');
}
function fRewContClo($f) {
# if (! is_resource($f))
$sz = ftell($f);
dbg1("fRewContClo size $sz");
rewind($f);
$r = fread($f, $sz);
fclose($f);
return $r;
}
/*----- (very simple) pretty print xml -
each element on a separate line
intendation showing the nesting level
linefeed and intendations are inserted only before the < (tag begin char) text remains unchanged
-----*/
function xmlPP($s) {
return preg_replace_callback(
'=<[/?]?|[/?]?>='
, function ($m) {
static $lv = 0, $laO = 0;
if ($m[0] ==='<') {
$laO = 1;
$r = "\n" . str_repeat(' ', $lv++);
} elseif ($m[0] ==='<?') {
$r = "\n" . str_repeat(' ', $lv);
} elseif ($m[0] ==='</') {
$lv--;
$r = $laO ? '' : "\n" . str_repeat(' ', $lv);
$laO = 0;
} else {
$r = '';
if ($m[0] ==='/>') {
$lv--;
$laO = 0;
}
}
return $r . $m[0];
}
, $s);
}
class Cloud {
public const
ROWKYS = ['%' => ['name', 'type', 'id', 'modTst', 'creTst', 'size', 'owner', 'path', 'parent']]
, DIR = '%dir'
, ROOT = '~/'
, ROOTPA = '//'
;
public static function pathCat($pa, $nm, $ty) { # append a further level to a path, root = ~/, undeRoot = //, append / for directories
if (! $nm)
return $pa;
elseif ($pa === Cloud::ROOTPA)
return strPos($nm, '/') === false ? Cloud::ROOT : err("pathCat($pa, $nm,)");
else
return ($pa === Cloud::ROOT ? '' : $pa) . ($nm[-1] !== '/' ? $nm : substr($nm, 0, -1)) . ($ty === Cloud::DIR ? '/' : '');
# return $pa === Cloud::ROOTPA ? Cloud::ROOT : (($pa === Cloud::ROOT ? $nm : "$pa$nm") .
}
public static function pathPar($pa) { # return parent directory of the given path
return $pa === Cloud::ROOTPA ? err("no parent of " . Cloud::ROOTPA . " ") : ($pa === Cloud::ROOT ? Cloud::ROOTPA
: (($px=strrpos($pa, '/', -2)) ? substr($pa, 0, $px+1) : Cloud::ROOT));
}
public static function fn2valid($f, $isPath=false) { # in the nfts filesystem certain file names are not allowed, they are replace here by similair valid names
if ($isPath) # no leading/trailing spaces or dots
$r = preg_replace([';\s*/\s*;', ';\./;', ';\.$;'], ['/', '@/', '@'], trim($f));
else
($r = str_replace('/', '!', trim($f, ' '))) and str_ends_with($r, '.') and $r[-1] = '@';
$r = str_replace('*', '@', $r); // stars are illegal
return $r === '' ? '___' : $r;
}
}
class CloudFactory {
public $sapi, $cfgPa, $redirUri, $goAuthToken,$goAuthCode, $goAutState, $key;
public function __construct($at=null, $cp=null, $ru=null) {
$this->sapi = $at ?? php_sapi_name() === 'cli' ? 'Cli' : 'Sess';
$this->cfgPa = $cp ?? str_starts_with($rp = realpath(__FILE__), '/wkData/www/') ? '/ppKey/cloud/' # path prefix for locallly stored keys
: (str_starts_with($rp, '/home/ch45859/web/') ? substr($rp, 0, strpos($rp, '/', 18)) . 'private/cloudKey/'
: err("realpath(__FILE__) $rp not supported"));
$this->redirUri = $ru ?? $this->sapi === 'Sess' ? "$_SERVER[REQUEST_SCHEME]://$_SERVER[HTTP_HOST]$_SERVER[PHP_SELF]"
: (gethostname() === 'wk13' ? 'http://localhost/home/inf/php/goAuth2callback.php'
: err("no redirUri for host " . gethostname()));
if ($this->sapi === 'Sess')
$this->beginSess();
}
/*----- create a Cloud (googledrive or nextcloud)
$aa is either a key into cloudKey.csv or an [cloudid,type,host/client,user,pw,dir] the arguments for the constructor
*/
public function key() {
if (! isset($this->key)) {
$this->key = [];
$hdr = fgetcsv($fk = fopen("{$this->cfgPa}cloudKey.csv", 'r'));
while ($r = fgetcsv($fk))
if (count($r) > 2)
$this->key[$r[0]] = $r;
fclose($fk);
dbg1('got key', $this->key);
}
return $this->key;
}
public function make($aa) {
if (is_array($aa)) {
$bb = $aa;
} else {
$bb = $this->key()[$aa] ?? err("key $aa not found in {$this->cfgPa}cloudKey.csv");
}
if ($bb[1] === 'nc') {
require_once 'nextcloud.php';
return new NextCloud(...$bb);
} elseif ($bb[1] === 'godr') {
require_once 'googledrive.php';
$clc = new ("GoAuth$this->sapi")($this, ...$bb);
#var_dump($a);
$dr = new GoogleDrive($clc);
APILIB ? $dr->authorizeApiLib() : $dr->authorize();
# var_dump($dr);
return $dr;
} else {
err('cloud type $bb[1] not supported', $bb);
}
}
public function beginSess() {
if (session_status() !== PHP_SESSION_ACTIVE)
if (! session_start())
err('could not start session');
#echo "<br>\$_SESSION " . print_r($_SESSION);
#echo "<br>\$_GET " . print_r($_GET);
if (! (isset($_GET['code']) and isset($_GET['state']))) {
# echo "goauth does not seem redirect from google OAUTH2";
return;
}
if (! isset($_SESSION['goAuthOriginalState']))
err ('not set $_SESSION[goAuthOriginalState]');
if (! isset($_SESSION['goAuthOriginalGet']))
err ('not set $_SESSION[goAuthOriginalGet]');
if ($_GET['state'] !== $_SESSION['goAuthOriginalState'])
err('goautSessionStart state mismatch from google OAUTH2');
$this->goAuthCode = $_GET['code'];
$_GET = $_SESSION['goAuthOriginalGet'];
out("after swap goAuthCode $this->goAuthCode");
OUT("\$_GET after swap", $_GET);
unset($_SESSION['goAuthOriginalGet']);
unset($_SESSION['goAuthOriginalState']);
}
} # end class CloudFactory
/*----- GoAuthCli: Cloud Configuration (clc) for google drive in cli ----*/
class GoAuthCli {
public $cliBsnm, $rorBsnm
, $scopes =
[ 'https://www.googleapis.com/auth/drive.metadata.readonly' # drive readonly
, 'https://www.googleapis.com/auth/drive' # drive file update
, 'https://www.googleapis.com/auth/documents.readonly' # docs readonly
#, 'https://www.googleapis.com/auth/documents' # docs readWrite
];
public function __construct(public $fact, public $rorId, $ty, public $cliN, public $user) {
$this->cliBsnm = "goAuthCli$cliN"; // basename without filetype to client (application) infos/keys
$this->rorBsnm = "{$this->cliBsnm}Ror{$rorId}Token";
}
/*----- get the info for the client (application) from the Json exported by the google console -----*/
public function clientInfo($retData = true) {
$cliPa = "{$this->fact->cfgPa}$this->cliBsnm.json"; // path to client (application) infos/keys
return $retData ? json_decode(file_get_contents($cliPa), true) : $cliPa;
}
public function tokenGet() { # get (Authorization Access) Token for ResourceOwner for client from a file
$pa = "{$this->fact->cfgPa}$this->rorBsnm.json"; // path to resource owner tokens
if (! file_exists($pa))
return null;
$aT = json_decode(file_get_contents($pa), true);
return $aT;
}
public function tokenPut($tk) {
$pa = "{$this->fact->cfgPa}$this->rorBsnm.json"; // path to resource owner tokens
if (!file_exists(dirname($pa)))
mkdir(dirname($pa), 0700, true);
file_put_contents($pa, json_encode($tk));
out("written $this->rorId new resource owner token for $this->cliN to $pa");
}
function codeGet($authUrl, $state) {
/* we request from google a code to authorize the resource owner
because we are in the cli interface, we start a web browseer, and google will send the answer to an url
our url will write the received code into a file RRCPA, that we can read
*/
$pa = $this->fact->cfgPa . "goAuthCode.csv"; // path to resource owner tokens
unlink($pa);
if (0) {
out("opening google authorization: chronium $authUrl");
system("chromium --ozone-platform=wayland '$authUrl' &");
} else {
out("opening google authorization: xdg-open $authUrl");
system("xdg-open '$authUrl' &");
}
out("opened google authorization: $authUrl");
do {
sleep(2);
out("waiting for you to give google authorization in browser");
} while(! is_file($pa));
$cd = fgetcsv($f = fopen($pa, 'r'));
fclose($f);
if ($cd[0] !== $state)
err("state mismatch got $cd[0] not as expected $state");
out("found code $cd[1] scope $cd[2]");
unlink($pa);
return $cd[1];
}
function codePut() {
$pa = $this->fact->cfgPa . "goAuthCode.csv"; // path to resource owner tokens
$cd = [ $_GET['state'] ?? err("codePut state not defined", $_GET)
, $_GET['code'] ?? err("codePut code not defined", $_GET)
, $_GET['scope'] ?? err("codePut scope not defined", $_GET)];
out("codePut", $cd, ", writing to $pa");
fputcsv($f = fopen($pa, 'w'), $cd);
fclose($f);
}
} # end class GoAuthCli
class GoAuthSess extends GoAuthCli {
public function tokenGet() { # get Authorization Token for $cliRor (client . ResourceOwneR) from a file
return $_SESSION[$this->rorBsnm] ?? null;
}
public function tokenPut($tk) {
$_SESSION[$this->rorBsnm] = $tk;
}
function codeGet($authUrl, $state) {
/* we request from google at $authUrl a code to authorize the resource owner
thus we will redirect to $authUrl
our redirectURI is this same script, that will detect the code and state in $_GET (in goAuthBeginWeb()) and put it to $goAuthWeb RRCPA, that we can read
$state is a random string, that will be used to check, that we do not use a missdirected code
we must save for later restore the $_GET variables
*/
if (isset($this->fact->goAuthCode)) {
out("returniong code". $this->fact->goAuthCode);
return $this->fact->goAuthCode;
}
$_SESSION['goAuthOriginalState'] = $state;
$_SESSION['goAuthOriginalGet'] = $_GET;
header("location: $authUrl"); // redirect to
exit(); // exit the script, the redirect from google will start it new
}
function codePut() {
err( __METHOD__ . ' should not be called - work is done in Cloud->beginSess');
}
} # end class GoAuthSess