Request: loginRequired => loginRequirements, SsoRequest, SAML
This commit is contained in:
@@ -9,7 +9,7 @@ namespace Core\API {
|
||||
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
}
|
||||
|
||||
protected function fetchAPIKey(int $apiKeyId): ApiKey|bool {
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Core\API {
|
||||
abstract class GpgKeyAPI extends \Core\API\Request {
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ namespace Core\API\GpgKey {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"publicKey" => new StringType("publicKey")
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
$this->forbidMethod("GET");
|
||||
}
|
||||
|
||||
@@ -125,7 +124,6 @@ namespace Core\API\GpgKey {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
"password" => new StringType("password")
|
||||
));
|
||||
$this->loginRequired = true;
|
||||
$this->forbidMethod("GET");
|
||||
}
|
||||
|
||||
@@ -159,7 +157,6 @@ namespace Core\API\GpgKey {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"token" => new StringType("token", 36)
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -209,7 +206,6 @@ namespace Core\API\GpgKey {
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT, true, null),
|
||||
"format" => new StringType("format", 16, true, "ascii")
|
||||
));
|
||||
$this->loginRequired = true;
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Core\API\Language {
|
||||
use Core\API\Parameter\Parameter;
|
||||
use Core\API\Parameter\RegexType;
|
||||
use Core\API\Parameter\StringType;
|
||||
use Core\API\Request;
|
||||
use Core\Driver\SQL\Condition\Compare;
|
||||
use Core\Driver\SQL\Condition\CondOr;
|
||||
use Core\Objects\Context;
|
||||
@@ -126,7 +127,7 @@ namespace Core\API\Language {
|
||||
"modules" => new ArrayType("modules", Parameter::TYPE_STRING, true, false),
|
||||
"compression" => new StringType("compression", -1, true, NULL, ["gzip", "zlib"])
|
||||
]);
|
||||
$this->loginRequired = false;
|
||||
$this->loginRequirements = Request::NONE;
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@ use PhpMqtt\Client\MqttClient;
|
||||
// TODO: many things are only checked for external calls, e.g. loginRequired. If we call the API internally, we might get null-pointers for $context->user
|
||||
abstract class Request {
|
||||
|
||||
// Login Requirements
|
||||
const NONE = 0;
|
||||
const LOGGED_IN = 1;
|
||||
const NOT_LOGGED_IN = 2;
|
||||
|
||||
protected Context $context;
|
||||
protected Logger $logger;
|
||||
protected array $params;
|
||||
@@ -21,7 +26,7 @@ abstract class Request {
|
||||
protected array $result;
|
||||
protected bool $success;
|
||||
protected bool $isPublic;
|
||||
protected bool $loginRequired;
|
||||
protected int $loginRequirements;
|
||||
protected bool $variableParamCount;
|
||||
protected bool $isDisabled;
|
||||
protected bool $apiKeyAllowed;
|
||||
@@ -47,9 +52,9 @@ abstract class Request {
|
||||
// restrictions
|
||||
$this->isPublic = true;
|
||||
$this->isDisabled = false;
|
||||
$this->loginRequired = false;
|
||||
$this->loginRequirements = self::NONE;
|
||||
$this->apiKeyAllowed = true;
|
||||
$this->allowedMethods = array("GET", "POST");
|
||||
$this->allowedMethods = ["GET", "POST"];
|
||||
$this->csrfTokenRequired = true;
|
||||
$this->rateLimiting = null;
|
||||
}
|
||||
@@ -270,8 +275,8 @@ abstract class Request {
|
||||
}
|
||||
}
|
||||
|
||||
// Logged in or api key authorized?
|
||||
if ($this->loginRequired) {
|
||||
if ($this->loginRequirements === self::LOGGED_IN) {
|
||||
// Logged in or api key authorized?
|
||||
if (!$session && !$apiKeyAuthorized) {
|
||||
$this->lastError = 'You are not logged in.';
|
||||
$this->result["loggedIn"] = false;
|
||||
@@ -281,6 +286,12 @@ abstract class Request {
|
||||
http_response_code(401);
|
||||
return false;
|
||||
}
|
||||
} else if ($this->loginRequirements === self::NOT_LOGGED_IN) {
|
||||
// Request only for unauthenticated users, e.g. login endpoint
|
||||
if ($session || $apiKeyAuthorized) {
|
||||
$this->lastError = "You are already logged in.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// CSRF Token
|
||||
@@ -383,7 +394,7 @@ abstract class Request {
|
||||
}
|
||||
|
||||
public function loginRequired(): bool {
|
||||
return $this->loginRequired;
|
||||
return $this->loginRequirements === self::LOGGED_IN;
|
||||
}
|
||||
|
||||
public function isExternalCall(): bool {
|
||||
|
||||
@@ -41,26 +41,39 @@ namespace Core\API\Sso {
|
||||
|
||||
use Core\API\Parameter\StringType;
|
||||
use Core\API\Parameter\UuidType;
|
||||
use Core\API\Request;
|
||||
use Core\Objects\Context;
|
||||
use Core\API\SsoAPI;
|
||||
use Core\Objects\DatabaseEntity\Group;
|
||||
use Core\Objects\DatabaseEntity\SsoProvider;
|
||||
use Core\Objects\RateLimiting;
|
||||
use Core\Objects\RateLimitRule;
|
||||
use Core\Objects\SSO\SAMLResponse;
|
||||
|
||||
class GetProviders extends SsoAPI {
|
||||
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, []);
|
||||
// TODO: auto-generated method stub
|
||||
$this->csrfTokenRequired = false;
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
// TODO: auto-generated method stub
|
||||
return $this->success;
|
||||
|
||||
$sql = $this->context->getSQL();
|
||||
$query = SsoProvider::createBuilder($sql, false);
|
||||
|
||||
if (!$this->context->getUser()) {
|
||||
$query->whereTrue("active");
|
||||
}
|
||||
|
||||
$providers = SsoProvider::findBy($query);
|
||||
$this->result["providers"] = SsoProvider::toJsonArray($providers);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getDescription(): string {
|
||||
// TODO: auto generated endpoint description
|
||||
return "Short description, what users are able to do with this endpoint.";
|
||||
return "Allows users to get a list of SSO providers. Unauthenticated users will only see active providers.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,14 +151,13 @@ namespace Core\API\Sso {
|
||||
"redirect" => new StringType("redirect", StringType::UNLIMITED, true, null)
|
||||
]);
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->loginRequirements = Request::NOT_LOGGED_IN;
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
new RateLimitRule(5, 1, RateLimitRule::MINUTE)
|
||||
);
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
|
||||
if ($this->context->getUser()) {
|
||||
return $this->createError("You are already logged in.");
|
||||
}
|
||||
|
||||
$redirectUrl = $this->getParam("redirect");
|
||||
if (!$this->validateRedirectURL($redirectUrl)) {
|
||||
return $this->createError("Invalid redirect URL");
|
||||
@@ -186,24 +198,30 @@ namespace Core\API\Sso {
|
||||
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"SAMLResponse" => new StringType("SAMLResponse"),
|
||||
"provider" => new UuidType("provider"),
|
||||
"redirect" => new StringType("redirect", StringType::UNLIMITED, true, null)
|
||||
"SAMLResponse" => new StringType("SAMLResponse")
|
||||
]);
|
||||
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->loginRequirements = Request::NOT_LOGGED_IN;
|
||||
$this->forbidMethod("GET");
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
new RateLimitRule(15, 1, RateLimitRule::MINUTE)
|
||||
);
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
|
||||
if ($this->context->getUser()) {
|
||||
return $this->createError("You are already logged in.");
|
||||
|
||||
$samlResponseEncoded = $this->getParam("SAMLResponse");
|
||||
if (($samlResponse = @gzinflate(base64_decode($samlResponseEncoded))) === false) {
|
||||
$samlResponse = base64_decode($samlResponseEncoded);
|
||||
}
|
||||
|
||||
$redirectUrl = $this->getParam("redirect");
|
||||
if (!$this->validateRedirectURL($redirectUrl)) {
|
||||
return $this->createError("Invalid redirect URL");
|
||||
$parsedResponse = SAMLResponse::parseResponse($this->context, $samlResponse);
|
||||
if (!$parsedResponse->wasSuccessful()) {
|
||||
return $this->createError("Error parsing SAMLResponse: " . $parsedResponse->getError());
|
||||
} else {
|
||||
return $this->processLogin($parsedResponse->getUser(), $parsedResponse->getRedirectURL());
|
||||
}
|
||||
|
||||
$sql = $this->context->getSQL();
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Core\API {
|
||||
|
||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
||||
parent::__construct($context, $externalCall, $params);
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
$this->apiKeyAllowed = false;
|
||||
$this->userVerificationRequired = false;
|
||||
}
|
||||
@@ -148,7 +148,7 @@ namespace Core\API\TFA {
|
||||
$twoFactorToken = $currentUser->getTwoFactorToken();
|
||||
if ($twoFactorToken && $twoFactorToken->isConfirmed()) {
|
||||
return $this->createError("You already added a two factor token");
|
||||
} else if (!$currentUser->isNativeAccount()) {
|
||||
} else if (!$currentUser->isLocalAccount()) {
|
||||
return $this->createError("Cannot add a 2FA token: Your account is managed by an external identity provider (SSO)");
|
||||
} else if (!($twoFactorToken instanceof TimeBasedTwoFactorToken)) {
|
||||
$sql = $this->context->getSQL();
|
||||
@@ -179,7 +179,6 @@ namespace Core\API\TFA {
|
||||
class ConfirmTotp extends VerifyTotp {
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall);
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -216,7 +215,6 @@ namespace Core\API\TFA {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"code" => new StringType("code", 6)
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
null,
|
||||
@@ -255,13 +253,12 @@ namespace Core\API\TFA {
|
||||
"clientDataJSON" => new StringType("clientDataJSON", 0, true, "{}"),
|
||||
"attestationObject" => new StringType("attestationObject", 0, true, "")
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
$currentUser = $this->context->getUser();
|
||||
if (!$currentUser->isNativeAccount()) {
|
||||
if (!$currentUser->isLocalAccount()) {
|
||||
return $this->createError("Cannot add a 2FA token: Your account is managed by an external identity provider (SSO)");
|
||||
}
|
||||
|
||||
@@ -357,7 +354,6 @@ namespace Core\API\TFA {
|
||||
"authData" => new StringType("authData"),
|
||||
"signature" => new StringType("signature"),
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
null,
|
||||
|
||||
@@ -148,6 +148,7 @@ namespace Core\API\User {
|
||||
use Core\API\Parameter\IntegerType;
|
||||
use Core\API\Parameter\Parameter;
|
||||
use Core\API\Parameter\StringType;
|
||||
use Core\API\Request;
|
||||
use Core\API\Template\Render;
|
||||
use Core\API\Traits\Captcha;
|
||||
use Core\API\Traits\Pagination;
|
||||
@@ -184,7 +185,7 @@ namespace Core\API\User {
|
||||
'groups' => new ArrayType("groups", Parameter::TYPE_INT, true, true, [])
|
||||
));
|
||||
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -304,7 +305,7 @@ namespace Core\API\User {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'id' => new Parameter('id', Parameter::TYPE_INT)
|
||||
));
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -447,7 +448,7 @@ namespace Core\API\User {
|
||||
'groups' => new ArrayType("groups", Parameter::TYPE_INT, true, true, [])
|
||||
));
|
||||
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -545,14 +546,10 @@ namespace Core\API\User {
|
||||
'confirmPassword' => new StringType('confirmPassword'),
|
||||
));
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->loginRequirements = Request::NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
if ($this->context->getUser()) {
|
||||
return $this->createError("You are already logged in.");
|
||||
}
|
||||
|
||||
$sql = $this->context->getSQL();
|
||||
$token = $this->getParam("token");
|
||||
$password = $this->getParam("password");
|
||||
@@ -593,17 +590,13 @@ namespace Core\API\User {
|
||||
'token' => new StringType('token', 36)
|
||||
));
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->loginRequirements = Request::NOT_LOGGED_IN;
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
new RateLimitRule(5, 1, RateLimitRule::MINUTE)
|
||||
);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
if ($this->context->getUser()) {
|
||||
return $this->createError("You are already logged in.");
|
||||
}
|
||||
|
||||
$sql = $this->context->getSQL();
|
||||
$token = $this->getParam("token");
|
||||
$userToken = $this->checkToken($token);
|
||||
@@ -708,7 +701,6 @@ namespace Core\API\User {
|
||||
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall);
|
||||
$this->loginRequired = false;
|
||||
$this->apiKeyAllowed = false;
|
||||
$this->forbidMethod("GET");
|
||||
}
|
||||
@@ -750,14 +742,10 @@ namespace Core\API\User {
|
||||
$this->addCaptchaParameters($context, $parameters);
|
||||
parent::__construct($context, $externalCall, $parameters);
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->loginRequirements = Request::NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
if ($this->context->getUser()) {
|
||||
return $this->createError(L('You are already logged in'));
|
||||
}
|
||||
|
||||
$settings = $this->context->getSettings();
|
||||
$registrationAllowed = $settings->isRegistrationAllowed();
|
||||
if (!$registrationAllowed) {
|
||||
@@ -848,7 +836,7 @@ namespace Core\API\User {
|
||||
class Edit extends UserAPI {
|
||||
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
parent::__construct($context, $externalCall, [
|
||||
'id' => new Parameter('id', Parameter::TYPE_INT),
|
||||
'username' => new StringType('username', 32, true, NULL),
|
||||
'fullName' => new StringType('fullName', 64, true, NULL),
|
||||
@@ -857,10 +845,10 @@ namespace Core\API\User {
|
||||
'groups' => new ArrayType('groups', Parameter::TYPE_INT, true, true, NULL),
|
||||
'confirmed' => new Parameter('confirmed', Parameter::TYPE_BOOLEAN, true, NULL),
|
||||
'active' => new Parameter('active', Parameter::TYPE_BOOLEAN, true, NULL)
|
||||
));
|
||||
]);
|
||||
|
||||
$this->loginRequired = true;
|
||||
$this->forbidMethod("GET");
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -980,7 +968,7 @@ namespace Core\API\User {
|
||||
'id' => new Parameter('id', Parameter::TYPE_INT)
|
||||
));
|
||||
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -1060,7 +1048,7 @@ namespace Core\API\User {
|
||||
} else if ($user !== null) {
|
||||
if (!$user->isActive()) {
|
||||
return $this->createError("This user is currently disabled. Contact the server administrator, if you believe this is a mistake.");
|
||||
} else if (!$user->isNativeAccount()) {
|
||||
} else if (!$user->isLocalAccount()) {
|
||||
// TODO: this allows user enumeration for SSO accounts
|
||||
return $this->createError("Cannot request a password reset: Account is managed by an external identity provider (SSO)");
|
||||
} else {
|
||||
@@ -1206,17 +1194,13 @@ namespace Core\API\User {
|
||||
$this->forbidMethod("GET");
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->apiKeyAllowed = false;
|
||||
$this->loginRequirements = Request::NOT_LOGGED_IN;
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
new RateLimitRule(5, 1, RateLimitRule::MINUTE)
|
||||
);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
if ($this->context->getUser()) {
|
||||
return $this->createError("You are already logged in.");
|
||||
}
|
||||
|
||||
$sql = $this->context->getSQL();
|
||||
$token = $this->getParam("token");
|
||||
$password = $this->getParam("password");
|
||||
@@ -1228,8 +1212,8 @@ namespace Core\API\User {
|
||||
return $this->createError("Invalid token type");
|
||||
}
|
||||
|
||||
$user = $token->getUser();
|
||||
if (!$user->isNativeAccount()) {
|
||||
$user = $userToken->getUser();
|
||||
if (!$user->isLocalAccount()) {
|
||||
return $this->createError("Cannot reset password: Your account is managed by an external identity provider (SSO)");
|
||||
} else if (!$this->checkPasswordRequirements($password, $confirmPassword)) {
|
||||
return false;
|
||||
@@ -1261,7 +1245,7 @@ namespace Core\API\User {
|
||||
'confirmPassword' => new StringType('confirmPassword', -1, true, NULL),
|
||||
'oldPassword' => new StringType('oldPassword', -1, true, NULL),
|
||||
));
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
$this->csrfTokenRequired = true;
|
||||
$this->apiKeyAllowed = false; // prevent account takeover when an API-key is stolen
|
||||
$this->forbidMethod("GET");
|
||||
@@ -1298,7 +1282,7 @@ namespace Core\API\User {
|
||||
}
|
||||
|
||||
if ($newPassword !== null || $newPasswordConfirm !== null) {
|
||||
if (!$currentUser->isNativeAccount()) {
|
||||
if (!$currentUser->isLocalAccount()) {
|
||||
return $this->createError("Cannot change password: Your account is managed by an external identity provider (SSO)");
|
||||
} else if (!$this->checkPasswordRequirements($newPassword, $newPasswordConfirm)) {
|
||||
return false;
|
||||
@@ -1338,7 +1322,7 @@ namespace Core\API\User {
|
||||
"y" => new FloatType("y", 0, PHP_FLOAT_MAX, true, NULL),
|
||||
"size" => new FloatType("size", self::MIN_SIZE, self::MAX_SIZE, true, NULL),
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
$this->forbidMethod("GET");
|
||||
}
|
||||
|
||||
@@ -1415,7 +1399,7 @@ namespace Core\API\User {
|
||||
class RemovePicture extends UserAPI {
|
||||
public function __construct(Context $context, bool $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, []);
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -1491,7 +1475,7 @@ namespace Core\API\User {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"active" => new Parameter("active", Parameter::TYPE_BOOLEAN, true, true)
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
@@ -1535,7 +1519,7 @@ namespace Core\API\User {
|
||||
parent::__construct($context, $externalCall, [
|
||||
"id" => new Parameter("id", Parameter::TYPE_INT)
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
$this->loginRequirements = Request::LOGGED_IN;
|
||||
}
|
||||
|
||||
protected function _execute(): bool {
|
||||
|
||||
Reference in New Issue
Block a user