Merge branch 'the-djmaze:master' into master
|
|
@ -3,13 +3,84 @@ PHP
|
|||
class Plugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
public function __construct();
|
||||
public function Name() : string;
|
||||
public function Description() : string;
|
||||
public function UseLangs(?bool $bLangs = null) : bool;
|
||||
public function Supported() : string;
|
||||
public function Init() : void;
|
||||
public function FilterAppDataPluginSection(bool $bAdmin, bool $bAuth, array &$aConfig) : void;
|
||||
protected function configMapping() : array;
|
||||
|
||||
/** Returns static::NAME */
|
||||
public function Name(): string;
|
||||
|
||||
/** Returns /README file contents or static::DESCRIPTION */
|
||||
public function Description(): string;
|
||||
|
||||
/** When $bLangs is boolean it sets the value, else returns current value */
|
||||
public function UseLangs(?bool $bLangs = null): bool;
|
||||
|
||||
/** When true the result is empty string, else the error message */
|
||||
public function Supported(): string;
|
||||
|
||||
/** Initialize settings */
|
||||
public function Init(): void;
|
||||
|
||||
public function FilterAppDataPluginSection(bool $bAdmin, bool $bAuth, array &$aConfig): void;
|
||||
|
||||
/** Returns array of all plugin Property options for use in Admin -> Extensions -> Plugin cog wheel */
|
||||
protected function configMapping(): array;
|
||||
|
||||
/** With this function you hook to an event
|
||||
* $sHookName see chapter "Hooks" below for available names
|
||||
* $sFunctionName the name of a function in this class
|
||||
*/
|
||||
final protected function addHook(string $sHookName, string $sFunctionName): self;
|
||||
|
||||
final protected function addCss(string $sFile, bool $bAdminScope = false): self;
|
||||
|
||||
final protected function addJs(string $sFile, bool $bAdminScope = false): self;
|
||||
|
||||
final protected function addTemplate(string $sFile, bool $bAdminScope = false): self;
|
||||
|
||||
final protected function addJsonHook(string $sActionName, string $sFunctionName): self;
|
||||
|
||||
/**
|
||||
* You may register your own service actions.
|
||||
* Url is like /?{actionname}/etc.
|
||||
* Predefined actions of \RainLoop\ServiceActions that can't be registered are:
|
||||
* - admin
|
||||
* - AdminAppData
|
||||
* - AppData
|
||||
* - Append
|
||||
* - Backup
|
||||
* - BadBrowser
|
||||
* - CspReport
|
||||
* - Css
|
||||
* - Json
|
||||
* - Lang
|
||||
* - Mailto
|
||||
* - NoCookie
|
||||
* - NoScript
|
||||
* - Ping
|
||||
* - Plugins
|
||||
* - ProxyExternal
|
||||
* - Raw
|
||||
* - Sso
|
||||
* - Upload
|
||||
* - UploadBackground
|
||||
* - UploadContacts
|
||||
*/
|
||||
final protected function addPartHook(string $sActionName, string $sFunctionName): self
|
||||
|
||||
final public function Config(): \RainLoop\Config\Plugin;
|
||||
final public function Manager(): \RainLoop\Plugins\Manager;
|
||||
final public function Path(): string;
|
||||
final public function ConfigMap(bool $flatten = false): array;
|
||||
|
||||
/**
|
||||
* Returns result of Actions->DefaultResponse($sFunctionName, $mData) or json_encode($mData)
|
||||
*/
|
||||
final protected function jsonResponse(string $sFunctionName, $mData): mixed;
|
||||
|
||||
final public function jsonParam(string $sKey, $mDefault = null): mixed;
|
||||
|
||||
final public function getUserSettings(): array;
|
||||
|
||||
final public function saveUserSettings(array $aSettings): bool;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -72,53 +143,53 @@ $Plugin->addHook('hook.name', 'functionName');
|
|||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Imap\ImapClient $oImapClient
|
||||
array &$aCredentials
|
||||
\MailSo\Imap\Settings $oSettings
|
||||
|
||||
### imap.after-connect
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Imap\ImapClient $oImapClient
|
||||
array $aCredentials
|
||||
\MailSo\Imap\Settings $oSettings
|
||||
|
||||
### imap.before-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Imap\ImapClient $oImapClient
|
||||
array &$aCredentials
|
||||
\MailSo\Imap\Settings $oSettings
|
||||
|
||||
### imap.after-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Imap\ImapClient $oImapClient
|
||||
bool $bSuccess
|
||||
array $aCredentials
|
||||
\MailSo\Imap\Settings $oSettings
|
||||
|
||||
## Sieve
|
||||
|
||||
### sieve.before-connect
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Sieve\ManageSieveClient $oSieveClient
|
||||
array &$aCredentials
|
||||
\MailSo\Sieve\SieveClient $oSieveClient
|
||||
\MailSo\Sieve\Settings $oSettings
|
||||
|
||||
### sieve.after-connect
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Sieve\ManageSieveClient $oSieveClient
|
||||
array $aCredentials
|
||||
\MailSo\Sieve\SieveClient $oSieveClient
|
||||
\MailSo\Sieve\Settings $oSettings
|
||||
|
||||
### sieve.before-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Sieve\ManageSieveClient $oSieveClient
|
||||
array &$aCredentials
|
||||
\MailSo\Sieve\SieveClient $oSieveClient
|
||||
\MailSo\Sieve\Settings $oSettings
|
||||
|
||||
### sieve.after-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Sieve\ManageSieveClient $oSieveClient
|
||||
\MailSo\Sieve\SieveClient $oSieveClient
|
||||
bool $bSuccess
|
||||
array $aCredentials
|
||||
\MailSo\Sieve\Settings $oSettings
|
||||
|
||||
## SMTP
|
||||
|
||||
|
|
@ -126,26 +197,26 @@ $Plugin->addHook('hook.name', 'functionName');
|
|||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Smtp\SmtpClient $oSmtpClient
|
||||
array &$aCredentials
|
||||
\MailSo\Smtp\Settings $oSettings
|
||||
|
||||
### smtp.after-connect
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Smtp\SmtpClient $oSmtpClient
|
||||
array $aCredentials
|
||||
\MailSo\Smtp\Settings $oSettings
|
||||
|
||||
### smtp.before-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Smtp\SmtpClient $oSmtpClient
|
||||
array &$aCredentials
|
||||
\MailSo\Smtp\Settings $oSettings
|
||||
|
||||
### smtp.after-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Smtp\SmtpClient $oSmtpClient
|
||||
bool $bSuccess
|
||||
array $aCredentials
|
||||
\MailSo\Smtp\Settings $oSettings
|
||||
|
||||
## Folders
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Auto Domain Selection',
|
||||
VERSION = '2.8',
|
||||
REQUIRED = '2.8.0',
|
||||
VERSION = '2.9',
|
||||
RELEASE = '2022-11-11',
|
||||
REQUIRED = '2.21.0',
|
||||
CATEGORY = 'General',
|
||||
DESCRIPTION = 'Sets the IMAP/SMTP host based on the user\'s login';
|
||||
|
||||
|
|
@ -32,20 +33,20 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
/**
|
||||
* This function detects the IMAP Host, and if it is set to 'auto', replaces it with the MX or email domain.
|
||||
*/
|
||||
public function FilterImapCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Imap\ImapClient $oImapClient, array &$aImapCredentials)
|
||||
public function FilterImapCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Imap\ImapClient $oImapClient, \MailSo\Imap\Settings $oSettings)
|
||||
{
|
||||
// Check for mail.$DOMAIN as entered value in RL settings
|
||||
if (!empty($aImapCredentials['Host']) && 'auto' === $aImapCredentials['Host'])
|
||||
if ('auto' === $oSettings->host)
|
||||
{
|
||||
$domain = \substr(\strrchr($oAccount->Email(), '@'), 1);
|
||||
$mxhosts = array();
|
||||
if (\getmxrr($domain, $mxhosts) && $mxhosts)
|
||||
{
|
||||
$aImapCredentials['Host'] = $mxhosts[0];
|
||||
$oSettings->host = $mxhosts[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
$aImapCredentials['Host'] = $this->imap_prefix.$domain;
|
||||
$oSettings->host = $this->imap_prefix.$domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,20 +54,20 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
/**
|
||||
* This function detects the SMTP Host, and if it is set to 'auto', replaces it with the MX or email domain.
|
||||
*/
|
||||
public function FilterSmtpCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Smtp\SmtpClient $oSmtpClient, array &$aSmtpCredentials)
|
||||
public function FilterSmtpCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Smtp\SmtpClient $oSmtpClient, \MailSo\Smtp\Settings $oSettings)
|
||||
{
|
||||
// Check for mail.$DOMAIN as entered value in RL settings
|
||||
if (!empty($aSmtpCredentials['Host']) && 'auto' === $aSmtpCredentials['Host'])
|
||||
if ('auto' === $oSettings->host)
|
||||
{
|
||||
$domain = \substr(\strrchr($oAccount->Email(), '@'), 1);
|
||||
$mxhosts = array();
|
||||
if (\getmxrr($domain, $mxhosts) && $mxhosts)
|
||||
{
|
||||
$aSmtpCredentials['Host'] = $mxhosts[0];
|
||||
$oSettings->host = $mxhosts[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
$aSmtpCredentials['Host'] = $this->smtp_prefix . $domain;
|
||||
$oSettings->host = $this->smtp_prefix . $domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
plugins/avatars/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 RainLoop Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
100
plugins/avatars/avatars.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
(rl => {
|
||||
// if (rl.settings.get('Nextcloud'))
|
||||
const
|
||||
queue = [],
|
||||
avatars = new Map,
|
||||
templateId = 'MailMessageView',
|
||||
getAvatarUid = msg => {
|
||||
let from = msg.from[0],
|
||||
bimi = 'pass' == from.dkimStatus ? 1 : 0;
|
||||
return `${bimi}/${from.email.toLowerCase()}`;
|
||||
},
|
||||
getAvatar = msg => avatars.get(getAvatarUid(msg)),
|
||||
runQueue = (() => {
|
||||
let item = queue.shift();
|
||||
while (item) {
|
||||
let url = getAvatar(item[0]);
|
||||
if (url) {
|
||||
item[1](url);
|
||||
item = queue.shift();
|
||||
} else {
|
||||
// TODO: fetch vCard from Nextcloud contacts
|
||||
// let cfg = rl.settings.get('Nextcloud'),
|
||||
|
||||
let from = item[0].from[0];
|
||||
rl.pluginRemoteRequest((iError, data) => {
|
||||
if (!iError && data?.Result.type) {
|
||||
url = `data:${data.Result.type};base64,${data.Result.data}`;
|
||||
avatars.set(getAvatarUid(item[0]), url);
|
||||
item[1](url);
|
||||
}
|
||||
runQueue();
|
||||
}, 'Avatar', {
|
||||
bimi: 'pass' == from.dkimStatus ? 1 : 0,
|
||||
email: from.email
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).debounce(1000);
|
||||
|
||||
ko.bindingHandlers.fromPic = {
|
||||
init: (element, self, dummy, msg) => {
|
||||
if (msg) {
|
||||
let url = getAvatar(msg),
|
||||
fn = url=>{element.src = url};
|
||||
if (url) {
|
||||
fn(url);
|
||||
} else {
|
||||
queue.push([msg, fn]);
|
||||
runQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addEventListener('rl-view-model.create', e => {
|
||||
if (templateId === e.detail.viewModelTemplateID) {
|
||||
|
||||
const
|
||||
template = document.getElementById(templateId),
|
||||
messageItemHeader = template.content.querySelector('.messageItemHeader');
|
||||
|
||||
if (messageItemHeader) {
|
||||
messageItemHeader.prepend(Element.fromHTML(
|
||||
`<img class="fromPic" data-bind="visible: viewUserPicVisible, attr: {'src': viewUserPic() }">`
|
||||
));
|
||||
}
|
||||
|
||||
let view = e.detail;
|
||||
view.viewUserPic = ko.observable('');
|
||||
view.viewUserPicVisible = ko.observable(false);
|
||||
|
||||
view.message.subscribe(msg => {
|
||||
view.viewUserPicVisible(false);
|
||||
if (msg) {
|
||||
let url = getAvatar(msg),
|
||||
fn = url => {
|
||||
view.viewUserPic(url);
|
||||
view.viewUserPicVisible(true);
|
||||
};
|
||||
if (url) {
|
||||
fn(url);
|
||||
} else {
|
||||
// let from = msg.from[0], bimi = 'pass' == from.dkimStatus ? 1 : 0;
|
||||
// view.viewUserPic(`?Avatar/${bimi}/${encodeURIComponent(from.email)}`);
|
||||
// view.viewUserPicVisible(true);
|
||||
queue.push([msg, fn]);
|
||||
runQueue();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ('MailMessageList' === e.detail.viewModelTemplateID) {
|
||||
document.getElementById('MailMessageList').content.querySelector('.messageCheckbox')
|
||||
.append(Element.fromHTML(`<img class="fromPic" data-bind="fromPic:$data">`));
|
||||
}
|
||||
});
|
||||
|
||||
})(window.rl);
|
||||
BIN
plugins/avatars/images/empty-contact.png
Normal file
|
After Width: | Height: | Size: 665 B |
BIN
plugins/avatars/images/services/amazon.com.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
plugins/avatars/images/services/apple.com.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
plugins/avatars/images/services/asana.com.png
Normal file
|
After Width: | Height: | Size: 995 B |
BIN
plugins/avatars/images/services/battle.net.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
plugins/avatars/images/services/blizzard.com.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
plugins/avatars/images/services/ea.com.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
plugins/avatars/images/services/ebay.com.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
plugins/avatars/images/services/facebook.com.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
plugins/avatars/images/services/github.com.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
plugins/avatars/images/services/google.com.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
plugins/avatars/images/services/microsoft.com.png
Normal file
|
After Width: | Height: | Size: 382 B |
BIN
plugins/avatars/images/services/onlive.com.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
plugins/avatars/images/services/paypal.com.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
plugins/avatars/images/services/skype.com.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
plugins/avatars/images/services/steampowered.com.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
plugins/avatars/images/services/ted.com.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
plugins/avatars/images/services/twitter.com.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
plugins/avatars/images/services/youtube.com.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
196
plugins/avatars/index.php
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
const
|
||||
NAME = 'Avatar',
|
||||
AUTHOR = 'SnappyMail',
|
||||
URL = 'https://snappymail.eu/',
|
||||
VERSION = '1.0',
|
||||
RELEASE = '2022-11-23',
|
||||
REQUIRED = '2.22.0',
|
||||
CATEGORY = 'Contacts',
|
||||
LICENSE = 'MIT',
|
||||
DESCRIPTION = 'Show photo of sender in message and messages list (supports BIMI and Gravatar, Contacts is still TODO)';
|
||||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addCss('style.css');
|
||||
$this->addJs('avatars.js');
|
||||
$this->addJsonHook('Avatar', 'DoAvatar');
|
||||
$this->addPartHook('Avatar', 'ServiceAvatar');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST method handling
|
||||
*/
|
||||
public function DoAvatar() : array
|
||||
{
|
||||
$bBimi = !empty($this->jsonParam('bimi'));
|
||||
$sEmail = $this->jsonParam('email');
|
||||
$aResult = $this->getAvatar(\urldecode($sEmail), !empty($sEmail));
|
||||
if ($aResult) {
|
||||
$aResult = [
|
||||
'type' => $aResult[0],
|
||||
'data' => \base64_encode($aResult[1])
|
||||
];
|
||||
}
|
||||
return $this->jsonResponse(__FUNCTION__, $aResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /?Avatar/${bimi}/${from.email}
|
||||
* Not fond of this idea because email address is exposed
|
||||
* Maybe use btoa(from.email) or Crypto.subtle.encrypt({name:'AES-GCM',iv:''}, token, from.email)
|
||||
*/
|
||||
// public function ServiceAvatar(...$aParts)
|
||||
public function ServiceAvatar(string $sServiceName, string $sBimi, string $sEmail)
|
||||
{
|
||||
$aResult = $this->getAvatar(\urldecode($sEmail), !empty($sEmail));
|
||||
if ($aResult) {
|
||||
\header('Content-Type: '.$aResult[0]);
|
||||
echo $aResult[1];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function configMapping() : array
|
||||
{
|
||||
return array(
|
||||
\RainLoop\Plugins\Property::NewInstance('bimi')->SetLabel('Use BIMI (https://bimigroup.org/)')
|
||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
|
||||
->SetDefaultValue(false),
|
||||
\RainLoop\Plugins\Property::NewInstance('gravatar')->SetLabel('Use Gravatar')
|
||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
|
||||
->SetDefaultValue(false)
|
||||
);
|
||||
}
|
||||
|
||||
private function getAvatar(string $sEmail, bool $bBimi) : ?array
|
||||
{
|
||||
if (!\strpos($sEmail, '@')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
$oActions->verifyCacheByKey($sEmail);
|
||||
|
||||
$aResult = null;
|
||||
|
||||
$sAsciiEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
||||
$sEmailId = \sha1(\strtolower($sAsciiEmail));
|
||||
|
||||
$sFile = \APP_PRIVATE_DATA . 'avatars/' . $sEmailId;
|
||||
$aFiles = \glob("{$sFile}.*");
|
||||
if ($aFiles) {
|
||||
$aResult = [
|
||||
\mime_content_type($aFiles[0]),
|
||||
\file_get_contents($aFiles[0])
|
||||
];
|
||||
$oActions->cacheByKey($sEmail);
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
// TODO: lookup contacts vCard and return PHOTO value
|
||||
/*
|
||||
if (!$aResult) {
|
||||
$oAccount = $oActions->getAccountFromToken();
|
||||
if ($oAccount) {
|
||||
$oAddressBookProvider = $oActions->AddressBookProvider($oAccount);
|
||||
if ($oAddressBookProvider) {
|
||||
$oContact = $oAddressBookProvider->GetContactByEmail($sEmail);
|
||||
if ($oContact && $oContact->vCard && $oContact->vCard['PHOTO']) {
|
||||
$aResult = [
|
||||
'text/vcard',
|
||||
$oContact->vCard
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (!$aResult) {
|
||||
$sDomain = \explode('@', $sEmail);
|
||||
$sDomain = \array_pop($sDomain);
|
||||
|
||||
$aUrls = [];
|
||||
|
||||
if ($this->Config()->Get('plugin', 'bimi', false)) {
|
||||
$BIMI = $bBimi ? \SnappyMail\DNS::BIMI($sDomain) : null;
|
||||
if ($BIMI) {
|
||||
$aUrls[] = $BIMI;
|
||||
// $aResult = ['text/uri-list', $BIMI];
|
||||
\SnappyMail\Log::debug('Avatar', "BIMI {$sDomain} for {$sUrl}");
|
||||
} else {
|
||||
\SnappyMail\Log::notice('Avatar', "BIMI 404 for {$sDomain}");
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->Config()->Get('plugin', 'gravatar', false)) {
|
||||
$aUrls[] = 'http://gravatar.com/avatar/'.\md5(\strtolower($sAsciiEmail)).'?s=80&d=404';
|
||||
}
|
||||
|
||||
foreach ($aUrls as $sUrl) {
|
||||
if ($aResult = static::getUrl($sUrl)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($aResult) {
|
||||
if (!\is_dir(\APP_PRIVATE_DATA . 'avatars')) {
|
||||
\mkdir(\APP_PRIVATE_DATA . 'avatars', 0700);
|
||||
}
|
||||
\file_put_contents(
|
||||
$sFile . \SnappyMail\File\MimeType::toExtension($aResult[0]),
|
||||
$aResult[1]
|
||||
);
|
||||
}
|
||||
|
||||
if (!$aResult) {
|
||||
$aServices = [
|
||||
"services/{$sDomain}",
|
||||
'services/' . \preg_replace('/^.+\\.([^.]+\\.[^.]+)$/D', '$1', $sDomain),
|
||||
'services/' . \preg_replace('/^(.+\\.)?(paypal\\.[a-z][a-z])$/D', 'paypal.com', $sDomain),
|
||||
'empty-contact' // DATA_IMAGE_USER_DOT_PIC
|
||||
];
|
||||
foreach ($aServices as $service) {
|
||||
if (\file_exists(__DIR__ . "/images/{$service}.png")) {
|
||||
$aResult = [
|
||||
'image/png',
|
||||
\file_get_contents(__DIR__ . "/images/{$service}.png")
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$oActions->cacheByKey($sEmail);
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
private static function getUrl(string $sUrl) : ?array
|
||||
{
|
||||
$oHTTP = \SnappyMail\HTTP\Request::factory(/*'socket' or 'curl'*/);
|
||||
$oHTTP->proxy = \RainLoop\Api::Config()->Get('labs', 'curl_proxy', '');
|
||||
$oHTTP->proxy_auth = \RainLoop\Api::Config()->Get('labs', 'curl_proxy_auth', '');
|
||||
$oHTTP->max_response_kb = 0;
|
||||
$oHTTP->timeout = 15; // timeout in seconds.
|
||||
$oResponse = $oHTTP->doRequest('GET', $sUrl);
|
||||
if ($oResponse) {
|
||||
if (200 === $oResponse->status && \str_starts_with($oResponse->getHeader('content-type'), 'image/')) {
|
||||
return [
|
||||
$oResponse->getHeader('content-type'),
|
||||
$oResponse->body
|
||||
];
|
||||
}
|
||||
\SnappyMail\Log::notice('Avatar', "error {$oResponse->status} for {$sUrl}");
|
||||
} else {
|
||||
\SnappyMail\Log::warning('Avatar', "failed for {$sUrl}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
25
plugins/avatars/style.css
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
.messageView .fromPic {
|
||||
border: 1px solid var(--border-color, #ddd);
|
||||
float: left;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 0 4px 0 0;
|
||||
padding: 1px;
|
||||
border-radius: var(--border-radius, 5px);
|
||||
}
|
||||
/*
|
||||
.checkboxMessage {
|
||||
background: #000 no-repeat center / contain;
|
||||
background: #000 no-repeat right / 32px;
|
||||
width:68px
|
||||
}
|
||||
*/
|
||||
#rl-app .messageCheckbox {
|
||||
padding-right:0;
|
||||
white-space:nowrap;
|
||||
}
|
||||
.messageCheckbox .fromPic {
|
||||
margin: 0 0 -0.4em 0.4em;
|
||||
height: 1.5em;
|
||||
max-width: 1.5em;
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ class DemoAccountPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
if (!$bAdmin && \is_array($aResult) && empty($aResult['Auth'])) {
|
||||
$aResult['DevEmail'] = $this->Config()->Get('plugin', 'email', $aResult['DevEmail']);
|
||||
$aResult['DevPassword'] = APP_DUMMY;
|
||||
$aResult['DevPassword'] = '********';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,13 @@ class DemoStorage extends \RainLoop\Providers\Storage\FileStorage
|
|||
if (!\is_dir($sDataPath) && \mkdir($sDataPath, 0700, true)) {
|
||||
\file_put_contents("{$sDataPath}/settings",'{"RemoveColors":true,"ListInlineAttachments":true}');
|
||||
if (\mkdir($sDataPath.'/.gnupg/private-keys-v1.d', 0700, true)) {
|
||||
// AES
|
||||
\file_put_contents("{$sDataPath}/.gnupg/private-keys-v1.d/3106F4281F98D820114228FEF16B5BA0D78AA005.key",file_get_contents("{$this->sDataPath}/demo.pgp/.gnupg/private-keys-v1.d/3106F4281F98D820114228FEF16B5BA0D78AA005.key"));
|
||||
\file_put_contents("{$sDataPath}/.gnupg/private-keys-v1.d/82CA239C482423D364BFD6DFC3E400B3B98AD66F.key",file_get_contents("{$this->sDataPath}/demo.pgp/.gnupg/private-keys-v1.d/82CA239C482423D364BFD6DFC3E400B3B98AD66F.key"));
|
||||
// ECC
|
||||
\file_put_contents("{$sDataPath}/.gnupg/private-keys-v1.d/5A1A6C7310D0508C68E8E74F15068301E83FD1AE.key",file_get_contents("{$this->sDataPath}/demo.pgp/.gnupg/private-keys-v1.d/5A1A6C7310D0508C68E8E74F15068301E83FD1AE.key"));
|
||||
\file_put_contents("{$sDataPath}/.gnupg/private-keys-v1.d/886921A7E06BE56F8E8C51797BB476BB26DF21BF.key",file_get_contents("{$this->sDataPath}/demo.pgp/.gnupg/private-keys-v1.d/886921A7E06BE56F8E8C51797BB476BB26DF21BF.key"));
|
||||
|
||||
\file_put_contents("{$sDataPath}/.gnupg/pubring.kbx",file_get_contents("{$this->sDataPath}/demo.pgp/.gnupg/pubring.kbx"));
|
||||
\file_put_contents("{$sDataPath}/.gnupg/trustdb.gpg",file_get_contents("{$this->sDataPath}/demo.pgp/.gnupg/trustdb.gpg"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -313,6 +313,12 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
|
|||
return $aResult;
|
||||
}
|
||||
|
||||
public function GetContactByEmail(string $sEmail) : ?Contact;
|
||||
{
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
|
||||
public function GetContactByID($mID, bool $bIsStrID = false) : ?Contact
|
||||
{
|
||||
if ($bIsStrID) {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ class KolabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Kolab',
|
||||
VERSION = '2.0',
|
||||
RELEASE = '2022-09-06',
|
||||
VERSION = '2.1',
|
||||
RELEASE = '2022-11-23',
|
||||
CATEGORY = 'Contacts',
|
||||
DESCRIPTION = 'Use an Address Book of Kolab.',
|
||||
REQUIRED = '2.18.0';
|
||||
REQUIRED = '2.22.0';
|
||||
|
||||
public function Init() : void
|
||||
{
|
||||
|
|
|
|||
20
plugins/login-external-sso/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 RainLoop Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
56
plugins/login-external-sso/index.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
class LoginExternalSsoPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
const
|
||||
NAME = 'Login External SSO',
|
||||
AUTHOR = 'SnappyMail',
|
||||
URL = 'https://snappymail.eu/',
|
||||
VERSION = '1.0',
|
||||
RELEASE = '2022-11-11',
|
||||
REQUIRED = '2.21.0',
|
||||
CATEGORY = 'Login',
|
||||
LICENSE = 'MIT',
|
||||
DESCRIPTION = 'Login with $_POST "Email", "Password" and "SsoKey". It returns an SSO hash to use with "?Sso&hash="';
|
||||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addPartHook('ExternalSso', 'ServiceExternalSso');
|
||||
}
|
||||
|
||||
public function ServiceExternalSso() : string
|
||||
{
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
$oActions->Http()->ServerNoCache();
|
||||
$sKey = $this->Config()->Get('plugin', 'key', '');
|
||||
$sEmail = isset($_POST['Email']) ? $_POST['Email'] : '';
|
||||
$sPassword = isset($_POST['Password']) ? $_POST['Password'] : '';
|
||||
if ($sEmail && $sPassword && $sKey && isset($_POST['SsoKey']) && $_POST['SsoKey'] == $sKey) {
|
||||
$sResult = \RainLoop\Api::CreateUserSsoHash($sEmail, $sPassword);
|
||||
if (isset($_POST['Output']) && 'json' === \strtolower($_POST['Output'])) {
|
||||
\header('Content-Type: application/json; charset=utf-8');
|
||||
echo \json_encode(array(
|
||||
'Action' => 'ExternalSso',
|
||||
'Result' => $sResult
|
||||
));
|
||||
} else {
|
||||
\header('Content-Type: text/plain');
|
||||
echo $sResult;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function configMapping() : array
|
||||
{
|
||||
return array(
|
||||
// Was application.ini external_sso_key
|
||||
\RainLoop\Plugins\Property::NewInstance('key')->SetLabel('SSO key')
|
||||
->SetDefaultValue(''),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
20
plugins/login-external/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 RainLoop Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
68
plugins/login-external/index.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
class LoginExternalPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
const
|
||||
NAME = 'Login External',
|
||||
AUTHOR = 'SnappyMail',
|
||||
URL = 'https://snappymail.eu/',
|
||||
VERSION = '1.0',
|
||||
RELEASE = '2022-11-11',
|
||||
REQUIRED = '2.21.0',
|
||||
CATEGORY = 'Login',
|
||||
LICENSE = 'MIT',
|
||||
DESCRIPTION = 'Login with $_POST["Email"] and $_POST["Password"] from anywhere';
|
||||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addPartHook('ExternalLogin', 'ServiceExternalLogin');
|
||||
}
|
||||
|
||||
public function ServiceExternalLogin() : bool
|
||||
{
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
$oActions->Http()->ServerNoCache();
|
||||
|
||||
$oAccount = null;
|
||||
$oException = null;
|
||||
|
||||
$sEmail = isset($_POST['Email']) ? $_POST['Email'] : '';
|
||||
$sPassword = isset($_POST['Password']) ? $_POST['Password'] : '';
|
||||
|
||||
try
|
||||
{
|
||||
$oAccount = $oActions->LoginProcess($sEmail, $sPassword);
|
||||
if ($oAccount instanceof \RainLoop\Model\MainAccount) {
|
||||
$oActions->SetAuthToken($oAccount);
|
||||
} else {
|
||||
$oAccount = null;
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$oLogger = $oActions->Logger();
|
||||
$oLogger && $oLogger->WriteException($oException);
|
||||
}
|
||||
|
||||
if (isset($_POST['Output']) && 'json' === \strtolower($_POST['Output'])) {
|
||||
\header('Content-Type: application/json; charset=utf-8');
|
||||
$aResult = array(
|
||||
'Action' => 'ExternalLogin',
|
||||
'Result' => $oAccount ? true : false,
|
||||
'ErrorCode' => 0
|
||||
);
|
||||
if (!$oAccount) {
|
||||
if ($oException instanceof \RainLoop\Exceptions\ClientException) {
|
||||
$aResult['ErrorCode'] = $oException->getCode();
|
||||
} else {
|
||||
$aResult['ErrorCode'] = Notifications::AuthError;
|
||||
}
|
||||
}
|
||||
echo Utils::jsonEncode($aResult);
|
||||
} else {
|
||||
$oActions->Location('./');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
526
plugins/login-oauth2/OAuth2/Client.php
Normal file
|
|
@ -0,0 +1,526 @@
|
|||
<?php
|
||||
/**
|
||||
* Note : Code is released under the GNU LGPL
|
||||
*
|
||||
* Please do not change the header of this file
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU
|
||||
* Lesser General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* See the GNU Lesser General Public License for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Light PHP wrapper for the OAuth 2.0 protocol.
|
||||
*
|
||||
* This client is based on the OAuth2 specification draft v2.15
|
||||
* http://tools.ietf.org/html/draft-ietf-oauth-v2-15
|
||||
*
|
||||
* @author Pierrick Charron <pierrick@webstart.fr>
|
||||
* @author Anis Berejeb <anis.berejeb@gmail.com>
|
||||
* @version 1.3.1-dev
|
||||
*/
|
||||
namespace OAuth2;
|
||||
|
||||
class Client
|
||||
{
|
||||
/**
|
||||
* Different AUTH method
|
||||
*/
|
||||
const AUTH_TYPE_URI = 0;
|
||||
const AUTH_TYPE_AUTHORIZATION_BASIC = 1;
|
||||
const AUTH_TYPE_FORM = 2;
|
||||
|
||||
/**
|
||||
* Different Access token type
|
||||
*/
|
||||
const ACCESS_TOKEN_URI = 0;
|
||||
const ACCESS_TOKEN_BEARER = 1;
|
||||
const ACCESS_TOKEN_OAUTH = 2;
|
||||
const ACCESS_TOKEN_MAC = 3;
|
||||
|
||||
/**
|
||||
* Different Grant types
|
||||
*/
|
||||
const GRANT_TYPE_AUTH_CODE = 'authorization_code';
|
||||
const GRANT_TYPE_PASSWORD = 'password';
|
||||
const GRANT_TYPE_CLIENT_CREDENTIALS = 'client_credentials';
|
||||
const GRANT_TYPE_REFRESH_TOKEN = 'refresh_token';
|
||||
|
||||
/**
|
||||
* HTTP Methods
|
||||
*/
|
||||
const HTTP_METHOD_GET = 'GET';
|
||||
const HTTP_METHOD_POST = 'POST';
|
||||
const HTTP_METHOD_PUT = 'PUT';
|
||||
const HTTP_METHOD_DELETE = 'DELETE';
|
||||
const HTTP_METHOD_HEAD = 'HEAD';
|
||||
const HTTP_METHOD_PATCH = 'PATCH';
|
||||
|
||||
/**
|
||||
* HTTP Form content types
|
||||
*/
|
||||
const HTTP_FORM_CONTENT_TYPE_APPLICATION = 0;
|
||||
const HTTP_FORM_CONTENT_TYPE_MULTIPART = 1;
|
||||
|
||||
/**
|
||||
* Client ID
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $client_id = null;
|
||||
|
||||
/**
|
||||
* Client Secret
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $client_secret = null;
|
||||
|
||||
/**
|
||||
* Client Authentication method
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $client_auth = self::AUTH_TYPE_URI;
|
||||
|
||||
/**
|
||||
* Access Token
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $access_token = null;
|
||||
|
||||
/**
|
||||
* Access Token Type
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $access_token_type = self::ACCESS_TOKEN_URI;
|
||||
|
||||
/**
|
||||
* Access Token Secret
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $access_token_secret = null;
|
||||
|
||||
/**
|
||||
* Access Token crypt algorithm
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $access_token_algorithm = null;
|
||||
|
||||
/**
|
||||
* Access Token Parameter name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $access_token_param_name = 'access_token';
|
||||
|
||||
/**
|
||||
* The path to the certificate file to use for https connections
|
||||
*
|
||||
* @var string Defaults to .
|
||||
*/
|
||||
protected $certificate_file = null;
|
||||
|
||||
/**
|
||||
* cURL options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $curl_options = array();
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* @param string $client_id Client ID
|
||||
* @param string $client_secret Client Secret
|
||||
* @param int $client_auth (AUTH_TYPE_URI, AUTH_TYPE_AUTHORIZATION_BASIC, AUTH_TYPE_FORM)
|
||||
* @param string $certificate_file Indicates if we want to use a certificate file to trust the server. Optional, defaults to null.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($client_id, $client_secret, $client_auth = self::AUTH_TYPE_URI, $certificate_file = null)
|
||||
{
|
||||
if (!extension_loaded('curl')) {
|
||||
throw new Exception('The PHP exention curl must be installed to use this library.', Exception::CURL_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->client_id = $client_id;
|
||||
$this->client_secret = $client_secret;
|
||||
$this->client_auth = $client_auth;
|
||||
$this->certificate_file = $certificate_file;
|
||||
if (!empty($this->certificate_file) && !is_file($this->certificate_file)) {
|
||||
throw new InvalidArgumentException('The certificate file was not found', InvalidArgumentException::CERTIFICATE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client Id
|
||||
*
|
||||
* @return string Client ID
|
||||
*/
|
||||
public function getClientId()
|
||||
{
|
||||
return $this->client_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client Secret
|
||||
*
|
||||
* @return string Client Secret
|
||||
*/
|
||||
public function getClientSecret()
|
||||
{
|
||||
return $this->client_secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* getAuthenticationUrl
|
||||
*
|
||||
* @param string $auth_endpoint Url of the authentication endpoint
|
||||
* @param string $redirect_uri Redirection URI
|
||||
* @param array $extra_parameters Array of extra parameters like scope or state (Ex: array('scope' => null, 'state' => ''))
|
||||
* @return string URL used for authentication
|
||||
*/
|
||||
public function getAuthenticationUrl($auth_endpoint, $redirect_uri, array $extra_parameters = array())
|
||||
{
|
||||
$parameters = array_merge(array(
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->client_id,
|
||||
'redirect_uri' => $redirect_uri
|
||||
), $extra_parameters);
|
||||
return $auth_endpoint . '?' . http_build_query($parameters, '', '&');
|
||||
}
|
||||
|
||||
/**
|
||||
* getAccessToken
|
||||
*
|
||||
* @param string $token_endpoint Url of the token endpoint
|
||||
* @param string $grant_type Grant Type ('authorization_code', 'password', 'client_credentials', 'refresh_token', or a custom code (@see GrantType Classes)
|
||||
* @param array $parameters Array sent to the server (depend on which grant type you're using)
|
||||
* @param array $extra_headers Array of extra headers
|
||||
* @return array Array of parameters required by the grant_type (CF SPEC)
|
||||
*/
|
||||
public function getAccessToken($token_endpoint, $grant_type, array $parameters, array $extra_headers = array())
|
||||
{
|
||||
if (!$grant_type) {
|
||||
throw new InvalidArgumentException('The grant_type is mandatory.', InvalidArgumentException::INVALID_GRANT_TYPE);
|
||||
}
|
||||
$grantTypeClassName = $this->convertToCamelCase($grant_type);
|
||||
$grantTypeClass = __NAMESPACE__ . '\\GrantType\\' . $grantTypeClassName;
|
||||
if (!class_exists($grantTypeClass)) {
|
||||
throw new InvalidArgumentException('Unknown grant type \'' . $grant_type . '\'', InvalidArgumentException::INVALID_GRANT_TYPE);
|
||||
}
|
||||
$grantTypeObject = new $grantTypeClass();
|
||||
$grantTypeObject->validateParameters($parameters);
|
||||
if (!defined($grantTypeClass . '::GRANT_TYPE')) {
|
||||
throw new Exception('Unknown constant GRANT_TYPE for class ' . $grantTypeClassName, Exception::GRANT_TYPE_ERROR);
|
||||
}
|
||||
$parameters['grant_type'] = $grantTypeClass::GRANT_TYPE;
|
||||
$http_headers = $extra_headers;
|
||||
switch ($this->client_auth) {
|
||||
case self::AUTH_TYPE_URI:
|
||||
case self::AUTH_TYPE_FORM:
|
||||
$parameters['client_id'] = $this->client_id;
|
||||
$parameters['client_secret'] = $this->client_secret;
|
||||
break;
|
||||
case self::AUTH_TYPE_AUTHORIZATION_BASIC:
|
||||
$parameters['client_id'] = $this->client_id;
|
||||
$http_headers['Authorization'] = 'Basic ' . base64_encode($this->client_id . ':' . $this->client_secret);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Unknown client auth type.', Exception::INVALID_CLIENT_AUTHENTICATION_TYPE);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->executeRequest($token_endpoint, $parameters, self::HTTP_METHOD_POST, $http_headers, self::HTTP_FORM_CONTENT_TYPE_APPLICATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* setToken
|
||||
*
|
||||
* @param string $token Set the access token
|
||||
* @return void
|
||||
*/
|
||||
public function setAccessToken($token)
|
||||
{
|
||||
$this->access_token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is an access token present
|
||||
*
|
||||
* @return bool Whether the access token is present
|
||||
*/
|
||||
public function hasAccessToken()
|
||||
{
|
||||
return !!$this->access_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the client authentication type
|
||||
*
|
||||
* @param string $client_auth (AUTH_TYPE_URI, AUTH_TYPE_AUTHORIZATION_BASIC, AUTH_TYPE_FORM)
|
||||
* @return void
|
||||
*/
|
||||
public function setClientAuthType($client_auth)
|
||||
{
|
||||
$this->client_auth = $client_auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an option for the curl transfer
|
||||
*
|
||||
* @param int $option The CURLOPT_XXX option to set
|
||||
* @param mixed $value The value to be set on option
|
||||
* @return void
|
||||
*/
|
||||
public function setCurlOption($option, $value)
|
||||
{
|
||||
$this->curl_options[$option] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple options for a cURL transfer
|
||||
*
|
||||
* @param array $options An array specifying which options to set and their values
|
||||
* @return void
|
||||
*/
|
||||
public function setCurlOptions($options)
|
||||
{
|
||||
$this->curl_options = array_merge($this->curl_options, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the access token type
|
||||
*
|
||||
* @param int $type Access token type (ACCESS_TOKEN_BEARER, ACCESS_TOKEN_MAC, ACCESS_TOKEN_URI)
|
||||
* @param string $secret The secret key used to encrypt the MAC header
|
||||
* @param string $algorithm Algorithm used to encrypt the signature
|
||||
* @return void
|
||||
*/
|
||||
public function setAccessTokenType($type, $secret = null, $algorithm = null)
|
||||
{
|
||||
$this->access_token_type = $type;
|
||||
$this->access_token_secret = $secret;
|
||||
$this->access_token_algorithm = $algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a protected ressource
|
||||
*
|
||||
* @param string $protected_ressource_url Protected resource URL
|
||||
* @param array $parameters Array of parameters
|
||||
* @param string $http_method HTTP Method to use (POST, PUT, GET, HEAD, DELETE)
|
||||
* @param array $http_headers HTTP headers
|
||||
* @param int $form_content_type HTTP form content type to use
|
||||
* @return array
|
||||
*/
|
||||
public function fetch($protected_resource_url, $parameters = array(), $http_method = self::HTTP_METHOD_GET, array $http_headers = array(), $form_content_type = self::HTTP_FORM_CONTENT_TYPE_MULTIPART)
|
||||
{
|
||||
if ($this->access_token) {
|
||||
switch ($this->access_token_type) {
|
||||
case self::ACCESS_TOKEN_URI:
|
||||
if (is_array($parameters)) {
|
||||
$parameters[$this->access_token_param_name] = $this->access_token;
|
||||
} else {
|
||||
throw new InvalidArgumentException(
|
||||
'You need to give parameters as array if you want to give the token within the URI.',
|
||||
InvalidArgumentException::REQUIRE_PARAMS_AS_ARRAY
|
||||
);
|
||||
}
|
||||
break;
|
||||
case self::ACCESS_TOKEN_BEARER:
|
||||
$http_headers['Authorization'] = 'Bearer ' . $this->access_token;
|
||||
break;
|
||||
case self::ACCESS_TOKEN_OAUTH:
|
||||
$http_headers['Authorization'] = 'OAuth ' . $this->access_token;
|
||||
break;
|
||||
case self::ACCESS_TOKEN_MAC:
|
||||
$http_headers['Authorization'] = 'MAC ' . $this->generateMACSignature($protected_resource_url, $parameters, $http_method);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Unknown access token type.', Exception::INVALID_ACCESS_TOKEN_TYPE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $this->executeRequest($protected_resource_url, $parameters, $http_method, $http_headers, $form_content_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the MAC signature
|
||||
*
|
||||
* @param string $url Called URL
|
||||
* @param array $parameters Parameters
|
||||
* @param string $http_method Http Method
|
||||
* @return string
|
||||
*/
|
||||
private function generateMACSignature($url, $parameters, $http_method)
|
||||
{
|
||||
$timestamp = time();
|
||||
$nonce = uniqid();
|
||||
$parsed_url = parse_url($url);
|
||||
if (!isset($parsed_url['port']))
|
||||
{
|
||||
$parsed_url['port'] = ($parsed_url['scheme'] == 'https') ? 443 : 80;
|
||||
}
|
||||
if ($http_method == self::HTTP_METHOD_GET) {
|
||||
if (is_array($parameters)) {
|
||||
$parsed_url['path'] .= '?' . http_build_query($parameters, '', '&');
|
||||
} elseif ($parameters) {
|
||||
$parsed_url['path'] .= '?' . $parameters;
|
||||
}
|
||||
}
|
||||
|
||||
$signature = base64_encode(hash_hmac($this->access_token_algorithm,
|
||||
$timestamp . "\n"
|
||||
. $nonce . "\n"
|
||||
. $http_method . "\n"
|
||||
. $parsed_url['path'] . "\n"
|
||||
. $parsed_url['host'] . "\n"
|
||||
. $parsed_url['port'] . "\n\n"
|
||||
, $this->access_token_secret, true));
|
||||
|
||||
return 'id="' . $this->access_token . '", ts="' . $timestamp . '", nonce="' . $nonce . '", mac="' . $signature . '"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a request (with curl)
|
||||
*
|
||||
* @param string $url URL
|
||||
* @param mixed $parameters Array of parameters
|
||||
* @param string $http_method HTTP Method
|
||||
* @param array $http_headers HTTP Headers
|
||||
* @param int $form_content_type HTTP form content type to use
|
||||
* @return array
|
||||
*/
|
||||
private function executeRequest($url, $parameters = array(), $http_method = self::HTTP_METHOD_GET, array $http_headers = null, $form_content_type = self::HTTP_FORM_CONTENT_TYPE_MULTIPART)
|
||||
{
|
||||
$curl_options = array(
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $http_method
|
||||
);
|
||||
|
||||
switch($http_method) {
|
||||
case self::HTTP_METHOD_POST:
|
||||
$curl_options[CURLOPT_POST] = true;
|
||||
/* No break */
|
||||
case self::HTTP_METHOD_PUT:
|
||||
case self::HTTP_METHOD_PATCH:
|
||||
|
||||
/**
|
||||
* Passing an array to CURLOPT_POSTFIELDS will encode the data as multipart/form-data,
|
||||
* while passing a URL-encoded string will encode the data as application/x-www-form-urlencoded.
|
||||
* http://php.net/manual/en/function.curl-setopt.php
|
||||
*/
|
||||
if(is_array($parameters) && self::HTTP_FORM_CONTENT_TYPE_APPLICATION === $form_content_type) {
|
||||
$parameters = http_build_query($parameters, '', '&');
|
||||
}
|
||||
$curl_options[CURLOPT_POSTFIELDS] = $parameters;
|
||||
break;
|
||||
case self::HTTP_METHOD_HEAD:
|
||||
$curl_options[CURLOPT_NOBODY] = true;
|
||||
/* No break */
|
||||
case self::HTTP_METHOD_DELETE:
|
||||
case self::HTTP_METHOD_GET:
|
||||
if (is_array($parameters) && count($parameters) > 0) {
|
||||
$url .= '?' . http_build_query($parameters, '', '&');
|
||||
} elseif ($parameters) {
|
||||
$url .= '?' . $parameters;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$curl_options[CURLOPT_URL] = $url;
|
||||
|
||||
if (is_array($http_headers)) {
|
||||
$header = array();
|
||||
foreach($http_headers as $key => $parsed_urlvalue) {
|
||||
$header[] = "$key: $parsed_urlvalue";
|
||||
}
|
||||
$curl_options[CURLOPT_HTTPHEADER] = $header;
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, $curl_options);
|
||||
// https handling
|
||||
if (!empty($this->certificate_file)) {
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
curl_setopt($ch, CURLOPT_CAINFO, $this->certificate_file);
|
||||
} else {
|
||||
// bypass ssl verification
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
}
|
||||
if (!empty($this->curl_options)) {
|
||||
curl_setopt_array($ch, $this->curl_options);
|
||||
}
|
||||
$result = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
if ($curl_error = curl_error($ch)) {
|
||||
throw new Exception($curl_error, Exception::CURL_ERROR);
|
||||
} else {
|
||||
$json_decode = json_decode($result, true);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
return array(
|
||||
'result' => (null === $json_decode) ? $result : $json_decode,
|
||||
'code' => $http_code,
|
||||
'content_type' => $content_type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the parameter that carry the access token
|
||||
*
|
||||
* @param string $name Token parameter name
|
||||
* @return void
|
||||
*/
|
||||
public function setAccessTokenParamName($name)
|
||||
{
|
||||
$this->access_token_param_name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the class name to camel case
|
||||
*
|
||||
* @param mixed $grant_type the grant type
|
||||
* @return string
|
||||
*/
|
||||
private function convertToCamelCase($grant_type)
|
||||
{
|
||||
$parts = explode('_', $grant_type);
|
||||
array_walk($parts, function(&$item) { $item = ucfirst($item);});
|
||||
return implode('', $parts);
|
||||
}
|
||||
}
|
||||
|
||||
class Exception extends \Exception
|
||||
{
|
||||
const CURL_NOT_FOUND = 0x01;
|
||||
const CURL_ERROR = 0x02;
|
||||
const GRANT_TYPE_ERROR = 0x03;
|
||||
const INVALID_CLIENT_AUTHENTICATION_TYPE = 0x04;
|
||||
const INVALID_ACCESS_TOKEN_TYPE = 0x05;
|
||||
}
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException
|
||||
{
|
||||
const INVALID_GRANT_TYPE = 0x01;
|
||||
const CERTIFICATE_NOT_FOUND = 0x02;
|
||||
const REQUIRE_PARAMS_AS_ARRAY = 0x03;
|
||||
const MISSING_PARAMETER = 0x04;
|
||||
}
|
||||
41
plugins/login-oauth2/OAuth2/GrantType/AuthorizationCode.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
namespace OAuth2\GrantType;
|
||||
|
||||
use OAuth2\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Authorization code Grant Type Validator
|
||||
*/
|
||||
class AuthorizationCode implements IGrantType
|
||||
{
|
||||
/**
|
||||
* Defines the Grant Type
|
||||
*
|
||||
* @var string Defaults to 'authorization_code'.
|
||||
*/
|
||||
const GRANT_TYPE = 'authorization_code';
|
||||
|
||||
/**
|
||||
* Adds a specific Handling of the parameters
|
||||
*
|
||||
* @return array of Specific parameters to be sent.
|
||||
* @param mixed $parameters the parameters array (passed by reference)
|
||||
*/
|
||||
public function validateParameters(&$parameters)
|
||||
{
|
||||
if (!isset($parameters['code']))
|
||||
{
|
||||
throw new InvalidArgumentException(
|
||||
'The \'code\' parameter must be defined for the Authorization Code grant type',
|
||||
InvalidArgumentException::MISSING_PARAMETER
|
||||
);
|
||||
}
|
||||
elseif (!isset($parameters['redirect_uri']))
|
||||
{
|
||||
throw new InvalidArgumentException(
|
||||
'The \'redirect_uri\' parameter must be defined for the Authorization Code grant type',
|
||||
InvalidArgumentException::MISSING_PARAMETER
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
plugins/login-oauth2/OAuth2/GrantType/ClientCredentials.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace OAuth2\GrantType;
|
||||
|
||||
/**
|
||||
* Client Credentials Parameters
|
||||
*/
|
||||
class ClientCredentials implements IGrantType
|
||||
{
|
||||
/**
|
||||
* Defines the Grant Type
|
||||
*
|
||||
* @var string Defaults to 'client_credentials'.
|
||||
*/
|
||||
const GRANT_TYPE = 'client_credentials';
|
||||
|
||||
/**
|
||||
* Adds a specific Handling of the parameters
|
||||
*
|
||||
* @return array of Specific parameters to be sent.
|
||||
* @param mixed $parameters the parameters array (passed by reference)
|
||||
*/
|
||||
public function validateParameters(&$parameters)
|
||||
{
|
||||
}
|
||||
}
|
||||
15
plugins/login-oauth2/OAuth2/GrantType/IGrantType.php
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
namespace OAuth2\GrantType;
|
||||
/**
|
||||
* Specific GrantType Interface
|
||||
*/
|
||||
interface IGrantType
|
||||
{
|
||||
/**
|
||||
* Adds a specific Handling of the parameters
|
||||
*
|
||||
* @return array of Specific parameters to be sent.
|
||||
* @param mixed $parameters the parameters array (passed by reference)
|
||||
*/
|
||||
public function validateParameters(&$parameters);
|
||||
}
|
||||
41
plugins/login-oauth2/OAuth2/GrantType/Password.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
namespace OAuth2\GrantType;
|
||||
|
||||
use OAuth2\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Password Parameters
|
||||
*/
|
||||
class Password implements IGrantType
|
||||
{
|
||||
/**
|
||||
* Defines the Grant Type
|
||||
*
|
||||
* @var string Defaults to 'password'.
|
||||
*/
|
||||
const GRANT_TYPE = 'password';
|
||||
|
||||
/**
|
||||
* Adds a specific Handling of the parameters
|
||||
*
|
||||
* @return array of Specific parameters to be sent.
|
||||
* @param mixed $parameters the parameters array (passed by reference)
|
||||
*/
|
||||
public function validateParameters(&$parameters)
|
||||
{
|
||||
if (!isset($parameters['username']))
|
||||
{
|
||||
throw new InvalidArgumentException(
|
||||
'The \'username\' parameter must be defined for the Password grant type',
|
||||
InvalidArgumentException::MISSING_PARAMETER
|
||||
);
|
||||
}
|
||||
elseif (!isset($parameters['password']))
|
||||
{
|
||||
throw new InvalidArgumentException(
|
||||
'The \'password\' parameter must be defined for the Password grant type',
|
||||
InvalidArgumentException::MISSING_PARAMETER
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
plugins/login-oauth2/OAuth2/GrantType/RefreshToken.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
namespace OAuth2\GrantType;
|
||||
|
||||
use OAuth2\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Refresh Token Parameters
|
||||
*/
|
||||
class RefreshToken implements IGrantType
|
||||
{
|
||||
/**
|
||||
* Defines the Grant Type
|
||||
*
|
||||
* @var string Defaults to 'refresh_token'.
|
||||
*/
|
||||
const GRANT_TYPE = 'refresh_token';
|
||||
|
||||
/**
|
||||
* Adds a specific Handling of the parameters
|
||||
*
|
||||
* @return array of Specific parameters to be sent.
|
||||
* @param mixed $parameters the parameters array (passed by reference)
|
||||
*/
|
||||
public function validateParameters(&$parameters)
|
||||
{
|
||||
if (!isset($parameters['refresh_token']))
|
||||
{
|
||||
throw new InvalidArgumentException(
|
||||
'The \'refresh_token\' parameter must be defined for the refresh token grant type',
|
||||
InvalidArgumentException::MISSING_PARAMETER
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
504
plugins/login-oauth2/OAuth2/LICENSE
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random
|
||||
Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
105
plugins/login-oauth2/OAuth2/README.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Light PHP wrapper for the OAuth 2.0
|
||||
|
||||
[](https://packagist.org/packages/adoy/fastcgi-client)
|
||||
[](LICENSE)
|
||||
[](https://packagist.org/packages/adoy/fastcgi-client)
|
||||
|
||||
|
||||
## How can I use it ?
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require('Client.php');
|
||||
require('GrantType/IGrantType.php');
|
||||
require('GrantType/AuthorizationCode.php');
|
||||
|
||||
const CLIENT_ID = 'your client id';
|
||||
const CLIENT_SECRET = 'your client secret';
|
||||
|
||||
const REDIRECT_URI = 'http://url/of/this.php';
|
||||
const AUTHORIZATION_ENDPOINT = 'https://graph.facebook.com/oauth/authorize';
|
||||
const TOKEN_ENDPOINT = 'https://graph.facebook.com/oauth/access_token';
|
||||
|
||||
$client = new OAuth2\Client(CLIENT_ID, CLIENT_SECRET);
|
||||
if (!isset($_GET['code']))
|
||||
{
|
||||
$auth_url = $client->getAuthenticationUrl(AUTHORIZATION_ENDPOINT, REDIRECT_URI);
|
||||
header('Location: ' . $auth_url);
|
||||
die('Redirect');
|
||||
}
|
||||
else
|
||||
{
|
||||
$params = array('code' => $_GET['code'], 'redirect_uri' => REDIRECT_URI);
|
||||
$response = $client->getAccessToken(TOKEN_ENDPOINT, 'authorization_code', $params);
|
||||
parse_str($response['result'], $info);
|
||||
$client->setAccessToken($info['access_token']);
|
||||
$response = $client->fetch('https://graph.facebook.com/me');
|
||||
var_dump($response, $response['result']);
|
||||
}
|
||||
```
|
||||
|
||||
## How can I add a new Grant Type ?
|
||||
|
||||
Simply write a new class in the namespace OAuth2\GrantType. You can place the class file under GrantType.
|
||||
Here is an example :
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace OAuth2\GrantType;
|
||||
|
||||
/**
|
||||
* MyCustomGrantType Grant Type
|
||||
*/
|
||||
class MyCustomGrantType implements IGrantType
|
||||
{
|
||||
/**
|
||||
* Defines the Grant Type
|
||||
*
|
||||
* @var string Defaults to 'my_custom_grant_type'.
|
||||
*/
|
||||
const GRANT_TYPE = 'my_custom_grant_type';
|
||||
|
||||
/**
|
||||
* Adds a specific Handling of the parameters
|
||||
*
|
||||
* @return array of Specific parameters to be sent.
|
||||
* @param mixed $parameters the parameters array (passed by reference)
|
||||
*/
|
||||
public function validateParameters(&$parameters)
|
||||
{
|
||||
if (!isset($parameters['first_mandatory_parameter']))
|
||||
{
|
||||
throw new \Exception('The \'first_mandatory_parameter\' parameter must be defined for the Password grant type');
|
||||
}
|
||||
elseif (!isset($parameters['second_mandatory_parameter']))
|
||||
{
|
||||
throw new \Exception('The \'seconde_mandatory_parameter\' parameter must be defined for the Password grant type');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
call the OAuth client getAccessToken with the grantType you defined in the GRANT_TYPE constant, As following :
|
||||
|
||||
```
|
||||
$response = $client->getAccessToken(TOKEN_ENDPOINT, 'my_custom_grant_type', $params);
|
||||
```
|
||||
|
||||
## LICENSE
|
||||
|
||||
This Code is released under the GNU LGPL
|
||||
|
||||
Please do not change the header of the file(s).
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
See the GNU Lesser General Public License for more details.
|
||||
|
|
@ -4,9 +4,9 @@ class LoginOAuth2Plugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'OAuth2',
|
||||
VERSION = '1.0',
|
||||
RELEASE = '2021-11-12',
|
||||
REQUIRED = '2.9.1',
|
||||
VERSION = '1.1',
|
||||
RELEASE = '2022-11-11',
|
||||
REQUIRED = '2.21.0',
|
||||
CATEGORY = 'Login',
|
||||
DESCRIPTION = 'IMAP, Sieve & SMTP login using RFC 7628 OAuth2';
|
||||
|
||||
|
|
@ -19,19 +19,26 @@ class LoginOAuth2Plugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
$this->UseLangs(true);
|
||||
$this->addJs('LoginOAuth2.js');
|
||||
// $this->addHook('imap.before-connect', array($this, $oImapClient, &$aCredentials));
|
||||
// $this->addHook('imap.after-connect', array($this, $oImapClient, $aCredentials));
|
||||
// $this->addHook('imap.before-connect', array($this, $oImapClient, $oSettings));
|
||||
// $this->addHook('imap.after-connect', array($this, $oImapClient, $oSettings));
|
||||
$this->addHook('imap.before-login', 'clientLogin');
|
||||
// $this->addHook('imap.after-login', array($this, $oImapClient, $aCredentials));
|
||||
// $this->addHook('smtp.before-connect', array($this, $oSmtpClient, &$aCredentials));
|
||||
// $this->addHook('smtp.after-connect', array($this, $oSmtpClient, $aCredentials));
|
||||
// $this->addHook('imap.after-login', array($this, $oImapClient, $oSettings));
|
||||
// $this->addHook('smtp.before-connect', array($this, $oSmtpClient, $oSettings));
|
||||
// $this->addHook('smtp.after-connect', array($this, $oSmtpClient, $oSettings));
|
||||
$this->addHook('smtp.before-login', 'clientLogin');
|
||||
// $this->addHook('smtp.after-login', array($this, $oSmtpClient, $aCredentials));
|
||||
// $this->addHook('sieve.before-connect', array($this, $oSieveClient, &$aCredentials));
|
||||
// $this->addHook('sieve.after-connect', array($this, $oSieveClient, $aCredentials));
|
||||
// $this->addHook('smtp.after-login', array($this, $oSmtpClient, $oSettings));
|
||||
// $this->addHook('sieve.before-connect', array($this, $oSieveClient, $oSettings));
|
||||
// $this->addHook('sieve.after-connect', array($this, $oSieveClient, $oSettings));
|
||||
$this->addHook('sieve.before-login', 'clientLogin');
|
||||
// $this->addHook('sieve.after-login', array($this, $oSieveClient, $aCredentials));
|
||||
// $this->addHook('sieve.after-login', array($this, $oSieveClient, $oSettings));
|
||||
$this->addHook('filter.account', 'filterAccount');
|
||||
|
||||
// set_include_path(get_include_path() . PATH_SEPARATOR . __DIR__);
|
||||
spl_autoload_register(function($classname){
|
||||
if (str_starts_with($classname, 'OAuth2\\')) {
|
||||
include_once __DIR__ . strtr("\\{$sClassName}", '\\', DIRECTORY_SEPARATOR) . '.php';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function configMapping() : array
|
||||
|
|
@ -46,9 +53,9 @@ class LoginOAuth2Plugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
];
|
||||
}
|
||||
|
||||
public function clientLogin(\RainLoop\Model\Account $oAccount, \MailSo\Net\NetClient $oClient, array &$aCredentials) : void
|
||||
public function clientLogin(\RainLoop\Model\Account $oAccount, \MailSo\Net\NetClient $oClient, \MailSo\Net\ConnectSettings $oSettings) : void
|
||||
{
|
||||
$sPassword = $aCredentials['Password'];
|
||||
$sPassword = $oSettings->Password;
|
||||
$iGatLen = \strlen(static::GMAIL_TOKENS_PREFIX);
|
||||
if ($sPassword && static::GMAIL_TOKENS_PREFIX === \substr($sPassword, 0, $iGatLen)) {
|
||||
$aTokens = \json_decode(\substr($sPassword, $iGatLen));
|
||||
|
|
@ -56,8 +63,8 @@ class LoginOAuth2Plugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
$sRefreshToken = !empty($aTokens[1]) ? $aTokens[1] : '';
|
||||
}
|
||||
if ($sAccessToken && $sRefreshToken) {
|
||||
$aCredentials['Password'] = $this->gmailRefreshToken($sAccessToken, $sRefreshToken);
|
||||
\array_unshift($aCredentials['SASLMechanisms'], 'OAUTHBEARER', 'XOAUTH2');
|
||||
$oSettings->Password = $this->gmailRefreshToken($sAccessToken, $sRefreshToken);
|
||||
\array_unshift($oSettings->SASLMechanisms, 'OAUTHBEARER', 'XOAUTH2');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -175,10 +182,10 @@ class LoginOAuth2Plugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
$client_id = \trim($this->Config()->Get('plugin', 'client_id', ''));
|
||||
$client_secret = \trim($this->Config()->Get('plugin', 'client_secret', ''));
|
||||
if ($client_id && $client_secret) {
|
||||
include_once APP_VERSION_ROOT_PATH.'app/libraries/PHP-OAuth2/Client.php';
|
||||
include_once APP_VERSION_ROOT_PATH.'app/libraries/PHP-OAuth2/GrantType/IGrantType.php';
|
||||
include_once APP_VERSION_ROOT_PATH.'app/libraries/PHP-OAuth2/GrantType/AuthorizationCode.php';
|
||||
include_once APP_VERSION_ROOT_PATH.'app/libraries/PHP-OAuth2/GrantType/RefreshToken.php';
|
||||
// include_once __DIR__ . '/OAuth2/Client.php';
|
||||
// include_once __DIR__ . '/OAuth2/GrantType/IGrantType.php';
|
||||
// include_once __DIR__ . '/OAuth2/GrantType/AuthorizationCode.php';
|
||||
// include_once __DIR__ . '/OAuth2/GrantType/RefreshToken.php';
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
20
plugins/login-remote/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 RainLoop Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
79
plugins/login-remote/index.php
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
class LoginRemotePlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
const
|
||||
NAME = 'Login Remote',
|
||||
AUTHOR = 'SnappyMail',
|
||||
URL = 'https://snappymail.eu/',
|
||||
VERSION = '1.1',
|
||||
RELEASE = '2022-11-24',
|
||||
REQUIRED = '2.21.0',
|
||||
CATEGORY = 'Login',
|
||||
LICENSE = 'MIT',
|
||||
DESCRIPTION = 'Tries to login using the $_ENV["REMOTE_*"] variables';
|
||||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addPartHook('RemoteAutoLogin', 'ServiceRemoteAutoLogin');
|
||||
$this->addHook('filter.app-data', 'FilterAppData');
|
||||
$this->addHook('login.credentials', 'FilterLoginCredentials');
|
||||
}
|
||||
|
||||
public function FilterAppData($bAdmin, &$aResult)
|
||||
{
|
||||
if (!$bAdmin && \is_array($aResult) && empty($aResult['Auth']) && isset($_ENV['REMOTE_USER'])) {
|
||||
$aResult['DevEmail'] = $_ENV['REMOTE_USER'];
|
||||
// $aResult['DevPassword'] = $_ENV['REMOTE_PASSWORD'];
|
||||
}
|
||||
}
|
||||
|
||||
private static bool $login = false;
|
||||
public function ServiceRemoteAutoLogin() : bool
|
||||
{
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
|
||||
$oException = null;
|
||||
$oAccount = null;
|
||||
|
||||
$sEmail = $_ENV['REMOTE_USER'] ?? '';
|
||||
$sPassword = $_ENV['REMOTE_PASSWORD'] ?? '';
|
||||
|
||||
if (\strlen($sEmail) && \strlen($sPassword)) {
|
||||
try
|
||||
{
|
||||
static::$login = true;
|
||||
$oAccount = $oActions->LoginProcess($sEmail, $sPassword);
|
||||
if ($oAccount instanceof \RainLoop\Model\MainAccount) {
|
||||
$oActions->SetAuthToken($oAccount);
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$oLogger = $oActions->Logger();
|
||||
$oLogger && $oLogger->WriteException($oException);
|
||||
}
|
||||
}
|
||||
|
||||
$oActions->Location('./');
|
||||
return true;
|
||||
}
|
||||
|
||||
public function FilterLoginCredentials(&$sEmail, &$sLogin, &$sPassword)
|
||||
{
|
||||
// cPanel https://github.com/the-djmaze/snappymail/issues/697
|
||||
// && !empty($_ENV['CPANEL'])
|
||||
if (static::$login/* && $sLogin == $_ENV['REMOTE_USER']*/) {
|
||||
if (empty($_ENV['REMOTE_TEMP_USER'])) {
|
||||
$iPos = \strpos($sPassword, '[::cpses::]');
|
||||
if ($iPos) {
|
||||
$_ENV['REMOTE_TEMP_USER'] = \substr($sPassword, 0, $iPos);
|
||||
}
|
||||
}
|
||||
if (!empty($_ENV['REMOTE_TEMP_USER'])) {
|
||||
$sLogin = $_ENV['REMOTE_USER'] . '/' . $_ENV['REMOTE_TEMP_USER'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Nextcloud',
|
||||
VERSION = '2.10',
|
||||
RELEASE = '2022-11-04',
|
||||
VERSION = '2.11',
|
||||
RELEASE = '2022-11-14',
|
||||
CATEGORY = 'Integrations',
|
||||
DESCRIPTION = 'Integrate with Nextcloud v20+',
|
||||
REQUIRED = '2.19.0';
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
}
|
||||
);
|
||||
} else if (file.url) {
|
||||
// TODO: other editors and text/plain
|
||||
view.oEditor.editor.squire.makeLink(file.url);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,54 @@
|
|||
(rl => {
|
||||
// if (rl.settings.get('Nextcloud'))
|
||||
const templateId = 'MailMessageView';
|
||||
|
||||
addEventListener('rl-view-model.create', e => {
|
||||
if ('MailMessageView' === e.detail.viewModelTemplateID) {
|
||||
if (templateId === e.detail.viewModelTemplateID) {
|
||||
|
||||
const
|
||||
template = document.getElementById(templateId),
|
||||
cfg = rl.settings.get('Nextcloud'),
|
||||
attachmentsControls = template.content.querySelector('.attachmentsControls'),
|
||||
msgMenu = template.content.querySelector('#more-view-dropdown-id + menu');
|
||||
|
||||
if (attachmentsControls) {
|
||||
attachmentsControls.append(Element.fromHTML(`<span>
|
||||
<i class="fontastic iconcolor-red" data-bind="visible: saveNextcloudError">✖</i>
|
||||
<i class="fontastic" data-bind="visible: !saveNextcloudError(),
|
||||
css: {'icon-spinner': saveNextcloudLoading()}">💾</i>
|
||||
<span class="g-ui-link" data-bind="click: saveNextcloud" data-i18n="NEXTCLOUD/SAVE_ATTACHMENTS"></span>
|
||||
</span>`));
|
||||
|
||||
// https://github.com/nextcloud/calendar/issues/4684
|
||||
if (cfg.CalDAV) {
|
||||
attachmentsControls.append(Element.fromHTML(`<span data-bind="visible: nextcloudICS" data-icon="📅">
|
||||
<span class="g-ui-link" data-bind="click: nextcloudSaveICS" data-i18n="NEXTCLOUD/SAVE_ICS"></span>
|
||||
</span>`));
|
||||
}
|
||||
}
|
||||
/*
|
||||
// https://github.com/the-djmaze/snappymail/issues/592
|
||||
if (cfg.CalDAV) {
|
||||
const attachmentsPlace = template.content.querySelector('.attachmentsPlace');
|
||||
attachmentsPlace.after(Element.fromHTML(`
|
||||
<table data-bind="if: nextcloudICS, visible: nextcloudICS"><tbody style="white-space:pre">
|
||||
<tr><td>Summary</td><td data-icon="📅" data-bind="text: nextcloudICS().SUMMARY"></td></tr>
|
||||
<tr><td>Organizer</td><td data-bind="text: nextcloudICS().ORGANIZER"></td></tr>
|
||||
<tr><td>Start</td><td data-bind="text: nextcloudICS().DTSTART"></td></tr>
|
||||
<tr><td>End</td><td data-bind="text: nextcloudICS().DTEND"></td></tr>
|
||||
<tr><td>Transparency</td><td data-bind="text: nextcloudICS().TRANSP"></td></tr>
|
||||
<tr data-bind="foreach: nextcloudICS().ATTENDEE">
|
||||
<td></td><td data-bind="text: $data.replace(/;/g,';\\n')"></td>
|
||||
</tr>
|
||||
</tbody></table>`));
|
||||
}
|
||||
*/
|
||||
if (msgMenu) {
|
||||
msgMenu.append(Element.fromHTML(`<li role="presentation">
|
||||
<a href="#" tabindex="-1" data-icon="📥" data-bind="click: nextcloudSaveMsg" data-i18n="NEXTCLOUD/SAVE_EML"></a>
|
||||
</li>`));
|
||||
}
|
||||
|
||||
let view = e.detail;
|
||||
view.saveNextcloudError = ko.observable(false).extend({ falseTimeout: 7000 });
|
||||
view.saveNextcloudLoading = ko.observable(false);
|
||||
|
|
@ -65,7 +111,7 @@
|
|||
view.nextcloudSaveICS = () => {
|
||||
let VEVENT = view.nextcloudICS();
|
||||
VEVENT && rl.nextcloud.selectCalendar()
|
||||
.then(href => href && rl.nextcloud.calendarPut(href, VEVENT.rawText));
|
||||
.then(href => href && rl.nextcloud.calendarPut(href, VEVENT));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -73,7 +119,7 @@
|
|||
*/
|
||||
view.message.subscribe(msg => {
|
||||
view.nextcloudICS(null);
|
||||
if (msg) {
|
||||
if (msg && cfg.CalDAV) {
|
||||
let ics = msg.attachments.find(attachment => 'text/calendar' == attachment.mimeType);
|
||||
if (ics && ics.download) {
|
||||
// fetch it and parse the VEVENT
|
||||
|
|
@ -119,7 +165,7 @@
|
|||
}
|
||||
}
|
||||
// METHOD:REPLY || METHOD:REQUEST
|
||||
console.dir({VEVENT:VEVENT});
|
||||
// console.dir({VEVENT:VEVENT});
|
||||
if (VEVENT) {
|
||||
VEVENT.rawText = text;
|
||||
VEVENT.isCancelled = () => VEVENT.STATUS?.includes('CANCELLED');
|
||||
|
|
@ -138,47 +184,4 @@
|
|||
}
|
||||
});
|
||||
|
||||
let template = document.getElementById('MailMessageView');
|
||||
let cfg = rl.settings.get('Nextcloud');
|
||||
|
||||
const attachmentsControls = template.content.querySelector('.attachmentsControls');
|
||||
if (attachmentsControls) {
|
||||
attachmentsControls.append(Element.fromHTML(`<span>
|
||||
<i class="fontastic iconcolor-red" data-bind="visible: saveNextcloudError">✖</i>
|
||||
<i class="fontastic" data-bind="visible: !saveNextcloudError(),
|
||||
css: {'icon-spinner': saveNextcloudLoading()}">💾</i>
|
||||
<span class="g-ui-link" data-bind="click: saveNextcloud" data-i18n="NEXTCLOUD/SAVE_ATTACHMENTS"></span>
|
||||
</span>`));
|
||||
|
||||
// https://github.com/nextcloud/calendar/issues/4684
|
||||
if (cfg.CalDAV) {
|
||||
attachmentsControls.append(Element.fromHTML(`<span data-bind="visible: nextcloudICS" data-icon="📅">
|
||||
<span class="g-ui-link" data-bind="click: nextcloudSaveICS" data-i18n="NEXTCLOUD/SAVE_ICS"></span>
|
||||
</span>`));
|
||||
}
|
||||
}
|
||||
/*
|
||||
// https://github.com/the-djmaze/snappymail/issues/592
|
||||
if (cfg.CalDAV) {
|
||||
const attachmentsPlace = template.content.querySelector('.attachmentsPlace');
|
||||
attachmentsPlace.after(Element.fromHTML(`
|
||||
<table data-bind="if: nextcloudICS, visible: nextcloudICS"><tbody style="white-space:pre">
|
||||
<tr><td>Summary</td><td data-icon="📅" data-bind="text: nextcloudICS().SUMMARY"></td></tr>
|
||||
<tr><td>Organizer</td><td data-bind="text: nextcloudICS().ORGANIZER"></td></tr>
|
||||
<tr><td>Start</td><td data-bind="text: nextcloudICS().DTSTART"></td></tr>
|
||||
<tr><td>End</td><td data-bind="text: nextcloudICS().DTEND"></td></tr>
|
||||
<tr><td>Transparency</td><td data-bind="text: nextcloudICS().TRANSP"></td></tr>
|
||||
<tr data-bind="foreach: nextcloudICS().ATTENDEE">
|
||||
<td></td><td data-bind="text: $data.replace(/;/g,';\\n')"></td>
|
||||
</tr>
|
||||
</tbody></table>`));
|
||||
}
|
||||
*/
|
||||
const msgMenu = template.content.querySelector('#more-view-dropdown-id + menu');
|
||||
if (msgMenu) {
|
||||
msgMenu.append(Element.fromHTML(`<li role="presentation">
|
||||
<a href="#" tabindex="-1" data-icon="📥" data-bind="click: nextcloudSaveMsg" data-i18n="NEXTCLOUD/SAVE_EML"></a>
|
||||
</li>`));
|
||||
}
|
||||
|
||||
})(window.rl);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
let view = e.detail;
|
||||
view.nextcloudSaveMsgs = () => {
|
||||
view.messageList.hasChecked()
|
||||
&& rl.ncFiles.selectFolder().then(folder => {
|
||||
&& rl.nextcloud.selectFolder().then(folder => {
|
||||
folder && view.messageList.forEach(msg => {
|
||||
msg.checked() && rl.pluginRemoteRequest(
|
||||
(iError, data) => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const
|
|||
OC = () => parent.OC,
|
||||
|
||||
// Nextcloud 19 deprecated generateUrl, but screw `import { generateUrl } from "@nextcloud/router"`
|
||||
shareUrl = () => OC().webroot + '/ocs/v2.php/apps/files_sharing/api/v1/shares',
|
||||
generateUrl = path => OC().webroot + (OC().config.modRewriteWorking ? '' : '/index.php') + path,
|
||||
generateRemoteUrl = path => location.protocol + '//' + location.host + generateUrl(path),
|
||||
|
||||
|
|
@ -22,36 +23,10 @@ const
|
|||
<oc:size/>
|
||||
<resourcetype/>
|
||||
<getcontentlength/>
|
||||
|
||||
<getcontenttype/>
|
||||
<oc:permissions/>
|
||||
<ocs:share-permissions/>
|
||||
<nc:share-attributes/>
|
||||
<oc:share-types/>
|
||||
<nc:is-encrypted/>
|
||||
</prop>
|
||||
</propfind>`,
|
||||
/*
|
||||
<d:propstat>
|
||||
<d:prop>
|
||||
<d:getcontenttype>video/mp4</d:getcontenttype>
|
||||
<oc:permissions>RGDNVW</oc:permissions>
|
||||
<d:getcontentlength>3963036</d:getcontentlength>
|
||||
<ocs:share-permissions>19</ocs:share-permissions>
|
||||
<nc:share-attributes>[]</nc:share-attributes>
|
||||
<oc:share-types>
|
||||
<oc:share-type>3</oc:share-type>
|
||||
</oc:share-types>
|
||||
</d:prop>
|
||||
<d:status>HTTP/1.1 200 OK</d:status>
|
||||
</d:propstat>
|
||||
<d:propstat>
|
||||
<d:prop>
|
||||
<nc:is-encrypted/>
|
||||
</d:prop>
|
||||
<d:status>HTTP/1.1 404 Not Found</d:status>
|
||||
</d:propstat>
|
||||
*/
|
||||
|
||||
propfindCal = `<?xml version="1.0"?>
|
||||
<propfind xmlns="DAV:">
|
||||
|
|
@ -70,11 +45,10 @@ const
|
|||
getDavElementByTagName = (parent, localName) => getDavElementsByTagName(parent, localName)?.item(0),
|
||||
getElementByTagName = (parent, localName) => +parent.getElementsByTagName(localName)?.item(0),
|
||||
|
||||
davFetch = (mode, path, options) => {
|
||||
ncFetch = (path, options) => {
|
||||
if (!OC().requestToken) {
|
||||
return Promise.reject(new Error('OC.requestToken missing'));
|
||||
}
|
||||
let cfg = rl.settings.get('Nextcloud');
|
||||
options = Object.assign({
|
||||
mode: 'same-origin',
|
||||
cache: 'no-cache',
|
||||
|
|
@ -83,8 +57,13 @@ const
|
|||
headers: {}
|
||||
}, options);
|
||||
options.headers.requesttoken = OC().requestToken;
|
||||
return fetch(path, options);
|
||||
},
|
||||
|
||||
davFetch = (mode, path, options) => {
|
||||
let cfg = rl.settings.get('Nextcloud');
|
||||
// cfg.UID = document.head.dataset.user
|
||||
return fetch(cfg.WebDAV + '/' + mode + '/' + cfg.UID + path, options);
|
||||
return ncFetch(cfg.WebDAV + '/' + mode + '/' + cfg.UID + path, options);
|
||||
},
|
||||
|
||||
davFetchFiles = (path, options) => davFetch('files', path, options),
|
||||
|
|
@ -129,6 +108,7 @@ const
|
|||
elem.id = e.getElementsByTagNameNS(nsOC, 'fileid')?.item(0)?.textContent;
|
||||
elem.size = getDavElementByTagName(e, 'getcontentlength')?.textContent
|
||||
|| getElementByTagName(e, 'oc:size')?.textContent;
|
||||
elem.shared = [...e.getElementsByTagNameNS(nsOC, 'share-type')].some(node => '3' == node.textContent);
|
||||
}
|
||||
elemList.push(elem);
|
||||
}
|
||||
|
|
@ -176,22 +156,25 @@ const
|
|||
li.dataset.icon = '🗎';
|
||||
|
||||
btn.name = 'select';
|
||||
btn.textContent = 'select';
|
||||
btn.dataset.icon = '📎';
|
||||
btn.textContent = 'attach';
|
||||
btn.className = 'button-vue';
|
||||
li.append(btn);
|
||||
|
||||
btn = document.createElement('button');
|
||||
btn.name = 'share-internal';
|
||||
btn.textContent = '🔗 internal';
|
||||
btn.dataset.icon = '🔗';
|
||||
btn.textContent = 'internal';
|
||||
btn.className = 'button-vue';
|
||||
li.append(btn);
|
||||
/*
|
||||
|
||||
btn = document.createElement('button');
|
||||
btn.name = 'share-public';
|
||||
btn.textContent = '🔗 public';
|
||||
btn.dataset.icon = '🔗';
|
||||
btn.textContent = 'public';
|
||||
btn.className = 'button-vue';
|
||||
li.append(btn);
|
||||
*/
|
||||
|
||||
parent.append(li);
|
||||
}
|
||||
});
|
||||
|
|
@ -225,46 +208,48 @@ class NextcloudFilesPopupView extends rl.pluginPopupView {
|
|||
this.tree.addEventListener('click', event => {
|
||||
let el = event.target;
|
||||
if (el.matches('button')) {
|
||||
let parent = el.parentNode;
|
||||
let parent = el.parentNode,
|
||||
item = parent.item;
|
||||
if ('select' == el.name) {
|
||||
this.select = this.files() ? [parent.item] : parent.item_name;
|
||||
this.select = this.files() ? [item] : parent.item_name;
|
||||
this.close();
|
||||
} else if ('share-internal' == el.name) {
|
||||
this.select = [{url:generateRemoteUrl(`/f/${parent.item.id}`)}];
|
||||
this.select = [{url:generateRemoteUrl(`/f/${item.id}`)}];
|
||||
this.close();
|
||||
} else if ('share-public' == el.name) {
|
||||
/*
|
||||
if (3 == share-type) {
|
||||
GET generateUrl(`/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json&path=${encodeURIComponent(parent.item.name)}&reshares=true`);
|
||||
if (item.shared) {
|
||||
ncFetch(
|
||||
shareUrl() + `?format=json&path=${encodeURIComponent(item.name)}&reshares=true`
|
||||
)
|
||||
.then(response => (response.status < 400) ? response.json() : Promise.reject(new Error({ response })))
|
||||
.then(json => {
|
||||
this.select = [{url:json.ocs.data[0].url}];
|
||||
this.close();
|
||||
// json.data[0].password
|
||||
});
|
||||
} else {
|
||||
POST generateUrl(`/ocs/v2.php/apps/files_sharing/api/v1/shares`)
|
||||
> {"path":"/Nextcloud intro.mp4","shareType":3,"attributes":"[]"}
|
||||
< {"ocs":{"meta":{"status":"ok","statuscode":200,"message":"OK"},
|
||||
"data":{
|
||||
"id":"2",
|
||||
"share_type":3,
|
||||
"permissions":17,
|
||||
"token":"7GK9mL9LCTseSgK",
|
||||
"path":"\/Nextcloud intro.mp4",
|
||||
"item_type":"file",
|
||||
"mimetype":"video\/mp4",
|
||||
"storage":1,
|
||||
"item_source":20,
|
||||
"file_source":20,
|
||||
"file_parent":2,
|
||||
"file_target":"\/Nextcloud intro.mp4",
|
||||
"password":null,
|
||||
"url":"https:\/\/example.com\/index.php\/s\/7GK9mL9LCTseSgK",
|
||||
"mail_send":1,
|
||||
"hide_download":0,
|
||||
"attributes":null
|
||||
}}}
|
||||
GET /index.php/s/7GK9mL9LCTseSgK
|
||||
PUT /ocs/v2.php/apps/files_sharing/api/v1/shares/2
|
||||
> {"expireDate":"\"2022-11-29T23:00:00.000Z\""}
|
||||
> {"password":"ABC09"}
|
||||
ncFetch(
|
||||
shareUrl(),
|
||||
{
|
||||
method:'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
path:item.name,
|
||||
shareType:3,
|
||||
attributes:"[]"
|
||||
})
|
||||
}
|
||||
)
|
||||
.then(response => (response.status < 400) ? response.json() : Promise.reject(new Error({ response })))
|
||||
.then(json => {
|
||||
// PUT /ocs/v2.php/apps/files_sharing/api/v1/shares/2 {"password":"ABC09"}
|
||||
this.select = [{url:json.ocs.data.url}];
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
*/
|
||||
} else if ('create' == el.name) {
|
||||
let name = el.input.value.replace(/[|\\?*<":>+[]\/&\s]/g, '');
|
||||
if (name.length) {
|
||||
|
|
@ -384,20 +369,29 @@ rl.nextcloud = {
|
|||
}),
|
||||
|
||||
calendarPut: (path, event) => {
|
||||
// Validation error in iCalendar: A calendar object on a CalDAV server MUST NOT have a METHOD property.
|
||||
event = event.replace(/METHOD:.+\r?\n/i, '');
|
||||
|
||||
let m = event.match(/UID:(.+)/);
|
||||
davFetch('calendars', path + '/' + m[1] + '.ics', {
|
||||
davFetch('calendars', path + '/' + event.UID + '.ics', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'text/calendar'
|
||||
},
|
||||
body: event
|
||||
// Validation error in iCalendar: A calendar object on a CalDAV server MUST NOT have a METHOD property.
|
||||
body: event.rawText
|
||||
.replace('METHOD:', 'X-METHOD:')
|
||||
// https://github.com/nextcloud/calendar/issues/4684
|
||||
.replace('ATTENDEE:', 'X-ATTENDEE:')
|
||||
.replace('ORGANIZER:', 'X-ORGANIZER:')
|
||||
.replace(/RSVP=TRUE/g, 'RSVP=FALSE')
|
||||
.replace(/\r?\n/g, '\r\n')
|
||||
})
|
||||
.then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response })))
|
||||
.then(text => {
|
||||
console.dir({event_response:text});
|
||||
.then(response => {
|
||||
if (201 == response.status) {
|
||||
// Created
|
||||
} else if (204 == response.status) {
|
||||
// Not modified
|
||||
} else {
|
||||
// response.text().then(text => console.error({status:response.status, body:text}));
|
||||
Promise.reject(new Error({ response }));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
}
|
||||
#V-PopupsNextcloudFiles li {
|
||||
clear: right;
|
||||
line-height: 2.5em;
|
||||
}
|
||||
#V-PopupsNextcloudFiles li button {
|
||||
cursor: pointer;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Override SMTP Credentials',
|
||||
VERSION = '2.2',
|
||||
RELEASE = '2022-04-13',
|
||||
REQUIRED = '2.5.0',
|
||||
VERSION = '2.3',
|
||||
RELEASE = '2022-11-11',
|
||||
REQUIRED = '2.21.0',
|
||||
CATEGORY = 'Filters',
|
||||
DESCRIPTION = 'Override SMTP credentials for specific users.';
|
||||
|
||||
|
|
@ -19,46 +19,43 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
/**
|
||||
* @param \RainLoop\Model\Account $oAccount
|
||||
* @param \MailSo\Smtp\SmtpClient $oSmtpClient
|
||||
* @param array $aSmtpCredentials
|
||||
* @param \MailSo\Smtp\Settings $oSettings
|
||||
*/
|
||||
public function FilterSmtpCredentials($oAccount, $oSmtpClient, &$aSmtpCredentials)
|
||||
public function FilterSmtpCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Smtp\SmtpClient $oSmtpClient, \MailSo\Smtp\Settings $oSettings)
|
||||
{
|
||||
if ($oAccount instanceof \RainLoop\Model\Account && \is_array($aSmtpCredentials))
|
||||
$sEmail = $oAccount->Email();
|
||||
|
||||
$sHost = \trim($this->Config()->Get('plugin', 'smtp_host', ''));
|
||||
$sWhiteList = \trim($this->Config()->Get('plugin', 'override_users', ''));
|
||||
|
||||
$sFoundValue = '';
|
||||
if (0 < strlen($sWhiteList) && 0 < \strlen($sHost) && \RainLoop\Plugins\Helper::ValidateWildcardValues($sEmail, $sWhiteList, $sFoundValue))
|
||||
{
|
||||
$sEmail = $oAccount->Email();
|
||||
\SnappyMail\LOG::debug('SMTP Override', "{$sEmail} matched {$sFoundValue}");
|
||||
$oSettings->host = $sHost;
|
||||
$oSettings->port = (int) $this->Config()->Get('plugin', 'smtp_port', 25);
|
||||
|
||||
$sHost = \trim($this->Config()->Get('plugin', 'smtp_host', ''));
|
||||
$sWhiteList = \trim($this->Config()->Get('plugin', 'override_users', ''));
|
||||
|
||||
$sFoundValue = '';
|
||||
if (0 < strlen($sWhiteList) && 0 < \strlen($sHost) && \RainLoop\Plugins\Helper::ValidateWildcardValues($sEmail, $sWhiteList, $sFoundValue))
|
||||
$sSecure = \trim($this->Config()->Get('plugin', 'smtp_secure', 'None'));
|
||||
switch ($sSecure)
|
||||
{
|
||||
\SnappyMail\LOG::debug('SMTP Override', "{$sEmail} matched {$sFoundValue}");
|
||||
$aSmtpCredentials['Host'] = $sHost;
|
||||
$aSmtpCredentials['Port'] = (int) $this->Config()->Get('plugin', 'smtp_port', 25);
|
||||
|
||||
$sSecure = \trim($this->Config()->Get('plugin', 'smtp_secure', 'None'));
|
||||
switch ($sSecure)
|
||||
{
|
||||
case 'SSL':
|
||||
$aSmtpCredentials['Secure'] = MailSo\Net\Enumerations\ConnectionSecurityType::SSL;
|
||||
break;
|
||||
case 'TLS':
|
||||
$aSmtpCredentials['Secure'] = MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS;
|
||||
break;
|
||||
default:
|
||||
$aSmtpCredentials['Secure'] = MailSo\Net\Enumerations\ConnectionSecurityType::NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
$aSmtpCredentials['UseAuth'] = (bool) $this->Config()->Get('plugin', 'smtp_auth', true);
|
||||
$aSmtpCredentials['Login'] = \trim($this->Config()->Get('plugin', 'smtp_user', ''));
|
||||
$aSmtpCredentials['Password'] = (string) $this->Config()->Get('plugin', 'smtp_password', '');
|
||||
}
|
||||
else
|
||||
{
|
||||
\SnappyMail\LOG::debug('SMTP Override', "{$sEmail} no match");
|
||||
case 'SSL':
|
||||
$oSettings->type = MailSo\Net\Enumerations\ConnectionSecurityType::SSL;
|
||||
break;
|
||||
case 'TLS':
|
||||
$oSettings->type = MailSo\Net\Enumerations\ConnectionSecurityType::STARTTLS;
|
||||
break;
|
||||
default:
|
||||
$oSettings->type = MailSo\Net\Enumerations\ConnectionSecurityType::NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
$oSettings->useAuth = (bool) $this->Config()->Get('plugin', 'smtp_auth', true);
|
||||
$oSettings->Login = \trim($this->Config()->Get('plugin', 'smtp_user', ''));
|
||||
$oSettings->Password = (string) $this->Config()->Get('plugin', 'smtp_password', '');
|
||||
}
|
||||
else
|
||||
{
|
||||
\SnappyMail\LOG::debug('SMTP Override', "{$sEmail} no match");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||