467 lines
10 KiB
PHP
467 lines
10 KiB
PHP
<?php namespace Models\OAuth2;
|
|
/**
|
|
* Copyright 2016 OpenStack Foundation
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
**/
|
|
use App\Models\Utils\BaseEntity;
|
|
use Doctrine\ORM\Mapping AS ORM;
|
|
use DateTime;
|
|
use DateInterval;
|
|
use DateTimeZone;
|
|
use Illuminate\Support\Facades\Log;
|
|
use models\exceptions\ValidationException;
|
|
use OAuth2\OAuth2Protocol;
|
|
use OAuth2\Requests\OAuth2AccessTokenRequestPasswordless;
|
|
use Utils\IPHelper;
|
|
use Utils\Model\Identifier;
|
|
use Zend\Math\Rand;
|
|
|
|
/**
|
|
* @ORM\Entity(repositoryClass="App\Repositories\DoctrineOAuth2OTPRepository")
|
|
* @ORM\Table(name="oauth2_otp")
|
|
* Class OTP
|
|
* @package Models\OAuth2
|
|
*/
|
|
class OAuth2OTP extends BaseEntity implements Identifier
|
|
{
|
|
/**
|
|
* @ORM\Column(name="value", type="string")
|
|
* @var string
|
|
*/
|
|
private $value;
|
|
|
|
/**
|
|
* @ORM\Column(name="length", type="integer")
|
|
* @var int
|
|
*/
|
|
private $length;
|
|
|
|
/**
|
|
* @ORM\Column(name="`connection`", type="string")
|
|
* @var string
|
|
*/
|
|
private $connection;
|
|
|
|
/**
|
|
* @ORM\Column(name="send", type="string")
|
|
* @var string
|
|
*/
|
|
private $send;
|
|
|
|
/**
|
|
* @ORM\Column(name="scope", type="string")
|
|
* @var string
|
|
*/
|
|
private $scope;
|
|
|
|
/**
|
|
* @ORM\Column(name="email", type="string")
|
|
* @var string
|
|
*/
|
|
private $email;
|
|
|
|
/**
|
|
* @ORM\Column(name="phone_number", type="string")
|
|
* @var string
|
|
*/
|
|
private $phone_number;
|
|
|
|
/**
|
|
* @ORM\Column(name="nonce", type="string")
|
|
* @var string
|
|
*/
|
|
private $nonce;
|
|
|
|
/**
|
|
* @ORM\Column(name="lifetime", type="integer")
|
|
* @var int
|
|
*/
|
|
private $lifetime;
|
|
|
|
/**
|
|
* @ORM\Column(name="redirect_url", type="string")
|
|
* @var string
|
|
*/
|
|
private $redirect_url;
|
|
|
|
/**
|
|
* @var \DateTime
|
|
* @ORM\Column(name="redeemed_at", type="datetime")
|
|
*/
|
|
private $redeemed_at;
|
|
|
|
/**
|
|
* @var string
|
|
* @ORM\Column(name="redeemed_from_ip", type="string")
|
|
*/
|
|
private $redeemed_from_ip;
|
|
|
|
/**
|
|
* @ORM\Column(name="redeemed_attempts", type="integer")
|
|
* @var int
|
|
*/
|
|
private $redeemed_attempts;
|
|
|
|
/**
|
|
* @ORM\ManyToOne(targetEntity="Models\OAuth2\Client", inversedBy="otp_grants", cascade={"persist"})
|
|
* @ORM\JoinColumn(name="oauth2_client_id", referencedColumnName="id", nullable=true)
|
|
* @var Client
|
|
*/
|
|
private $client;
|
|
|
|
/**
|
|
* OAuth2OTP constructor.
|
|
* @param int $length
|
|
* @param int $lifetime
|
|
*/
|
|
public function __construct(int $length, int $lifetime = 0 )
|
|
{
|
|
parent::__construct();
|
|
$this->length = $length;
|
|
$this->lifetime = $lifetime;
|
|
$this->redeemed_at = null;
|
|
$this->redeemed_attempts = 0;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getValue(): string
|
|
{
|
|
return $this->value;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getConnection(): string
|
|
{
|
|
return $this->connection;
|
|
}
|
|
|
|
/**
|
|
* @param string $connection
|
|
*/
|
|
public function setConnection(string $connection): void
|
|
{
|
|
$this->connection = $connection;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getSend(): string
|
|
{
|
|
return $this->send;
|
|
}
|
|
|
|
/**
|
|
* @param string $send
|
|
*/
|
|
public function setSend(string $send): void
|
|
{
|
|
$this->send = $send;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getScope(): ?string
|
|
{
|
|
return $this->scope;
|
|
}
|
|
|
|
/**
|
|
* @param string $scope
|
|
*/
|
|
public function setScope(?string $scope): void
|
|
{
|
|
$this->scope = $scope;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getEmail(): ?string
|
|
{
|
|
return $this->email;
|
|
}
|
|
|
|
/**
|
|
* @param string $email
|
|
*/
|
|
public function setEmail(?string $email): void
|
|
{
|
|
$this->email = $email;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getPhoneNumber(): ?string
|
|
{
|
|
return $this->phone_number;
|
|
}
|
|
|
|
/**
|
|
* @param string $phone_number
|
|
*/
|
|
public function setPhoneNumber(?string $phone_number): void
|
|
{
|
|
$this->phone_number = $phone_number;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getNonce(): ?string
|
|
{
|
|
return $this->nonce;
|
|
}
|
|
|
|
/**
|
|
* @param string $nonce
|
|
*/
|
|
public function setNonce(?string $nonce): void
|
|
{
|
|
$this->nonce = $nonce;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getRedirectUrl(): ?string
|
|
{
|
|
return $this->redirect_url;
|
|
}
|
|
|
|
/**
|
|
* @param string $redirect_url
|
|
*/
|
|
public function setRedirectUrl(?string $redirect_url): void
|
|
{
|
|
$this->redirect_url = $redirect_url;
|
|
}
|
|
|
|
/**
|
|
* @return \DateTime|null
|
|
*/
|
|
public function getRedeemedAt(): ?\DateTime
|
|
{
|
|
return $this->redeemed_at;
|
|
}
|
|
|
|
public function isRedeemed():bool{
|
|
return !is_null($this->redeemed_at);
|
|
}
|
|
|
|
/**
|
|
* @throws ValidationException
|
|
*/
|
|
public function redeem(): void
|
|
{
|
|
if(!is_null($this->redeemed_at))
|
|
throw new ValidationException("OTP is already redeemed.");
|
|
$this->redeemed_at = new \DateTime('now', new \DateTimeZone('UTC'));
|
|
$this->redeemed_from_ip = IPHelper::getUserIp();
|
|
|
|
Log::debug(sprintf("OAuth2OTP::redeem from ip %s", $this->redeemed_from_ip));
|
|
}
|
|
|
|
/**
|
|
* @return Client
|
|
*/
|
|
public function getClient(): Client
|
|
{
|
|
return $this->client;
|
|
}
|
|
|
|
public function hasClient():bool{
|
|
return !is_null($this->client);
|
|
}
|
|
|
|
/**
|
|
* @param Client $client
|
|
*/
|
|
public function setClient(Client $client): void
|
|
{
|
|
$this->client = $client;
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
public function getLifetime(): int
|
|
{
|
|
return $this->lifetime;
|
|
}
|
|
|
|
/**
|
|
* @param int $lifetime
|
|
*/
|
|
public function setLifetime(int $lifetime): void
|
|
{
|
|
$this->lifetime = $lifetime;
|
|
}
|
|
|
|
public function getRemainingLifetime()
|
|
{
|
|
//check is refresh token is stills alive... (ZERO is infinite lifetime)
|
|
if (intval($this->lifetime) == 0) {
|
|
return 0;
|
|
}
|
|
$created_at = clone $this->created_at;
|
|
$created_at->add(new DateInterval('PT' . intval($this->lifetime) . 'S'));
|
|
$now = new DateTime(gmdate("Y-m-d H:i:s", time()), new DateTimeZone("UTC"));
|
|
//check validity...
|
|
if ($now > $created_at) {
|
|
return -1;
|
|
}
|
|
$seconds = abs($created_at->getTimestamp() - $now->getTimestamp());;
|
|
|
|
return $seconds;
|
|
}
|
|
|
|
public function isAlive():bool{
|
|
return $this->getRemainingLifetime() >= 0;
|
|
}
|
|
|
|
public function clearClient():void{
|
|
$this->client = null;
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
public function getLength(): int
|
|
{
|
|
return $this->length;
|
|
}
|
|
|
|
const MaxRedeemAttempts = 3;
|
|
|
|
public function logRedeemAttempt():void{
|
|
if($this->redeemed_attempts < self::MaxRedeemAttempts){
|
|
$this->redeemed_attempts = $this->redeemed_attempts + 1;
|
|
Log::debug(sprintf("OAuth2OTP::logRedeemAttempt redeemed_attempts %s", $this->redeemed_attempts));
|
|
}
|
|
}
|
|
|
|
public function isValid():bool{
|
|
return ($this->redeemed_attempts < self::MaxRedeemAttempts) && $this->isAlive();
|
|
}
|
|
|
|
public function getUserName():?string{
|
|
return $this->connection == OAuth2Protocol::OAuth2PasswordlessEmail ? $this->email : $this->phone_number;
|
|
}
|
|
|
|
/**
|
|
* @param string $scope
|
|
* @return bool
|
|
*/
|
|
public function allowScope(string $scope):bool{
|
|
$s1 = explode(" ", $scope);
|
|
$s2 = explode(" ", $this->scope);
|
|
return count(array_diff($s1, $s2)) == 0;
|
|
}
|
|
|
|
public function setValue(string $value)
|
|
{
|
|
$this->value = $value;
|
|
}
|
|
|
|
public function getType(): string
|
|
{
|
|
return "otp";
|
|
}
|
|
|
|
const VsChar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
|
|
public function generateValue(): string
|
|
{
|
|
// calculate value
|
|
// entropy(SHANNON FANO Approx) len * log(count(VsChar))/log(2) = bits of entropy
|
|
$this->value = Rand::getString($this->length, self::VsChar);
|
|
return $this->value;
|
|
}
|
|
|
|
/**
|
|
* @param OAuth2AccessTokenRequestPasswordless $request
|
|
* @param int $length
|
|
* @return OAuth2OTP
|
|
*/
|
|
public static function fromRequest(OAuth2AccessTokenRequestPasswordless $request, int $length):OAuth2OTP{
|
|
$instance = new self($length);
|
|
$instance->connection = $request->getConnection();
|
|
$instance->email = $request->getEmail();
|
|
$instance->phone_number = $request->getPhoneNumber();
|
|
$instance->scope = $request->getScopes();
|
|
$instance->value = $request->getOTP();
|
|
return $instance;
|
|
}
|
|
|
|
/**
|
|
* @param string $user_name
|
|
* @param string $connection
|
|
* @param string $value
|
|
* @return OAuth2OTP|null
|
|
*/
|
|
public static function fromParams(string $user_name, string $connection, string $value):?OAuth2OTP{
|
|
$instance = new self(strlen($value));
|
|
$instance->connection = $connection;
|
|
if($connection == OAuth2Protocol::OAuth2PasswordlessConnectionEmail)
|
|
$instance->email = $user_name;
|
|
if($connection == OAuth2Protocol::OAuth2PasswordlessConnectionEmail)
|
|
$instance->phone_number = $user_name;
|
|
$instance->value = $value;
|
|
return $instance;
|
|
}
|
|
|
|
// non db fields
|
|
|
|
private $auth_time;
|
|
|
|
private $user_id;
|
|
|
|
/**
|
|
* @param int $auth_time
|
|
*/
|
|
public function setAuthTime(int $auth_time): void
|
|
{
|
|
$this->auth_time = $auth_time;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $user_id
|
|
*/
|
|
public function setUserId($user_id): void
|
|
{
|
|
$this->user_id = $user_id;
|
|
}
|
|
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
public function getAuthTime()
|
|
{
|
|
return $this->auth_time;
|
|
}
|
|
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
public function getUserId()
|
|
{
|
|
return $this->user_id;
|
|
}
|
|
|
|
|
|
} |