forked from extern/egroupware
WIP Oauth authentication for Office365:
- add all Microsoft email domains - using login.microsoftonline.com/common as OAuth provider URL - use mail-server name to detect custom mail domains --> auth with IMAP agains outlook.office365.com still NOT working, probably needs some kind of further verification / being an Microsoft partner
This commit is contained in:
parent
5d385455d2
commit
4a70021f41
@ -33,7 +33,7 @@ class admin_mail
|
||||
/**
|
||||
* Enable logging of IMAP communication to given path, eg. /tmp/autoconfig.log
|
||||
*/
|
||||
const DEBUG_LOG = null;
|
||||
const DEBUG_LOG = '/var/lib/egroupware/imap.log';
|
||||
/**
|
||||
* Connection timeout in seconds used in autoconfig, can and should be really short!
|
||||
*/
|
||||
@ -233,14 +233,7 @@ class admin_mail
|
||||
{
|
||||
$content['output'] .= lang('Using IMAP:%1, SMTP:%2, OAUTH:%3:', $oauth['imap'], $oauth['smtp'], $oauth['provider'])."\n";
|
||||
$hosts[$oauth['imap']] = true;
|
||||
$content['acc_smpt_host'] = $oauth['smtp'];
|
||||
$content['acc_sieve_enabled'] = false;
|
||||
$content['acc_oauth_provider_url'] = $oauth['provider'];
|
||||
$content['acc_oauth_client_id'] = $oauth['client'];
|
||||
$content['acc_oauth_client_secret'] = $oauth['secret'];
|
||||
$content['acc_oauth_scopes'] = $oauth['scopes'];
|
||||
$content[OpenIDConnectClient::ADD_CLIENT_TO_WELL_KNOWN] = $oauth[OpenIDConnectClient::ADD_CLIENT_TO_WELL_KNOWN] ?? null;
|
||||
$content[OpenIDConnectClient::ADD_AUTH_PARAM] = $oauth[OpenIDConnectClient::ADD_AUTH_PARAM] ?? null;
|
||||
$content += self::oauth2content($oauth);
|
||||
}
|
||||
elseif (!empty($content['acc_imap_host']))
|
||||
{
|
||||
@ -292,6 +285,11 @@ class admin_mail
|
||||
// iterate over all hosts and try to connect
|
||||
foreach(!isset($connected) ? $hosts : [] as $host => $data)
|
||||
{
|
||||
// check if we support OAuth for the (manual) configured mail-server
|
||||
if (empty($content['acc_oauth_provider_url']) && ($oauth = OpenIDConnectClient::providerByDomain($content['acc_imap_username'], $host)))
|
||||
{
|
||||
$content += self::oauth2content($oauth);
|
||||
}
|
||||
$content['acc_imap_host'] = $host;
|
||||
// by default we check SSL, STARTTLS and at last an insecure connection
|
||||
if (!is_array($data)) $data = array('TLS' => 993, 'SSL' => 993, 'STARTTLS' => 143, 'insecure' => 143);
|
||||
@ -375,6 +373,26 @@ class admin_mail
|
||||
array_diff_key($content, ['output'=>true]), 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert OAuth provider data to our content-names
|
||||
*
|
||||
* @param array $oauth
|
||||
* @return array
|
||||
*/
|
||||
protected static function oauth2content(array $oauth)
|
||||
{
|
||||
return [
|
||||
'acc_smpt_host' => $oauth['smtp'],
|
||||
'acc_sieve_enabled' => false,
|
||||
'acc_oauth_provider_url' => $oauth['provider'],
|
||||
'acc_oauth_client_id' => $oauth['client'],
|
||||
'acc_oauth_client_secret' => $oauth['secret'],
|
||||
'acc_oauth_scopes' => $oauth['scopes'],
|
||||
OpenIDConnectClient::ADD_CLIENT_TO_WELL_KNOWN => $oauth[OpenIDConnectClient::ADD_CLIENT_TO_WELL_KNOWN] ?? null,
|
||||
OpenIDConnectClient::ADD_AUTH_PARAM => $oauth[OpenIDConnectClient::ADD_AUTH_PARAM] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Folder - let user select trash, sent, drafs and template folder
|
||||
*
|
||||
@ -1492,7 +1510,7 @@ class admin_mail
|
||||
// Google requires access_type=offline&prompt=consent to return a refresh-token
|
||||
if (!empty($content[OpenIDConnectClient::ADD_AUTH_PARAM]))
|
||||
{
|
||||
$oidc->addAuthParam($content[OpenIDConnectClient::ADD_AUTH_PARAM]);
|
||||
$oidc->addAuthParam(str_replace('$username', $content['acc_oauth_username'] ?? $content['acc_imap_username'], $content[OpenIDConnectClient::ADD_AUTH_PARAM]));
|
||||
}
|
||||
|
||||
// we need to use response_code=query / GET request to keep our session token!
|
||||
|
@ -51,15 +51,22 @@ class OpenIDConnectClient extends \Jumbojett\OpenIDConnectClient
|
||||
* @var array[] email-regexp => [imap-host, smtp-host, oauth-provider, client-id, client-secret, scopes] pairs
|
||||
*/
|
||||
public static $oauth_domain_regexps = [
|
||||
'/(^|@)([^.@]+\.onmicrosoft\.com)$/i' => ['outlook.office365.com', 'smtp.office365.com', 'login.microsoftonline.com/$2',
|
||||
// MS domains from https://www.internetearnings.com/how-to-register-live-or-hotmail-e-mail-address/
|
||||
'/(^|@)([^.@]+\.onmicrosoft\.com|'.
|
||||
'outlook\.(sa|com|com\.(ar|au|cz|gr|in|tw|tr|vn)|co\.(in|th)|at|cl|fr|de|hu|ie|it|jp|kr|lv|my|ph|pt|sg|sk|es)|'.
|
||||
'hotmail\.(com|com\.(ar|au|br|hk|tr|vn)|co\.(in|il|jp|kr|za|th|uk)|be|ca|cz|cl|dk|fi|fr|gr|de|hu|it|lv|lt|my|nl|no|ph|rs|sg|sk|es|se)|'.
|
||||
'live\.(com|com\.(ar|br|my|mx|ph|pt|sg)|co\.(il|kr|za|uk)|at|be|ca|cl|cn|dk|fi|fr|de|hk|ie|it|jp|nl|no|ru|se)|'.
|
||||
'windowslive\.com|livemail\.tw)$/i' => ['outlook.office365.com', 'smtp.office365.com', 'login.microsoftonline.com/common',
|
||||
'e09fe57b-ffc5-496e-9ef8-3e6c7d628c09', 'Hd18Q~t-8_-ImvPFXlh8DSFjWKYyvpUTqURRJc7i',
|
||||
'https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access email',
|
||||
[self::ADD_CLIENT_TO_WELL_KNOWN => 'appid']],
|
||||
'https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access email',
|
||||
[/*self::ADD_CLIENT_TO_WELL_KNOWN => 'appid',*/ self::ADD_AUTH_PARAM => ['login_hint' => '$username', 'approval_prompt' => 'auto']],
|
||||
null],
|
||||
'/(^|@)g(oogle)?mail\.com$/i' => ['imap.gmail.com', 'smtp.gmail.com', 'accounts.google.com',
|
||||
'581021931838-unqjf9tivr9brnmo34rbsoj179ojp79p.apps.googleusercontent.com', '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',
|
||||
'https://mail.google.com/ https://www.googleapis.com/auth/userinfo.email',
|
||||
// https://stackoverflow.com/questions/10827920/not-receiving-google-oauth-refresh-token
|
||||
[self::ADD_AUTH_PARAM => ['access_type' => 'offline', 'prompt' => 'consent']]],
|
||||
[self::ADD_AUTH_PARAM => ['access_type' => 'offline', 'prompt' => 'consent']],
|
||||
'/^(imap|smtp|mail)\.g(oogle)?mail\.com$/i'],
|
||||
];
|
||||
|
||||
public function __construct($provider_url = null, $client_id = null, $client_secret = null, $issuer = null)
|
||||
@ -71,19 +78,27 @@ class OpenIDConnectClient extends \Jumbojett\OpenIDConnectClient
|
||||
|
||||
// ToDo: set proxy, if configured in EGroupware
|
||||
//$this->setHttpProxy("http://my.proxy.com:80/");
|
||||
|
||||
// login.microsoftonline.com/common returns as issuer an URL with {tenantid}
|
||||
if ($this->getProviderURL() === 'https://login.microsoftonline.com/common')
|
||||
{
|
||||
$this->setIssuerValidator(new MicrosoftIssuerValidator($this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find server config by email-domain incl. oauth data
|
||||
*
|
||||
* @param string $domain domain or email address
|
||||
* @param string $mailserver option name of imap or smtp server to identify the provider
|
||||
* @return array|null for keys provider, client, secret, scopes, imap, smtp
|
||||
*/
|
||||
public static function providerByDomain($domain)
|
||||
public static function providerByDomain($domain, $mailserver=null)
|
||||
{
|
||||
foreach(self::$oauth_domain_regexps as $regexp => [$imap, $smtp, $provider, $client, $secret, $scopes, $extra])
|
||||
foreach(self::$oauth_domain_regexps as $regexp => [$imap, $smtp, $provider, $client, $secret, $scopes, $extra, $server_regexp])
|
||||
{
|
||||
if (preg_match($regexp, $domain, $matches))
|
||||
if (preg_match($regexp, $domain, $matches) ||
|
||||
!empty($mailserver) && (in_array($mailserver, [$imap, $smtp]) || !empty($server_regexp) && preg_match($server_regexp, $mailserver)))
|
||||
{
|
||||
return [
|
||||
'imap' => $imap,
|
||||
@ -124,7 +139,7 @@ class OpenIDConnectClient extends \Jumbojett\OpenIDConnectClient
|
||||
// Google requires access_type=offline to return a refresh-token
|
||||
if (!empty($provider[self::ADD_AUTH_PARAM]))
|
||||
{
|
||||
$oidc->addAuthParam($provider[self::ADD_AUTH_PARAM]);
|
||||
$oidc->addAuthParam(str_replace('$username', $domain, $provider[self::ADD_AUTH_PARAM]));
|
||||
}
|
||||
|
||||
$oidc->addScope($provider['scopes']);
|
||||
@ -208,4 +223,36 @@ class OpenIDConnectClient extends \Jumbojett\OpenIDConnectClient
|
||||
}
|
||||
call_user_func_array([$oidc, 'authenticateThen'], $authenticateThenParams);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* login.microsoftonline.com/common returns as issuer an URL with {tenantid}
|
||||
*
|
||||
* We currently only check the there is some reasonable tenantid, not necessary the correct one, for which we would need to know the tenant.
|
||||
*/
|
||||
class MicrosoftIssuerValidator
|
||||
{
|
||||
/**
|
||||
* @var OpenIDConnectClient
|
||||
*/
|
||||
private $oidc;
|
||||
|
||||
public function __construct(OpenIDConnectClient $oidc)
|
||||
{
|
||||
$this->oidc = $oidc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator for Microsoft issuer
|
||||
*
|
||||
* @param string $iss
|
||||
* @return bool
|
||||
* @throws OpenIDConnectClientException
|
||||
*/
|
||||
public function __invoke($iss)
|
||||
{
|
||||
$issuer_regexp = '#^'.str_replace('{tenantid}', '[a-f0-9-]+', $this->oidc->getWellKnownIssuer()).'$#';
|
||||
|
||||
return (bool)preg_match($issuer_regexp, $iss);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user