Commit 3de850e7 authored by Emiliano Balbuena's avatar Emiliano Balbuena

(feat): SSO

1 merge request!400WIP: SSO for Pro sites
Pipeline #96001210 passed with stages
in 7 minutes and 55 seconds
<?php
/**
* authorize
* @author edgebal
*/
namespace Minds\Controllers\api\v2\sso;
use Exception;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\Session;
use Minds\Core\SSO\Manager;
use Minds\Entities\User;
use Minds\Interfaces;
use Zend\Diactoros\ServerRequest;
class authorize implements Interfaces\Api, Interfaces\ApiIgnorePam
{
/** @var ServerRequest */
public $request;
/**
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
*/
public function get($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
* @throws Exception
*/
public function post($pages)
{
$host = $this->request->getServerParams()['HTTP_HOST'] ?? '';
if (!$host) {
return Factory::response([
'status' => 'error',
'message' => 'No HTTP Host header',
]);
}
/** @var Manager $sso */
$sso = Di::_()->get('SSO');
$sso
->setDomain($host);
if (!$sso->isAllowed()) {
return Factory::response([
'status' => 'error',
'message' => 'Domain not allowed',
]);
}
// TODO: Use headers
$jwt = $_POST['token'];
$success = $sso
->authorize($jwt);
if (!$success) {
return Factory::response([
'status' => 'error',
'message' => 'Invalid token',
]);
}
/** @var User $currentUser */
$currentUser = Session::getLoggedinUser();
return Factory::response([
'user' => $currentUser ? $currentUser->export() : null,
]);
}
/**
* Equivalent to HTTP PUT method
* @param array $pages
* @return mixed|null
*/
public function put($pages)
{
return Factory::response([]);
}
/**
* Equivalent to HTTP DELETE method
* @param array $pages
* @return mixed|null
*/
public function delete($pages)
{
return Factory::response([]);
}
}
......@@ -6,6 +6,7 @@
namespace Minds\Controllers\api\v2\sso;
use Exception;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\SSO\Manager;
......@@ -31,6 +32,7 @@ class connect implements Interfaces\Api, Interfaces\ApiIgnorePam
* Equivalent to HTTP POST method
* @param array $pages
* @return mixed|null
* @throws Exception
*/
public function post($pages)
{
......@@ -53,7 +55,7 @@ class connect implements Interfaces\Api, Interfaces\ApiIgnorePam
if (!$sso->isAllowed()) {
return Factory::response([
'status' => 'error',
'message' => 'HTTP Origin not allowed'
'message' => 'Domain not allowed'
]);
}
......
......@@ -20,8 +20,7 @@ class ProDelegate
*/
public function __construct(
$proDomain = null
)
{
) {
$this->proDomain = $proDomain ?: Di::_()->get('Pro\Domain');
}
......
......@@ -6,14 +6,34 @@
namespace Minds\Core\SSO;
use Exception;
use Lcobucci\JWT\Token as JwtToken;
use Minds\Common\Jwt;
use Minds\Core\Config;
use Minds\Core\Data\cache\abstractCacher;
use Minds\Core\Di\Di;
use Minds\Core\Sessions\Manager as SessionsManager;
class Manager
{
/** @var int */
const JTW_EXPIRE = 300;
/** @var string */
const SESSION_COOKIE = 'minds_sess';
/** @var Config */
protected $config;
/** @var abstractCacher */
protected $cache;
/** @var Jwt */
protected $jwt;
/** @var SessionsManager */
protected $sessions;
/** @var Delegates\ProDelegate */
protected $proDelegate;
......@@ -22,15 +42,23 @@ class Manager
/**
* Manager constructor.
* @param Config $config
* @param abstractCacher $cache
* @param Jwt $jwt
* @param SessionsManager $sessions
* @param Delegates\ProDelegate $proDelegate
*/
public function __construct(
$config = null,
$cache = null,
$jwt = null,
$sessions = null,
$proDelegate = null
)
{
) {
$this->config = $config ?: Di::_()->get('Config');
$this->cache = $cache ?: Di::_()->get('Cache');
$this->jwt = $jwt ?: new Jwt();
$this->sessions = $sessions ?: Di::_()->get('Sessions\Manager');
$this->proDelegate = $proDelegate ?: new Delegates\ProDelegate();
}
......@@ -58,18 +86,75 @@ class Manager
/**
* @return string
* @throws Exception
*/
public function generateToken(): string
public function generateToken(): ?string
{
return $this->domain;
$now = time();
$session = $this->sessions->getSession();
if (!$session->getUserGuid()) {
return null;
}
$key = $this->config->get('oauth')['encryption_key'] ?? '';
if (!$key) {
throw new Exception('Invalid encryption key');
}
$sessionToken = (string) $session->getToken();
$sessionTokenHash = hash('sha256', $key . $sessionToken);
$ssoKey = implode(':', ['sso', $this->domain, $sessionTokenHash, $this->jwt->randomString()]);
$jwt = $this->jwt
->setKey($key)
->encode([
'key' => $ssoKey,
'domain' => $this->domain,
], $now, $now + static::JTW_EXPIRE);
$this->cache
->set($ssoKey, $sessionToken, static::JTW_EXPIRE * 2);
return $jwt;
}
/**
* @param string $token
* @param string $jwt
* @return bool
* @throws Exception
*/
public function verify(string $token): bool
public function authorize(string $jwt): bool
{
if (!$jwt) {
return false;
}
$key = $this->config->get('oauth')['encryption_key'] ?? '';
if (!$key) {
throw new Exception('Invalid encryption key');
}
try {
$data = $this->jwt
->setKey($key)
->decode($jwt);
$sessionToken = $this->cache
->get($data['key']);
if ($sessionToken) {
$this->sessions
->withString($sessionToken)
->save();
}
return true;
} catch (Exception $e) {
return false;
}
}
}
......@@ -12,7 +12,7 @@ class Provider extends DiProvider
{
public function register()
{
$this->di->bind('SSO', function() {
$this->di->bind('SSO', function () {
return new Manager();
});
}
......
......@@ -81,17 +81,27 @@ class Manager
/**
* Build session from jwt cookie
* @return $this
* @param $request
* @return Manager
*/
public function withRouterRequest($request)
public function withRouterRequest($request): Manager
{
$cookies = $request->getCookieParams();
if (!isset($cookies['minds_sess'])) {
return $this;
}
return $this->withString((string) $cookies['minds_sess']);
}
/**
* @param string $sessionToken
* @return Manager
*/
public function withString(string $sessionToken): Manager
{
try {
$token = $this->jwtParser->parse((string) $cookies['minds_sess']); // Collect from cookie
$token = $this->jwtParser->parse($sessionToken);
$token->getHeaders();
$token->getClaims();
} catch (\Exception $e) {
......
......@@ -2,10 +2,24 @@
/**
* Minds Session
*/
namespace Minds\Core\Sessions;
use Lcobucci\JWT\Token as JwtToken;
use Minds\Traits\MagicAttributes;
/**
* Class Session
* @package Minds\Core\Sessions
* @method string getId()
* @method Session setId(string $id)
* @method string|JwtToken getToken()
* @method Session setToken(string|JwtToken $token)
* @method int|string getUserGuid()
* @method Session setUserGuid(int|string $userGuid)
* @method int getExpires()
* @method Session setExpires(int $expires)
*/
class Session
{
use MagicAttributes;
......@@ -13,7 +27,7 @@ class Session
/** @var string $id */
private $id;
/** @var string $token */
/** @var string|JwtToken $token */
private $token;
/** @var int $userGuid */
......
Please register or to comment